zbus_macros-5.3.0/.cargo_vcs_info.json0000644000000001510000000000100133650ustar { "git": { "sha1": "af422fef144da647037f89df254132b838b71163" }, "path_in_vcs": "zbus_macros" }zbus_macros-5.3.0/Cargo.lock0000644000000332750000000000100113550ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "async-io" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock", "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", "rustix", "slab", "tracing", "windows-sys 0.59.0", ] [[package]] name = "async-lock" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ "event-listener", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "endi" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" [[package]] name = "enumflags2" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" dependencies = [ "enumflags2_derive", "serde", ] [[package]] name = "enumflags2_derive" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "event-listener" version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "event-listener-strategy" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ "event-listener", "pin-project-lite", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "futures-core", "pin-project-lite", ] [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-task", "pin-project-lite", "pin-utils", ] [[package]] name = "hashbrown" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "indexmap" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "parking" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "polling" version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", "rustix", "tracing", "windows-sys 0.52.0", ] [[package]] name = "proc-macro-crate" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "rustix" version = "0.38.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "serde" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "2.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" dependencies = [ "memchr", ] [[package]] name = "zbus_macros" version = "5.3.0" dependencies = [ "async-io", "futures-util", "proc-macro-crate", "proc-macro2", "quote", "serde", "syn", "zbus_names", "zvariant", "zvariant_utils", ] [[package]] name = "zbus_names" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "856b7a38811f71846fd47856ceee8bccaec8399ff53fb370247e66081ace647b" dependencies = [ "serde", "static_assertions", "winnow", "zvariant", ] [[package]] name = "zvariant" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55e6b9b5f1361de2d5e7d9fd1ee5f6f7fcb6060618a1f82f3472f58f2b8d4be9" dependencies = [ "endi", "enumflags2", "serde", "static_assertions", "winnow", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "573a8dd76961957108b10f7a45bac6ab1ea3e9b7fe01aff88325dc57bb8f5c8b" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", "zvariant_utils", ] [[package]] name = "zvariant_utils" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd46446ea2a1f353bfda53e35f17633afa79f4fe290a611c94645c69fe96a50" dependencies = [ "proc-macro2", "quote", "serde", "static_assertions", "syn", "winnow", ] zbus_macros-5.3.0/Cargo.toml0000644000000036130000000000100113710ustar # 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 = "2021" rust-version = "1.77" name = "zbus_macros" version = "5.3.0" authors = [ "Marc-André Lureau ", "Zeeshan Ali Khan ", ] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "proc-macros for zbus" readme = "README.md" keywords = [ "D-Bus", "DBus", "IPC", ] categories = [ "data-structures", "encoding", "parsing", ] license = "MIT" repository = "https://github.com/dbus2/zbus/" [lib] name = "zbus_macros" path = "src/lib.rs" proc-macro = true [[test]] name = "tests" path = "tests/tests.rs" [dependencies.proc-macro-crate] version = "3.2.0" [dependencies.proc-macro2] version = "1.0.81" [dependencies.quote] version = "1.0.36" [dependencies.syn] version = "2.0.64" features = [ "extra-traits", "fold", "full", ] [dependencies.zbus_names] version = "4.0" [dependencies.zvariant] version = "5.0.0" default-features = false [dependencies.zvariant_utils] version = "3.1.0" [dev-dependencies.async-io] version = "2.3.2" [dev-dependencies.futures-util] version = "0.3.30" default-features = false [dev-dependencies.serde] version = "1.0.200" features = ["derive"] [features] blocking-api = [] default = [] gvariant = [ "zvariant/gvariant", "zvariant_utils/gvariant", ] [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = ["cfg(tokio_unstable)"] zbus_macros-5.3.0/Cargo.toml.orig000064400000000000000000000022761046102023000150560ustar 00000000000000[package] name = "zbus_macros" # Keep version in sync with zbus crate version = "5.3.0" authors = [ "Marc-André Lureau ", "Zeeshan Ali Khan ", ] edition = "2021" rust-version = { workspace = true } description = "proc-macros for zbus" repository = "https://github.com/dbus2/zbus/" keywords = ["D-Bus", "DBus", "IPC"] license = "MIT" categories = ["data-structures", "encoding", "parsing"] readme = "README.md" [features] default = [] # Enable blocking API. blocking-api = ["zbus/blocking-api"] gvariant = ["zvariant/gvariant", "zvariant_utils/gvariant"] [lib] proc-macro = true [dependencies] proc-macro2 = "1.0.81" syn = { version = "2.0.64", features = ["extra-traits", "fold", "full"] } quote = "1.0.36" proc-macro-crate = "3.2.0" zvariant = { path = "../zvariant", version = "5.0.0", default-features = false } zbus_names = { path = "../zbus_names", version = "4.0" } zvariant_utils = { path = "../zvariant_utils", version = "3.1.0" } [dev-dependencies] zbus = { path = "../zbus" } serde = { version = "1.0.200", features = ["derive"] } async-io = "2.3.2" futures-util = { version = "0.3.30", default-features = false } [lints] workspace = true zbus_macros-5.3.0/LICENSE000064400000000000000000000020701046102023000131640ustar 00000000000000Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors Permission 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-5.3.0/README.md000064400000000000000000000010051046102023000134330ustar 00000000000000# zbus_macros [![](https://docs.rs/zbus_macros/badge.svg)](https://docs.rs/zbus_macros/) [![](https://img.shields.io/crates/v/zbus_macros)](https://crates.io/crates/zbus_macros) This subcrate of the [zbus project][zp] provides convenient attribute macros that make zbus super easy to use. The main subcrate, [`zbus`] re-exports these macros for your convenience so you do not need to use this crate directly. **Status:** Stable. [zp]: https://github.com/dbus2/zbus\#readme [`zbus`]: https://crates.io/crates/zbus zbus_macros-5.3.0/src/error.rs000064400000000000000000000205761046102023000144600ustar 00000000000000use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{spanned::Spanned, Data, DeriveInput, Error, Fields, Ident, Variant}; use zvariant_utils::def_attrs; def_attrs! { crate zbus; pub StructAttributes("struct") { prefix str, impl_display bool }; pub VariantAttributes("enum variant") { name str, error none }; } use crate::utils::*; pub fn expand_derive(input: DeriveInput) -> Result { let StructAttributes { prefix, impl_display, } = StructAttributes::parse(&input.attrs)?; let prefix = prefix.unwrap_or_else(|| "org.freedesktop.DBus".to_string()); let generate_display = impl_display.unwrap_or(true); let (_vis, name, _generics, data) = match input.data { Data::Enum(data) => (input.vis, input.ident, input.generics, data), _ => return Err(Error::new(input.span(), "only enums supported")), }; let zbus = zbus_path(); let mut replies = quote! {}; let mut error_names = quote! {}; let mut error_descriptions = quote! {}; let mut error_converts = quote! {}; let mut zbus_error_variant = None; for variant in data.variants { let VariantAttributes { name, error } = VariantAttributes::parse(&variant.attrs)?; let ident = &variant.ident; let name = name.unwrap_or_else(|| ident.to_string()); let fqn = if !error { format!("{prefix}.{name}") } else { // The ZBus error variant will always be a hardcoded string. String::from("org.freedesktop.zbus.Error") }; let error_name = quote! { #zbus::names::ErrorName::from_static_str_unchecked(#fqn) }; let e = match variant.fields { Fields::Unit => quote! { Self::#ident => #error_name, }, Fields::Unnamed(_) => quote! { Self::#ident(..) => #error_name, }, Fields::Named(_) => quote! { Self::#ident { .. } => #error_name, }, }; error_names.extend(e); if error { if zbus_error_variant.is_some() { panic!("More than 1 `#[zbus(error)]` variant found"); } zbus_error_variant = Some(quote! { #ident }); } // 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 => None, }, Fields::Unnamed(_) => { if error { quote! { Self::#ident(#zbus::Error::MethodError(_, desc, _)) => desc.as_deref(), Self::#ident(_) => None, } } else { quote! { Self::#ident(desc, ..) => Some(&desc), } } } Fields::Named(n) => { let f = &n .named .first() .ok_or_else(|| Error::new(n.span(), "expected at least one field"))? .ident; quote! { Self::#ident { #f, } => Some(#f), } } }; error_descriptions.extend(e); // The conversion for #[zbus(error)] variant is handled separately/explicitly. if !error { // 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(::std::clone::Clone::clone(desc).unwrap_or_default()) }, }, Fields::Named(n) => { let f = &n .named .first() .ok_or_else(|| Error::new(n.span(), "expected at least one field"))? .ident; quote! { #fqn => { let desc = ::std::clone::Clone::clone(desc).unwrap_or_default(); Self::#ident { #f: desc } } } } }; error_converts.extend(e); } let r = gen_reply_for_variant(&variant, error)?; replies.extend(r); } let from_zbus_error_impl = zbus_error_variant .map(|ident| { quote! { impl ::std::convert::From<#zbus::Error> for #name { fn from(value: #zbus::Error) -> #name { if let #zbus::Error::MethodError(name, desc, _) = &value { match name.as_str() { #error_converts _ => Self::#ident(value), } } else { Self::#ident(value) } } } } }) .unwrap_or_default(); let display_impl = if generate_display { quote! { impl ::std::fmt::Display for #name { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { let name = #zbus::DBusError::name(self); let description = #zbus::DBusError::description(self).unwrap_or("no description"); ::std::write!(f, "{}: {}", name, description) } } } } else { quote! {} }; Ok(quote! { impl #zbus::DBusError for #name { fn name(&self) -> #zbus::names::ErrorName { match self { #error_names } } fn description(&self) -> Option<&str> { match self { #error_descriptions } } fn create_reply(&self, call: &#zbus::message::Header) -> #zbus::Result<#zbus::message::Message> { let name = self.name(); match self { #replies } } } #display_impl impl ::std::error::Error for #name {} #from_zbus_error_impl }) } fn gen_reply_for_variant( variant: &Variant, zbus_error_variant: bool, ) -> Result { let zbus = zbus_path(); let ident = &variant.ident; match &variant.fields { Fields::Unit => Ok(quote! { Self::#ident => #zbus::message::Message::error(call, name)?.build(&()), }), Fields::Unnamed(f) => { // Name the unnamed fields as the number of the field with an 'f' in front. let in_fields = (0..f.unnamed.len()) .map(|n| Ident::new(&format!("f{n}"), ident.span()).to_token_stream()) .collect::>(); let out_fields = if zbus_error_variant { let error_field = in_fields.first().ok_or_else(|| { Error::new( ident.span(), "expected at least one field for #[zbus(error)] variant", ) })?; vec![quote! { match #error_field { #zbus::Error::MethodError(name, desc, _) => { ::std::clone::Clone::clone(desc) } _ => None, } .unwrap_or_else(|| ::std::string::ToString::to_string(#error_field)) }] } else { // FIXME: Workaround for https://github.com/rust-lang/rust-clippy/issues/10577 #[allow(clippy::redundant_clone)] in_fields.clone() }; Ok(quote! { Self::#ident(#(#in_fields),*) => #zbus::message::Message::error(call, name)?.build(&(#(#out_fields),*)), }) } Fields::Named(f) => { let fields = f.named.iter().map(|v| v.ident.as_ref()).collect::>(); Ok(quote! { Self::#ident { #(#fields),* } => #zbus::message::Message::error(call, name)?.build(&(#(#fields),*)), }) } } } zbus_macros-5.3.0/src/iface.rs000064400000000000000000001610751046102023000143760ustar 00000000000000use proc_macro2::TokenStream; use quote::{format_ident, quote}; use std::collections::BTreeMap; use syn::{ parse::{Parse, ParseStream}, parse_quote, parse_str, punctuated::Punctuated, spanned::Spanned, token::{Async, Comma}, AngleBracketedGenericArguments, Attribute, Error, Expr, ExprLit, FnArg, GenericArgument, Ident, ImplItem, ImplItemFn, ItemImpl, Lit::Str, Meta, MetaNameValue, PatType, PathArguments, ReturnType, Signature, Token, Type, TypePath, Visibility, }; use zvariant_utils::{case, def_attrs}; use crate::utils::*; def_attrs! { crate zbus; pub ImplAttributes("impl block") { interface str, name str, spawn bool, introspection_docs bool, proxy { // Keep this in sync with proxy's method attributes. // TODO: Find a way to share code with proxy module. pub ProxyAttributes("proxy") { assume_defaults bool, default_path str, default_service str, async_name str, blocking_name str, gen_async bool, gen_blocking bool, visibility str } } }; pub MethodAttributes("method") { name str, signal none, property { pub PropertyAttributes("property") { emits_changed_signal str } }, out_args [str], proxy { // Keep this in sync with proxy's method attributes. // TODO: Find a way to share code with proxy module. pub ProxyMethodAttributes("proxy") { object str, async_object str, blocking_object str, no_reply none, no_autostart none, allow_interactive_auth none } } }; pub ArgAttributes("argument") { object_server none, connection none, header none, signal_context none, signal_emitter none }; } #[derive(Debug)] struct Property<'a> { read: bool, write: bool, emits_changed_signal: PropertyEmitsChangedSignal, ty: Option<&'a Type>, doc_comments: TokenStream, } impl Property<'_> { fn new() -> Self { Self { read: false, write: false, emits_changed_signal: PropertyEmitsChangedSignal::True, ty: None, doc_comments: quote!(), } } } #[derive(Debug, PartialEq, Copy, Clone)] enum MethodType { Signal, Property(PropertyType), Other, } #[derive(Debug, PartialEq, Copy, Clone)] enum PropertyType { Setter, Getter, } #[derive(Debug, Clone)] struct MethodInfo { /// The method identifier ident: Ident, /// The type of method being parsed method_type: MethodType, /// Whether the method has inputs has_inputs: bool, /// Whether the method is async is_async: bool, /// Doc comments on the methods doc_comments: TokenStream, /// Whether self is passed as mutable to the method is_mut: bool, /// The await to append to method calls method_await: TokenStream, /// The typed inputs passed to the method typed_inputs: Vec, /// The method arguments' introspection intro_args: TokenStream, /// Whether the output type is a Result is_result_output: bool, /// Code block to deserialize arguments from zbus message args_from_msg: TokenStream, /// Names of all arguments to the method args_names: TokenStream, /// Code stream to match on the reply of the method call reply: TokenStream, /// The signal context object argument signal_emitter_arg: Option, /// The name of the method (setters are stripped of set_ prefix) member_name: String, /// The proxy method attributes, if any. proxy_attrs: Option, /// The method output type. output: ReturnType, /// The cfg attributes of the method. cfg_attrs: Vec, /// The doc attributes of the method. doc_attrs: Vec, } impl MethodInfo { fn new( zbus: &TokenStream, method: &ImplItemFn, attrs: &MethodAttributes, cfg_attrs: &[&Attribute], doc_attrs: &[&Attribute], introspect_docs: bool, ) -> syn::Result { let is_async = method.sig.asyncness.is_some(); let Signature { ident, inputs, output, .. } = &method.sig; let doc_comments = if introspect_docs { let docs = get_doc_attrs(&method.attrs) .iter() .filter_map(|attr| { if let Ok(MetaNameValue { value: Expr::Lit(ExprLit { lit: Str(s), .. }), .. }) = &attr.meta.require_name_value() { Some(s.value()) } else { // non #[doc = "..."] attributes are not our concern // we leave them for rustc to handle None } }) .collect(); to_xml_docs(docs) } else { quote!() }; let is_property = attrs.property.is_some(); let is_signal = attrs.signal; assert!(!is_property || !is_signal); let mut typed_inputs = inputs .iter() .filter_map(typed_arg) .cloned() .collect::>(); let has_inputs = count_regular_args(&typed_inputs) > 0; let method_type = if is_signal { MethodType::Signal } else if is_property { if has_inputs { MethodType::Property(PropertyType::Setter) } else { MethodType::Property(PropertyType::Getter) } } else { MethodType::Other }; let is_mut = if let FnArg::Receiver(r) = inputs .first() .ok_or_else(|| Error::new_spanned(ident, "not &self method"))? { r.mutability.is_some() } else if is_signal { false } else { return Err(Error::new_spanned(method, "missing receiver")); }; if is_signal && !is_async { return Err(Error::new_spanned(method, "signals must be async")); } let method_await = if is_async { quote! { .await } } else { quote! {} }; let signal_emitter_arg: Option = if is_signal { if typed_inputs.is_empty() { return Err(Error::new_spanned( inputs, "Expected a `&zbus::object_server::SignalEmitter<'_> argument", )); } Some(typed_inputs.remove(0)) } else { None }; let mut intro_args = quote!(); intro_args.extend(introspect_input_args(&typed_inputs, is_signal, cfg_attrs)); let is_result_output = introspect_add_output_args( &mut intro_args, output, attrs.out_args.as_deref(), cfg_attrs, )?; let (args_from_msg, args_names) = get_args_from_inputs(&typed_inputs, method_type, zbus)?; let reply = if is_result_output { let ret = quote!(r); quote!(match reply { ::std::result::Result::Ok(r) => connection.reply(&hdr, &#ret).await, ::std::result::Result::Err(e) => connection.reply_dbus_error(&hdr, e).await, }) } else { quote!(connection.reply(&hdr, &reply).await) }; let member_name = attrs.name.clone().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) }); Ok(MethodInfo { ident: ident.clone(), method_type, has_inputs, is_async, doc_comments, is_mut, method_await, typed_inputs, signal_emitter_arg, intro_args, is_result_output, args_from_msg, args_names, reply, member_name, proxy_attrs: attrs.proxy.clone(), output: output.clone(), cfg_attrs: cfg_attrs.iter().cloned().cloned().collect(), doc_attrs: doc_attrs.iter().cloned().cloned().collect(), }) } } pub fn expand(args: Punctuated, mut input: ItemImpl) -> syn::Result { let zbus = zbus_path(); let self_ty = &input.self_ty; let mut properties = BTreeMap::new(); let mut set_dispatch = quote!(); let mut set_mut_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!(); let mut generated_signals = quote!(); let mut signals_trait_methods = quote!(); let mut signals_emitter_impl_methods = quote!(); let mut signals_interface_ref_impl_methods = quote!(); // the impl Type let ty = match input.self_ty.as_ref() { Type::Path(p) => { &p.path .segments .last() .ok_or_else(|| Error::new_spanned(p, "Unsupported 'impl' type"))? .ident } _ => return Err(Error::new_spanned(&input.self_ty, "Invalid type")), }; let impl_attrs = ImplAttributes::parse_nested_metas(args)?; let iface_name = { match (impl_attrs.name, impl_attrs.interface) { // Ensure the interface name is valid. (Some(name), None) | (None, Some(name)) => zbus_names::InterfaceName::try_from(name) .map_err(|e| Error::new(input.span(), format!("{e}"))) .map(|i| i.to_string())?, (None, None) => format!("org.freedesktop.{ty}"), (Some(_), Some(_)) => { return Err(syn::Error::new( input.span(), "`name` and `interface` attributes should not be specified at the same time", )) } } }; let with_spawn = impl_attrs.spawn.unwrap_or(true); let mut proxy = impl_attrs .proxy .map(|p| Proxy::new(ty, &iface_name, p, &zbus)); let introspect_docs = impl_attrs.introspection_docs.unwrap_or(true); // Store parsed information about each method let mut methods = vec![]; for item in &mut input.items { let (method, is_signal) = match item { ImplItem::Fn(m) => (m, false), // Since signals do not have a function body, they don't parse as ImplItemFn… ImplItem::Verbatim(tokens) => { // … thus parse them ourselves and construct an ImplItemFn from that let decl = syn::parse2::(tokens.clone())?; let ImplItemSignal { attrs, vis, sig } = decl; *item = ImplItem::Fn(ImplItemFn { attrs, vis, defaultness: None, sig, // This empty block will be replaced below. block: parse_quote!({}), }); match item { ImplItem::Fn(m) => (m, true), _ => unreachable!(), } } _ => continue, }; let method_attrs = MethodAttributes::parse(&method.attrs)?; method.attrs.retain(|attr| !attr.path().is_ident("zbus")); if is_signal && !method_attrs.signal { return Err(syn::Error::new_spanned( item, "methods that are not signals must have a body", )); } let cfg_attrs: Vec<_> = method .attrs .iter() .filter(|a| a.path().is_ident("cfg")) .collect(); let doc_attrs: Vec<_> = method .attrs .iter() .filter(|a| a.path().is_ident("doc")) .collect(); let method_info = MethodInfo::new( &zbus, method, &method_attrs, &cfg_attrs, &doc_attrs, introspect_docs, )?; let attr_property = method_attrs.property; if let Some(prop_attrs) = &attr_property { if method_info.method_type == MethodType::Property(PropertyType::Getter) { let emits_changed_signal = if let Some(s) = &prop_attrs.emits_changed_signal { PropertyEmitsChangedSignal::parse(s, method.span())? } else { PropertyEmitsChangedSignal::True }; let mut property = Property::new(); property.emits_changed_signal = emits_changed_signal; properties.insert(method_info.member_name.to_string(), property); } else if prop_attrs.emits_changed_signal.is_some() { return Err(syn::Error::new( method.span(), "`emits_changed_signal` cannot be specified on setters", )); } }; methods.push((method, method_info)); } for (method, method_info) in methods { let cfg_attrs: Vec<_> = method .attrs .iter() .filter(|a| a.path().is_ident("cfg")) .collect(); let info = method_info.clone(); let MethodInfo { method_type, has_inputs, is_async, doc_comments, is_mut, method_await, typed_inputs, signal_emitter_arg, intro_args, is_result_output, args_from_msg, args_names, reply, member_name, .. } = method_info; let mut method_clone = method.clone(); let Signature { ident, inputs, output, .. } = &mut method.sig; clear_input_arg_attrs(inputs); match method_type { MethodType::Signal => { introspect.extend(doc_comments); introspect.extend(introspect_signal(&member_name, &intro_args)); let signal_emitter = signal_emitter_arg.unwrap().pat; method.block = parse_quote!({ #signal_emitter.emit( <#self_ty as #zbus::object_server::Interface>::name(), #member_name, &(#args_names), ) .await }); method_clone.sig.asyncness = Some(Async(method_clone.span())); *method_clone.sig.inputs.first_mut().unwrap() = parse_quote!(&self); method_clone.vis = Visibility::Inherited; let sig = &method_clone.sig; signals_trait_methods.extend(quote! { #sig; }); method_clone.block = parse_quote!({ self.emit( #iface_name, #member_name, &(#args_names), ) .await }); signals_emitter_impl_methods.extend(quote! { #method_clone }); method_clone.block = parse_quote!({ <#zbus::object_server::InterfaceRef<#self_ty>>::signal_emitter(self) .emit( #iface_name, #member_name, &(#args_names), ) .await }); signals_interface_ref_impl_methods.extend(quote! { #method_clone }); } MethodType::Property(_) => { let p = properties.get_mut(&member_name).ok_or(Error::new_spanned( &member_name, "Write-only properties aren't supported yet", ))?; let sk_member_name = case::snake_or_kebab_case(&member_name, true); let prop_changed_method_name = format_ident!("{sk_member_name}_changed"); let prop_invalidate_method_name = format_ident!("{sk_member_name}_invalidate"); p.doc_comments.extend(doc_comments); if has_inputs { p.write = true; let set_call = if is_result_output { quote!(self.#ident(#args_names)#method_await) } else if is_async { quote!( #zbus::export::futures_util::future::FutureExt::map( self.#ident(#args_names), ::std::result::Result::Ok, ) .await ) } else { quote!( ::std::result::Result::Ok(self.#ident(#args_names)) ) }; // * For reference arg, we convert from `&Value` (so `TryFrom<&Value<'_>>` is // required). // // * For argument type with lifetimes, we convert from `Value` (so // `TryFrom>` is required). // // * For all other arg types, we convert the passed value to `OwnedValue` first // and then pass it as `Value` (so `TryFrom` is required). let value_to_owned = quote! { match ::zbus::zvariant::Value::try_to_owned(value) { ::std::result::Result::Ok(val) => ::zbus::zvariant::Value::from(val), ::std::result::Result::Err(e) => { return ::std::result::Result::Err( ::std::convert::Into::into(#zbus::Error::Variant(::std::convert::Into::into(e))) ); } } }; let value_param = typed_inputs.iter().find(|input| { let a = ArgAttributes::parse(&input.attrs).unwrap(); !a.object_server && !a.connection && !a.header && !a.signal_context && !a.signal_emitter }); let value_arg = match &*value_param .ok_or_else(|| Error::new_spanned(inputs, "Expected a value argument"))? .ty { Type::Reference(_) => quote!(value), Type::Path(path) => path .path .segments .first() .map(|segment| match &segment.arguments { PathArguments::AngleBracketed(angled) => angled .args .first() .filter(|arg| matches!(arg, GenericArgument::Lifetime(_))) .map(|_| quote!(match ::zbus::zvariant::Value::try_clone(value) { ::std::result::Result::Ok(val) => val, ::std::result::Result::Err(e) => { return ::std::result::Result::Err( ::std::convert::Into::into(#zbus::Error::Variant(::std::convert::Into::into(e))) ); } })) .unwrap_or_else(|| value_to_owned.clone()), _ => value_to_owned.clone(), }) .unwrap_or_else(|| value_to_owned.clone()), _ => value_to_owned, }; let value_param_name = &value_param.unwrap().pat; let prop_changed_method = match p.emits_changed_signal { PropertyEmitsChangedSignal::True => { quote!({ self .#prop_changed_method_name(&signal_emitter) .await .map(|_| set_result) .map_err(Into::into) }) } PropertyEmitsChangedSignal::Invalidates => { quote!({ self .#prop_invalidate_method_name(&signal_emitter) .await .map(|_| set_result) .map_err(Into::into) }) } PropertyEmitsChangedSignal::False | PropertyEmitsChangedSignal::Const => { quote!({ Ok(()) }) } }; let do_set = quote!({ #args_from_msg let value = #value_arg; match ::std::convert::TryInto::try_into(value) { ::std::result::Result::Ok(val) => { let #value_param_name = val; match #set_call { ::std::result::Result::Ok(set_result) => #prop_changed_method e => e, } } ::std::result::Result::Err(e) => { ::std::result::Result::Err( ::std::convert::Into::into(#zbus::Error::Variant(::std::convert::Into::into(e))), ) } } }); if is_mut { let q = quote!( #(#cfg_attrs)* #member_name => { ::std::option::Option::Some((move || async move { #do_set }) ().await) } ); set_mut_dispatch.extend(q); let q = quote!( #(#cfg_attrs)* #member_name => #zbus::object_server::DispatchResult::RequiresMut, ); set_dispatch.extend(q); } else { let q = quote!( #(#cfg_attrs)* #member_name => { #zbus::object_server::DispatchResult::Async(::std::boxed::Box::pin(async move { #do_set })) } ); set_dispatch.extend(q); } } else { let is_fallible_property = is_result_output; p.ty = Some(get_return_type(output)?); p.read = true; let value_convert = quote!( <#zbus::zvariant::OwnedValue as ::std::convert::TryFrom<_>>::try_from( <#zbus::zvariant::Value as ::std::convert::From<_>>::from( value, ), ) .map_err(|e| #zbus::fdo::Error::Failed(e.to_string())) ); let inner = if is_fallible_property { quote!(self.#ident(#args_names) #method_await .and_then(|value| #value_convert)) } else { quote!({ let value = self.#ident(#args_names)#method_await; #value_convert }) }; let q = quote!( #(#cfg_attrs)* #member_name => { #args_from_msg ::std::option::Option::Some(#inner) }, ); get_dispatch.extend(q); let q = if is_fallible_property { quote!( #args_from_msg if let Ok(prop) = self.#ident(#args_names)#method_await { props.insert( ::std::string::ToString::to_string(#member_name), <#zbus::zvariant::OwnedValue as ::std::convert::TryFrom<_>>::try_from( <#zbus::zvariant::Value as ::std::convert::From<_>>::from( prop, ), ) .map_err(|e| #zbus::fdo::Error::Failed(e.to_string()))?, ); }) } else { quote!( #args_from_msg props.insert( ::std::string::ToString::to_string(#member_name), <#zbus::zvariant::OwnedValue as ::std::convert::TryFrom<_>>::try_from( <#zbus::zvariant::Value as ::std::convert::From<_>>::from( self.#ident(#args_names)#method_await, ), ) .map_err(|e| #zbus::fdo::Error::Failed(e.to_string()))?, );) }; get_all.extend(q); let prop_value_handled = if is_fallible_property { quote!(self.#ident(#args_names)#method_await?) } else { quote!(self.#ident(#args_names)#method_await) }; if p.emits_changed_signal == PropertyEmitsChangedSignal::True { let prop_changed_method = quote!( pub async fn #prop_changed_method_name( &self, signal_emitter: &#zbus::object_server::SignalEmitter<'_>, ) -> #zbus::Result<()> { let header = ::std::option::Option::None::<&#zbus::message::Header<'_>>; let connection = signal_emitter.connection(); let object_server = connection.object_server(); #args_from_msg let mut changed = ::std::collections::HashMap::new(); let value = <#zbus::zvariant::Value as ::std::convert::From<_>>::from(#prop_value_handled); changed.insert(#member_name, value); #zbus::fdo::Properties::properties_changed( signal_emitter, #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name), changed, ::std::borrow::Cow::Borrowed(&[]), ).await } ); generated_signals.extend(prop_changed_method); } if p.emits_changed_signal == PropertyEmitsChangedSignal::Invalidates { let prop_invalidate_method = quote!( pub async fn #prop_invalidate_method_name( &self, signal_emitter: &#zbus::object_server::SignalEmitter<'_>, ) -> #zbus::Result<()> { #zbus::fdo::Properties::properties_changed( signal_emitter, #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name), ::std::collections::HashMap::new(), ::std::borrow::Cow::Borrowed(&[#member_name]), ).await } ); generated_signals.extend(prop_invalidate_method); } } } MethodType::Other => { introspect.extend(doc_comments); introspect.extend(introspect_method(&member_name, &intro_args)); let m = quote! { #(#cfg_attrs)* #member_name => { let future = async move { #args_from_msg let reply = self.#ident(#args_names)#method_await; let hdr = message.header(); #reply }; #zbus::object_server::DispatchResult::Async(::std::boxed::Box::pin(async move { future.await })) }, }; if is_mut { call_dispatch.extend(quote! { #(#cfg_attrs)* #member_name => #zbus::object_server::DispatchResult::RequiresMut, }); call_mut_dispatch.extend(m); } else { call_dispatch.extend(m); } } } if let Some(proxy) = &mut proxy { proxy.add_method(info, &properties)?; } } introspect_properties(&mut introspect, properties)?; let generics = &input.generics; let where_clause = &generics.where_clause; let generated_signals_impl = if generated_signals.is_empty() { quote!() } else { quote! { impl #generics #self_ty #where_clause { #generated_signals } } }; let signals_trait_and_impl = if signals_trait_methods.is_empty() { quote!() } else { let signals_trait_name = format_ident!("{}Signals", ty); let signals_trait_doc = format!("Trait providing all signal emission methods for `{ty}`."); quote! { #[doc = #signals_trait_doc] #[#zbus::export::async_trait::async_trait] pub trait #signals_trait_name { #signals_trait_methods } #[#zbus::export::async_trait::async_trait] impl #signals_trait_name for #zbus::object_server::SignalEmitter<'_> { #signals_emitter_impl_methods } #[#zbus::export::async_trait::async_trait] impl #generics #signals_trait_name for #zbus::object_server::InterfaceRef<#self_ty> #where_clause { #signals_interface_ref_impl_methods } } }; let proxy = proxy.map(|proxy| proxy.gen()).transpose()?; let introspect_format_str = format!("{}", "{:indent$}"); Ok(quote! { #input #generated_signals_impl #signals_trait_and_impl #[#zbus::export::async_trait::async_trait] impl #generics #zbus::object_server::Interface for #self_ty #where_clause { fn name() -> #zbus::names::InterfaceName<'static> { #zbus::names::InterfaceName::from_static_str_unchecked(#iface_name) } fn spawn_tasks_for_methods(&self) -> bool { #with_spawn } async fn get( &self, property_name: &str, object_server: &#zbus::ObjectServer, connection: &#zbus::Connection, header: Option<&#zbus::message::Header<'_>>, signal_emitter: &#zbus::object_server::SignalEmitter<'_>, ) -> ::std::option::Option<#zbus::fdo::Result<#zbus::zvariant::OwnedValue>> { match property_name { #get_dispatch _ => ::std::option::Option::None, } } async fn get_all( &self, object_server: &#zbus::ObjectServer, connection: &#zbus::Connection, header: Option<&#zbus::message::Header<'_>>, signal_emitter: &#zbus::object_server::SignalEmitter<'_>, ) -> #zbus::fdo::Result<::std::collections::HashMap< ::std::string::String, #zbus::zvariant::OwnedValue, >> { let mut props: ::std::collections::HashMap< ::std::string::String, #zbus::zvariant::OwnedValue, > = ::std::collections::HashMap::new(); #get_all Ok(props) } fn set<'call>( &'call self, property_name: &'call str, value: &'call #zbus::zvariant::Value<'_>, object_server: &'call #zbus::ObjectServer, connection: &'call #zbus::Connection, header: Option<&'call #zbus::message::Header<'_>>, signal_emitter: &'call #zbus::object_server::SignalEmitter<'_>, ) -> #zbus::object_server::DispatchResult<'call> { match property_name { #set_dispatch _ => #zbus::object_server::DispatchResult::NotFound, } } async fn set_mut( &mut self, property_name: &str, value: &#zbus::zvariant::Value<'_>, object_server: &#zbus::ObjectServer, connection: &#zbus::Connection, header: Option<&#zbus::message::Header<'_>>, signal_emitter: &#zbus::object_server::SignalEmitter<'_>, ) -> ::std::option::Option<#zbus::fdo::Result<()>> { match property_name { #set_mut_dispatch _ => ::std::option::Option::None, } } fn call<'call>( &'call self, object_server: &'call #zbus::ObjectServer, connection: &'call #zbus::Connection, message: &'call #zbus::message::Message, name: #zbus::names::MemberName<'call>, ) -> #zbus::object_server::DispatchResult<'call> { match name.as_str() { #call_dispatch _ => #zbus::object_server::DispatchResult::NotFound, } } fn call_mut<'call>( &'call mut self, object_server: &'call #zbus::ObjectServer, connection: &'call #zbus::Connection, message: &'call #zbus::message::Message, name: #zbus::names::MemberName<'call>, ) -> #zbus::object_server::DispatchResult<'call> { match name.as_str() { #call_mut_dispatch _ => #zbus::object_server::DispatchResult::NotFound, } } fn introspect_to_writer(&self, writer: &mut dyn ::std::fmt::Write, level: usize) { ::std::writeln!( writer, #introspect_format_str, "", indent = level ).unwrap(); { use #zbus::zvariant::Type; let level = level + 2; #introspect } ::std::writeln!(writer, r#"{:indent$}"#, "", indent = level).unwrap(); } } #proxy }) } fn get_args_from_inputs( inputs: &[PatType], method_type: MethodType, zbus: &TokenStream, ) -> syn::Result<(TokenStream, TokenStream)> { if inputs.is_empty() { Ok((quote!(), quote!())) } else { let mut server_arg_decl = None; let mut conn_arg_decl = None; let mut header_arg_decl = None; let mut signal_emitter_arg_decl = None; let mut args_names = Vec::new(); let mut tys = Vec::new(); for input in inputs { let ArgAttributes { object_server, connection, header, signal_emitter, signal_context, } = ArgAttributes::parse(&input.attrs)?; if object_server { if server_arg_decl.is_some() { return Err(Error::new_spanned( input, "There can only be one object_server argument", )); } let server_arg = &input.pat; server_arg_decl = Some(quote! { let #server_arg = &object_server; }); } else if connection { if conn_arg_decl.is_some() { return Err(Error::new_spanned( input, "There can only be one connection argument", )); } let conn_arg = &input.pat; conn_arg_decl = Some(quote! { let #conn_arg = &connection; }); } else if header { if header_arg_decl.is_some() { return Err(Error::new_spanned( input, "There can only be one header argument", )); } let header_arg = &input.pat; header_arg_decl = match method_type { MethodType::Property(_) => Some(quote! { let #header_arg = ::std::option::Option::<&#zbus::message::Header<'_>>::cloned(header); }), _ => Some(quote! { let #header_arg = message.header(); }), }; } else if signal_context || signal_emitter { if signal_emitter_arg_decl.is_some() { return Err(Error::new_spanned( input, "There can only be one `signal_emitter` or `signal_context` argument", )); } let signal_context_arg = &input.pat; signal_emitter_arg_decl = match method_type { MethodType::Property(_) => Some( quote! { let #signal_context_arg = ::std::clone::Clone::clone(signal_emitter); }, ), _ => Some(quote! { let #signal_context_arg = match hdr.path() { ::std::option::Option::Some(p) => { #zbus::object_server::SignalEmitter::new(connection, p).expect("Infallible conversion failed") } ::std::option::Option::None => { let err = #zbus::fdo::Error::UnknownObject("Path Required".into()); return connection.reply_dbus_error(&hdr, err).await; } }; }), }; } else { args_names.push(pat_ident(input).unwrap()); tys.push(&input.ty); } } let (hdr_init, msg_init, args_decl) = match method_type { MethodType::Property(PropertyType::Getter) => (quote! {}, quote! {}, quote! {}), MethodType::Property(PropertyType::Setter) => ( quote! { let hdr = header.as_ref().unwrap(); }, quote! {}, quote! {}, ), _ => ( quote! { let hdr = message.header(); }, quote! { let msg_body = message.body(); }, quote! { let (#(#args_names),*): (#(#tys),*) = match msg_body.deserialize() { ::std::result::Result::Ok(r) => r, ::std::result::Result::Err(e) => { let err = <#zbus::fdo::Error as ::std::convert::From<_>>::from(e); return connection.reply_dbus_error(&hdr, err).await; } }; }, ), }; let args_from_msg = quote! { #hdr_init #msg_init #server_arg_decl #conn_arg_decl #header_arg_decl #signal_emitter_arg_decl #args_decl }; let all_args_names = inputs.iter().filter_map(pat_ident); let all_args_names = quote! { #(#all_args_names,)* }; Ok((args_from_msg, all_args_names)) } } // Removes all `zbus` attributes from the given inputs. fn clear_input_arg_attrs(inputs: &mut Punctuated) { for input in inputs { if let FnArg::Typed(t) = input { t.attrs.retain(|attr| !attr.path().is_ident("zbus")); } } } fn introspect_signal(name: &str, args: &TokenStream) -> TokenStream { let format_str = format!("{}", "{:indent$}"); quote!( ::std::writeln!(writer, #format_str, "", indent = level).unwrap(); { let level = level + 2; #args } ::std::writeln!(writer, "{:indent$}", "", indent = level).unwrap(); ) } fn introspect_method(name: &str, args: &TokenStream) -> TokenStream { let format_str = format!("{}", "{:indent$}"); quote!( ::std::writeln!(writer, #format_str, "", indent = level).unwrap(); { let level = level + 2; #args } ::std::writeln!(writer, "{:indent$}", "", indent = level).unwrap(); ) } fn introspect_input_args<'i>( inputs: &'i [PatType], is_signal: bool, cfg_attrs: &'i [&'i syn::Attribute], ) -> impl Iterator + 'i { inputs .iter() .filter_map(move |pat_type @ PatType { ty, attrs, .. }| { if is_special_arg(attrs) { return None; } let ident = pat_ident(pat_type).unwrap(); let arg_name = quote!(#ident).to_string(); let dir = if is_signal { "" } else { " direction=\"in\"" }; let format_str = format!( "{}", "{:indent$}", "{}", ); Some(quote!( #(#cfg_attrs)* ::std::writeln!(writer, #format_str, "", <#ty>::SIGNATURE, indent = level).unwrap(); )) }) } fn count_regular_args(inputs: &[PatType]) -> usize { inputs .iter() .filter(|PatType { attrs, .. }| !is_special_arg(attrs)) .count() } fn is_special_arg(attrs: &[Attribute]) -> bool { attrs.iter().any(|attr| { if !attr.path().is_ident("zbus") { return false; } let Ok(list) = &attr.meta.require_list() else { return false; }; let Ok(nested) = list.parse_args_with(Punctuated::::parse_terminated) else { return false; }; let res = nested.iter().any(|nested_meta| { matches!( nested_meta, Meta::Path(path) if path.is_ident("object_server") || path.is_ident("connection") || path.is_ident("header") || path.is_ident("signal_context") || path.is_ident("signal_emitter") ) }); res }) } fn introspect_output_arg( ty: &Type, arg_name: Option<&String>, cfg_attrs: &[&syn::Attribute], ) -> TokenStream { let arg_name_attr = match arg_name { Some(name) => format!("name=\"{name}\" "), None => String::from(""), }; let format_str = format!( "{}", "{:indent$}", "{}", ); quote!( #(#cfg_attrs)* ::std::writeln!(writer, #format_str, "", <#ty>::SIGNATURE, indent = level).unwrap(); ) } fn get_result_inner_type(p: &TypePath) -> syn::Result<&Type> { if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = &p .path .segments .last() .ok_or_else(|| Error::new_spanned(p, "unsupported result type"))? .arguments { if let Some(syn::GenericArgument::Type(ty)) = args.first() { return Ok(ty); } } Err(Error::new_spanned(p, "unhandled Result return")) } fn introspect_add_output_args( args: &mut TokenStream, output: &ReturnType, arg_names: Option<&[String]>, cfg_attrs: &[&syn::Attribute], ) -> 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() .ok_or_else(|| Error::new_spanned(ty, "unsupported output type"))? .ident == "Result"; if is_result_output { ty = get_result_inner_type(p)?; } } if let Type::Tuple(t) = ty { if let Some(arg_names) = arg_names { if t.elems.len() != arg_names.len() { // Turn into error panic!("Number of out arg names different from out args specified") } } for i in 0..t.elems.len() { let name = arg_names.map(|names| &names[i]); args.extend(introspect_output_arg(&t.elems[i], name, cfg_attrs)); } } else { args.extend(introspect_output_arg(ty, None, cfg_attrs)); } } Ok(is_result_output) } fn get_return_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() .ok_or_else(|| Error::new_spanned(ty, "unsupported property type"))? .ident == "Result"; if is_result_output { return get_result_inner_type(p); } } Ok(ty) } else { Err(Error::new_spanned(output, "Invalid return type")) } } fn introspect_properties( introspection: &mut TokenStream, properties: BTreeMap>, ) -> syn::Result<()> { for (name, prop) in properties { let access = if prop.read && prop.write { "readwrite" } else if prop.read { "read" } else if prop.write { "write" } else { return Err(Error::new_spanned( name, "property is neither readable nor writable", )); }; let ty = prop.ty.ok_or_else(|| { Error::new_spanned(&name, "Write-only properties aren't supported yet") })?; let doc_comments = prop.doc_comments; if prop.emits_changed_signal == PropertyEmitsChangedSignal::True { let format_str = format!( "{}", "{:indent$}", "{}", ); introspection.extend(quote!( #doc_comments ::std::writeln!(writer, #format_str, "", <#ty>::SIGNATURE, indent = level).unwrap(); )); } else { let emits_changed_signal = prop.emits_changed_signal.to_string(); let annot_name = "org.freedesktop.DBus.Property.EmitsChangedSignal"; let format_str = format!( "{}\n\ {}\n\ {}", "{:indent$}", "{}", "{:annot_indent$}", "{:indent$}", ); introspection.extend(quote!( #doc_comments ::std::writeln!( writer, #format_str, "", <#ty>::SIGNATURE, "", "", indent = level, annot_indent = level + 2, ).unwrap(); )); } } Ok(()) } 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!(::std::writeln!(writer, "{:indent$}", "", indent = level).unwrap();)); docs } // Like ImplItemFn, but with a semicolon at the end instead of a body block struct ImplItemSignal { attrs: Vec, vis: Visibility, sig: Signature, } impl Parse for ImplItemSignal { fn parse(input: ParseStream<'_>) -> syn::Result { let attrs = input.call(Attribute::parse_outer)?; let vis = input.parse()?; let sig = input.parse()?; let _: Token![;] = input.parse()?; Ok(ImplItemSignal { attrs, vis, sig }) } } #[derive(Debug)] struct Proxy { // The type name ty: Ident, // The interface name iface_name: String, // The zbus crate zbus: TokenStream, // Input attrs: ProxyAttributes, // Output methods: TokenStream, } impl Proxy { fn new(ty: &Ident, iface_name: &str, attrs: ProxyAttributes, zbus: &TokenStream) -> Self { Self { iface_name: iface_name.to_string(), ty: ty.clone(), zbus: zbus.clone(), attrs, methods: quote!(), } } fn add_method( &mut self, method_info: MethodInfo, properties: &BTreeMap>, ) -> syn::Result<()> { let inputs: Punctuated = method_info .typed_inputs .iter() .filter(|input| { let a = ArgAttributes::parse(&input.attrs).unwrap(); !a.object_server && !a.connection && !a.header && !a.signal_context && !a.signal_emitter }) .cloned() .collect(); let zbus = &self.zbus; let ret = match &method_info.output { ReturnType::Type(_, ty) => { let ty = ty.as_ref(); if let Type::Path(p) = ty { let is_result_output = p .path .segments .last() .ok_or_else(|| Error::new_spanned(ty, "unsupported return type"))? .ident == "Result"; if is_result_output { let is_prop = matches!(method_info.method_type, MethodType::Property(_)); if is_prop { // Proxy methods always return `zbus::Result` let inner_ty = get_result_inner_type(p)?; quote! { #zbus::Result<#inner_ty> } } else { quote! { #ty } } } else { quote! { #zbus::Result<#ty> } } } else { quote! { #zbus::Result<#ty> } } } ReturnType::Default => quote! { #zbus::Result<()> }, }; let ident = &method_info.ident; let member_name = method_info.member_name; let mut proxy_method_attrs = quote! { name = #member_name, }; proxy_method_attrs.extend(match method_info.method_type { MethodType::Signal => quote!(signal), MethodType::Property(_) => { let emits_changed_signal = properties .get(&member_name) .unwrap() .emits_changed_signal .to_string(); let emits_changed_signal = quote! { emits_changed_signal = #emits_changed_signal }; quote! { property(#emits_changed_signal) } } MethodType::Other => quote!(), }); if let Some(attrs) = method_info.proxy_attrs { if let Some(object) = attrs.object { proxy_method_attrs.extend(quote! { object = #object, }); } if let Some(async_object) = attrs.async_object { proxy_method_attrs.extend(quote! { async_object = #async_object, }); } if let Some(blocking_object) = attrs.blocking_object { proxy_method_attrs.extend(quote! { blocking_object = #blocking_object, }); } if attrs.no_reply { proxy_method_attrs.extend(quote! { no_reply, }); } if attrs.no_autostart { proxy_method_attrs.extend(quote! { no_autostart, }); } if attrs.allow_interactive_auth { proxy_method_attrs.extend(quote! { allow_interactive_auth, }); } } let cfg_attrs = method_info.cfg_attrs; let doc_attrs = method_info.doc_attrs; self.methods.extend(quote! { #(#cfg_attrs)* #(#doc_attrs)* #[zbus(#proxy_method_attrs)] fn #ident(&self, #inputs) -> #ret; }); Ok(()) } fn gen(&self) -> syn::Result { let attrs = &self.attrs; let ( assume_defaults, default_path, default_service, async_name, blocking_name, gen_async, gen_blocking, ty, methods, ) = ( attrs .assume_defaults .map(|value| quote! { assume_defaults = #value, }), attrs .default_path .as_ref() .map(|value| quote! { default_path = #value, }), attrs .default_service .as_ref() .map(|value| quote! { default_service = #value, }), attrs .async_name .as_ref() .map(|value| quote! { async_name = #value, }), attrs .blocking_name .as_ref() .map(|value| quote! { blocking_name = #value, }), attrs.gen_async.map(|value| quote! { gen_async = #value, }), attrs .gen_blocking .map(|value| quote! { gen_blocking = #value, }), &self.ty, &self.methods, ); let iface_name = &self.iface_name; let vis = match &self.attrs.visibility { Some(s) => parse_str::(s)?, None => Visibility::Public(Token![pub](ty.span())), }; let zbus = &self.zbus; let proxy_doc = format!("Proxy for the `{iface_name}` interface."); Ok(quote! { #[doc = #proxy_doc] #[#zbus::proxy( name = #iface_name, #assume_defaults #default_path #default_service #async_name #blocking_name #gen_async #gen_blocking )] #vis trait #ty { #methods } }) } } zbus_macros-5.3.0/src/lib.rs000064400000000000000000000506321046102023000140710ustar 00000000000000#![deny(rust_2018_idioms)] #![doc( html_logo_url = "https://raw.githubusercontent.com/dbus2/zbus/9f7a90d2b594ddc48b7a5f39fda5e00cd56a7dfb/logo.png" )] #![doc = include_str!("../README.md")] #![doc(test(attr( warn(unused), deny(warnings), allow(dead_code), // W/o this, we seem to get some bogus warning about `extern crate zbus`. allow(unused_extern_crates), )))] use proc_macro::TokenStream; use syn::{ parse_macro_input, punctuated::Punctuated, DeriveInput, ItemImpl, ItemTrait, Meta, Token, }; mod error; mod iface; mod proxy; mod utils; /// Attribute macro for defining D-Bus proxies (using [`zbus::Proxy`] and /// [`zbus::blocking::Proxy`]). /// /// The macro must be applied on a `trait T`. Two matching `impl T` will provide an asynchronous /// Proxy implementation, named `TraitNameProxy` and a blocking one, named `TraitNameProxyBlocking`. /// The proxy instances can be created with the associated `new()` or `builder()` methods. The /// former doesn't take any argument and uses the default service name and path. The later allows /// you to specify non-default proxy arguments. /// /// The following attributes are supported: /// /// * `interface` - the name of the D-Bus interface this proxy is for. /// /// * `default_service` - the default service this proxy should connect to. /// /// * `default_path` - The default object path the method calls will be sent on and signals will be /// sent for by the target service. /// /// * `gen_async` - Whether or not to generate the asynchronous Proxy type. /// /// * `gen_blocking` - Whether or not to generate the blocking Proxy type. If the `blocking-api` /// cargo feature is disabled, this attribute is ignored and blocking Proxy type is not generated. /// /// * `async_name` - Specify the exact name of the asynchronous proxy type. /// /// * `blocking_name` - Specify the exact name of the blocking proxy type. /// /// * `assume_defaults` - whether to auto-generate values for `default_path` and `default_service` /// if none are specified (default: `false`). `proxy` generates a warning if neither this /// attribute nor one of the default values are specified. Please make sure to explicitly set /// either this attribute or the default values, according to your needs. /// /// Each trait method will be expanded to call to the associated D-Bus remote interface. /// /// Trait methods accept `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. Additional sub-attributes exists to /// control specific property behaviors: /// * `emits_changed_signal` - specifies how property changes are signaled. Valid values are those /// documented in [DBus specifications][dbus_emits_changed_signal]: /// * `"true"` - (default) change signal is always emitted with the value included. This uses /// the default caching behavior of the proxy, and generates a listener method for the change /// signal. /// * `"invalidates"` - change signal is emitted, but the value is not included in the signal. /// This has the same behavior as `"true"`. /// * `"const"` - property never changes, thus no signal is ever emitted for it. This uses the /// default caching behavior of the proxy, but does not generate a listener method for the /// change signal. /// * `"false"` - change signal is not (guaranteed to be) emitted if the property changes. This /// disables property value caching, and does not generate a listener method for the change /// signal. /// /// * `signal` - declare a signal just like a D-Bus method. Read the [Signals](#signals) section /// below for details. /// /// * `no_reply` - declare a method call that does not wait for a reply. /// /// * `no_autostart` - declare a method call that will not trigger the bus to automatically launch /// the destination service if it is not already running. /// /// * `allow_interactive_auth` - declare a method call that is allowed to trigger an interactive /// prompt for authorization or confirmation from the receiver. /// /// * `object` - methods that returns an [`ObjectPath`] can be annotated with the `object` attribute /// to specify the proxy object to be constructed from the returned [`ObjectPath`]. /// /// * `async_object` - if the assumptions made by `object` attribute about naming of the /// asynchronous proxy type, don't fit your bill, you can use this to specify its exact name. /// /// * `blocking_object` - if the assumptions made by `object` attribute about naming of the blocking /// proxy type, don't fit your bill, you can use this to specify its exact name. /// /// NB: Any doc comments provided shall be appended to the ones added by the macro. /// /// # Signals /// /// For each signal method declared, this macro will provide a method, named `receive_` /// to create a [`zbus::SignalStream`] ([`zbus::blocking::SignalIterator`] for the blocking proxy) /// wrapper, named `Stream` (`Iterator` for the blocking proxy) that yield /// a [`zbus::message::Message`] wrapper, named ``. This wrapper provides type safe /// access to the signal arguments. It also implements `Deref` to allow easy /// access to the underlying [`zbus::message::Message`]. /// /// # Example /// /// ```no_run /// # use std::error::Error; /// use zbus_macros::proxy; /// use zbus::{blocking::Connection, Result, fdo, zvariant::Value}; /// use futures_util::stream::StreamExt; /// use async_io::block_on; /// /// #[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; /// /// #[zbus(property)] /// fn a_property(&self) -> fdo::Result; /// /// #[zbus(property)] /// fn set_a_property(&self, a_property: &str) -> fdo::Result<()>; /// /// #[zbus(signal)] /// fn some_signal(&self, arg1: &str, arg2: u32) -> fdo::Result<()>; /// /// #[zbus(object = "SomeOtherIface", blocking_object = "SomeOtherInterfaceBlock")] /// // The method will return a `SomeOtherIfaceProxy` or `SomeOtherIfaceProxyBlock`, depending /// // on whether it is called on `SomeIfaceProxy` or `SomeIfaceProxyBlocking`, respectively. /// // /// // NB: We explicitly specified the exact name of the blocking proxy type. If we hadn't, /// // `SomeOtherIfaceProxyBlock` would have been assumed and expected. We could also specify /// // the specific name of the asynchronous proxy types, using the `async_object` attribute. /// fn some_method(&self, arg1: &str); /// } /// /// #[proxy( /// interface = "org.test.SomeOtherIface", /// default_service = "org.test.SomeOtherService", /// blocking_name = "SomeOtherInterfaceBlock", /// )] /// trait SomeOtherIface {} /// /// let connection = Connection::session()?; /// // Use `builder` to override the default arguments, `new` otherwise. /// let proxy = SomeIfaceProxyBlocking::builder(&connection) /// .destination("org.another.Service")? /// .cache_properties(zbus::proxy::CacheProperties::No) /// .build()?; /// let _ = proxy.do_this("foo", 32, &Value::new(true)); /// let _ = proxy.set_a_property("val"); /// /// let signal = proxy.receive_some_signal()?.next().unwrap(); /// let args = signal.args()?; /// println!("arg1: {}, arg2: {}", args.arg1(), args.arg2()); /// /// // Now the same again, but asynchronous. /// block_on(async move { /// let proxy = SomeIfaceProxy::builder(&connection.into()) /// .cache_properties(zbus::proxy::CacheProperties::No) /// .build() /// .await /// .unwrap(); /// let _ = proxy.do_this("foo", 32, &Value::new(true)).await; /// let _ = proxy.set_a_property("val").await; /// /// let signal = proxy.receive_some_signal().await?.next().await.unwrap(); /// let args = signal.args()?; /// println!("arg1: {}, arg2: {}", args.arg1(), args.arg2()); /// /// Ok::<(), zbus::Error>(()) /// })?; /// /// # 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 /// [`zbus::Proxy`]: https://docs.rs/zbus/latest/zbus/proxy/struct.Proxy.html /// [`zbus::message::Message`]: https://docs.rs/zbus/latest/zbus/message/struct.Message.html /// [`zbus::blocking::Proxy`]: https://docs.rs/zbus/latest/zbus/blocking/proxy/struct.Proxy.html /// [`zbus::SignalStream`]: https://docs.rs/zbus/latest/zbus/proxy/struct.SignalStream.html /// [`zbus::blocking::SignalIterator`]: https://docs.rs/zbus/latest/zbus/blocking/proxy/struct.SignalIterator.html /// [`ObjectPath`]: https://docs.rs/zvariant/latest/zvariant/struct.ObjectPath.html /// [dbus_emits_changed_signal]: https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format #[proc_macro_attribute] pub fn proxy(attr: TokenStream, item: TokenStream) -> TokenStream { let args = parse_macro_input!(attr with Punctuated::parse_terminated); let input = parse_macro_input!(item as ItemTrait); proxy::expand(args, input) .unwrap_or_else(|err| err.to_compile_error()) .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 trait accepts the `interface` attributes: /// /// * `name` - the D-Bus interface name /// /// * `spawn` - Controls the spawning of tasks for method calls. By default, `true`, allowing zbus /// to spawn a separate task for each method call. This default behavior can lead to methods being /// handled out of their received order, which might not always align with expected or desired /// behavior. /// /// - **When True (Default):** Suitable for interfaces where method calls are independent of each /// other or can be processed asynchronously without strict ordering. In scenarios where a /// client must wait for a reply before making further dependent calls, this default behavior is /// appropriate. /// /// - **When False:** Use this setting to ensure methods are handled in the order they are /// received, which is crucial for interfaces requiring sequential processing of method calls. /// However, care must be taken to avoid making D-Bus method calls from within your interface /// methods when this setting is false, as it may lead to deadlocks under certain conditions. /// /// * `proxy` - If specified, a proxy type will also be generated for the interface. This attribute /// supports all the [`macro@proxy`]-specific sub-attributes (e.g `gen_async`). The common /// sub-attributes (e.g `name`) are automatically forwarded to the [`macro@proxy`] macro. /// /// * `introspection_docs` - whether to include the documentation in the introspection data /// (Default: `true`). If your interface is well-known or well-documented, you may want to set /// this to `false` to reduce the the size of your binary and D-Bus traffic. /// /// The methods accepts the `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. If it may fail, a property method must /// return `zbus::fdo::Result`. An additional sub-attribute exists to control the emission of /// signals on changes to the property: /// * `emits_changed_signal` - specifies how property changes are signaled. Valid values are those /// documented in [DBus specifications][dbus_emits_changed_signal]: /// * `"true"` - (default) the change signal is always emitted when the property's setter is /// called. The value of the property is included in the signal. /// * `"invalidates"` - the change signal is emitted, but the value is not included in the /// signal. /// * `"const"` - the property never changes, thus no signal is ever emitted for it. /// * `"false"` - the change signal is not emitted if the property changes. /// /// * `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. Moreover, `interface` will also generate a trait named `Signals` that /// provides all the signal methods but without the `SignalEmitter` argument. The macro implements /// this trait for two types, `zbus::object_server::InterfaceRef` and /// `SignalEmitter<'_>`. The former is useful for emitting signals from outside the context of an /// interface method and the latter is useful for emitting signals from inside interface methods. /// /// You can call a signal method from a an interface method, or from an [`ObjectServer::with`] /// function. /// /// * `out_args` - When returning multiple values from a method, naming the out arguments become /// important. You can use `out_args` to specify their names. /// /// * `proxy` - Use this to specify the [`macro@proxy`]-specific method sub-attributes (e.g /// `object`). The common sub-attributes (e.g `name`) are automatically forworded to the /// [`macro@proxy`] macro. Moreover, you can use `visibility` sub-attribute to specify the /// visibility of the generated proxy type(s). /// /// In such case, your method must return a tuple containing /// your out arguments, in the same order as passed to `out_args`. /// /// The `struct_return` attribute (from zbus 1.x) is no longer supported. If you want to return a /// single structure from a method, declare it to return a tuple containing either a named structure /// or a nested tuple. /// /// Note: a `_changed` method is generated for each property: this /// method emits the "PropertiesChanged" signal for the associated property. The setter (if it /// exists) will automatically call this method. For instance, a property setter named `set_foo` /// will be called to set the property "Foo", and will emit the "PropertiesChanged" signal with the /// new value for "Foo". Other changes to the "Foo" property can be signaled manually with the /// generated `foo_changed` method. In addition, a `_invalidated` /// method is also generated that much like `_changed` method, emits a "PropertyChanged" signal /// but does not send over the new value of the property along with it. It is usually best to avoid /// using this since it will force all interested peers to fetch the new value and hence result in /// excess traffic on the bus. /// /// The method arguments support the following `zbus` attributes: /// /// * `object_server` - This marks the method argument to receive a reference to the /// [`ObjectServer`] this method was called by. /// * `connection` - This marks the method argument to receive a reference to the [`Connection`] on /// which the method call was received. /// * `header` - This marks the method argument to receive the message header associated with the /// D-Bus method call being handled. For property methods, this will be an `Option>`, /// which will be set to `None` if the method is called for reasons other than to respond to an /// external property access. /// * `signal_emitter` - This marks the method argument to receive a [`SignalEmitter`] instance, /// which is needed for emitting signals the easy way. /// /// # Example /// /// ``` /// # use std::error::Error; /// use zbus_macros::interface; /// use zbus::{ObjectServer, object_server::SignalEmitter, message::Header}; /// /// struct Example { /// _some_data: String, /// } /// /// #[interface(name = "org.myservice.Example")] /// impl Example { /// // "Quit" method. A method may throw errors. /// async fn quit( /// &self, /// #[zbus(header)] /// hdr: Header<'_>, /// #[zbus(signal_emitter)] /// emitter: SignalEmitter<'_>, /// #[zbus(object_server)] /// _server: &ObjectServer, /// ) -> zbus::fdo::Result<()> { /// let path = hdr.path().unwrap(); /// let msg = format!("You are leaving me on the {} path?", path); /// emitter.bye(&msg).await?; /// /// // Do some asynchronous tasks before quitting.. /// /// Ok(()) /// } /// /// // "TheAnswer" property (note: the "name" attribute), with its associated getter. /// // A `the_answer_changed` method has also been generated to emit the /// // "PropertiesChanged" signal for this property. /// #[zbus(property, name = "TheAnswer")] /// fn answer(&self) -> u32 { /// 2 * 3 * 7 /// } /// /// // "IFail" property with its associated getter. /// // An `i_fail_changed` method has also been generated to emit the /// // "PropertiesChanged" signal for this property. /// #[zbus(property)] /// fn i_fail(&self) -> zbus::fdo::Result { /// Err(zbus::fdo::Error::UnknownProperty("IFail".into())) /// } /// /// // "Bye" signal (note: no implementation body). /// #[zbus(signal)] /// async fn bye(signal_emitter: &SignalEmitter<'_>, message: &str) -> zbus::Result<()>; /// /// #[zbus(out_args("answer", "question"))] /// fn meaning_of_life(&self) -> zbus::fdo::Result<(i32, String)> { /// Ok((42, String::from("Meaning of life"))) /// } /// } /// /// # Ok::<_, Box>(()) /// ``` /// /// See also [`ObjectServer`] documentation to learn how to export an interface over a `Connection`. /// /// [`ObjectServer`]: https://docs.rs/zbus/latest/zbus/object_server/struct.ObjectServer.html /// [`ObjectServer::with`]: https://docs.rs/zbus/latest/zbus/object_server/struct.ObjectServer.html#method.with /// [`Connection`]: https://docs.rs/zbus/latest/zbus/connection/struct.Connection.html /// [`Connection::emit_signal()`]: https://docs.rs/zbus/latest/zbus/connection/struct.Connection.html#method.emit_signal /// [`SignalEmitter`]: https://docs.rs/zbus/latest/zbus/object_server/struct.SignalEmitter.html /// [`Interface`]: https://docs.rs/zbus/latest/zbus/object_server/trait.Interface.html /// [dbus_emits_changed_signal]: https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format #[proc_macro_attribute] pub fn interface(attr: TokenStream, item: TokenStream) -> TokenStream { let args = parse_macro_input!(attr with Punctuated::parse_terminated); let input = parse_macro_input!(item as ItemImpl); iface::expand(args, input) .unwrap_or_else(|err| err.to_compile_error()) .into() } /// Derive macro for implementing [`zbus::DBusError`] trait. /// /// This macro makes it easy to implement the [`zbus::DBusError`] trait for your custom error type /// (currently only enums are supported). /// /// If a special variant marked with the `zbus` attribute is present, `From` is /// also implemented for your type. This variant can only have a single unnamed field of type /// [`zbus::Error`]. This implementation makes it possible for you to declare proxy methods to /// directly return this type, rather than [`zbus::Error`]. /// /// Each variant (except for the special `zbus` one) can optionally have a (named or unnamed) /// `String` field (which is used as the human-readable error description). /// /// # Example /// /// ``` /// use zbus_macros::DBusError; /// /// #[derive(DBusError, Debug)] /// #[zbus(prefix = "org.myservice.App")] /// enum Error { /// #[zbus(error)] /// ZBus(zbus::Error), /// FileNotFound(String), /// OutOfMemory, /// } /// ``` /// /// [`zbus::DBusError`]: https://docs.rs/zbus/latest/zbus/trait.DBusError.html /// [`zbus::Error`]: https://docs.rs/zbus/latest/zbus/enum.Error.html /// [`zvariant::Type`]: https://docs.rs/zvariant/latest/zvariant/trait.Type.html /// [`serde::Serialize`]: https://docs.rs/serde/1.0.132/serde/trait.Serialize.html #[proc_macro_derive(DBusError, attributes(zbus))] pub fn derive_dbus_error(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); error::expand_derive(input) .unwrap_or_else(|err| err.to_compile_error()) .into() } zbus_macros-5.3.0/src/proxy.rs000064400000000000000000001161531046102023000145050ustar 00000000000000use crate::utils::{pat_ident, typed_arg, zbus_path, PropertyEmitsChangedSignal}; use proc_macro2::{Literal, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ fold::Fold, parse_quote, parse_str, punctuated::Punctuated, spanned::Spanned, Error, FnArg, Ident, ItemTrait, Meta, Path, ReturnType, Token, TraitItemFn, Visibility, }; use zvariant_utils::{case, def_attrs}; def_attrs! { crate zbus; // Keep this in sync with interface's proxy method attributes. pub TraitAttributes("trait") { interface str, name str, assume_defaults bool, default_path str, default_service str, async_name str, blocking_name str, gen_async bool, gen_blocking bool }; // Keep this in sync with interface's proxy method attributes. pub MethodAttributes("method") { name str, property { pub PropertyAttributes("property") { emits_changed_signal str } }, signal none, object str, async_object str, blocking_object str, no_reply none, no_autostart none, allow_interactive_auth none }; } struct AsyncOpts { blocking: bool, usage: TokenStream, wait: TokenStream, } impl AsyncOpts { fn new(blocking: bool) -> Self { let (usage, wait) = if blocking { (quote! {}, quote! {}) } else { (quote! { async }, quote! { .await }) }; Self { blocking, usage, wait, } } } pub fn expand(args: Punctuated, input: ItemTrait) -> Result { let attrs = TraitAttributes::parse_nested_metas(args)?; let iface_name = match (attrs.interface, attrs.name) { (Some(name), None) | (None, Some(name)) => Ok(Some(name)), (None, None) => Ok(None), (Some(_), Some(_)) => Err(syn::Error::new( input.span(), "both `interface` and `name` attributes shouldn't be specified at the same time", )), }?; let gen_async = attrs.gen_async.unwrap_or(true); #[cfg(feature = "blocking-api")] let gen_blocking = attrs.gen_blocking.unwrap_or(true); #[cfg(not(feature = "blocking-api"))] let gen_blocking = false; // Some sanity checks assert!( gen_blocking || gen_async, "Can't disable both asynchronous and blocking proxy. 😸", ); assert!( gen_blocking || attrs.blocking_name.is_none(), "Can't set blocking proxy's name if you disabled it. 😸", ); assert!( gen_async || attrs.async_name.is_none(), "Can't set asynchronous proxy's name if you disabled it. 😸", ); let blocking_proxy = if gen_blocking { let proxy_name = attrs.blocking_name.unwrap_or_else(|| { if gen_async { format!("{}ProxyBlocking", input.ident) } else { // When only generating blocking proxy, there is no need for a suffix. format!("{}Proxy", input.ident) } }); create_proxy( &input, iface_name.as_deref(), attrs.assume_defaults, attrs.default_path.as_deref(), attrs.default_service.as_deref(), &proxy_name, true, // Signal args structs are shared between the two proxies so always generate it for // async proxy only unless async proxy generation is disabled. !gen_async, )? } else { quote! {} }; let async_proxy = if gen_async { let proxy_name = attrs .async_name .unwrap_or_else(|| format!("{}Proxy", input.ident)); create_proxy( &input, iface_name.as_deref(), attrs.assume_defaults, attrs.default_path.as_deref(), attrs.default_service.as_deref(), &proxy_name, false, true, )? } else { quote! {} }; Ok(quote! { #blocking_proxy #async_proxy }) } #[allow(clippy::too_many_arguments)] pub fn create_proxy( input: &ItemTrait, iface_name: Option<&str>, assume_defaults: Option, default_path: Option<&str>, default_service: Option<&str>, proxy_name: &str, blocking: bool, gen_sig_args: bool, ) -> Result { let zbus = zbus_path(); let other_attrs: Vec<_> = input .attrs .iter() .filter(|a| !a.path().is_ident("zbus")) .collect(); let proxy_name = Ident::new(proxy_name, Span::call_site()); let ident = input.ident.to_string(); let iface_name = iface_name .map(|iface| { // Ensure the interface name is valid. zbus_names::InterfaceName::try_from(iface) .map_err(|e| Error::new(input.span(), format!("{e}"))) .map(|i| i.to_string()) }) .transpose()? .unwrap_or(format!("org.freedesktop.{ident}")); let assume_defaults = assume_defaults.unwrap_or(false); let default_path = default_path .map(|path| { // Ensure the path is valid. zvariant::ObjectPath::try_from(path) .map_err(|e| Error::new(input.span(), format!("{e}"))) .map(|p| p.to_string()) }) .transpose()?; let default_service = default_service .map(|srv| { // Ensure the service is valid. zbus_names::BusName::try_from(srv) .map_err(|e| Error::new(input.span(), format!("{e}"))) .map(|n| n.to_string()) }) .transpose()?; let (default_path, default_service) = if assume_defaults { let path = default_path.or_else(|| Some(format!("/org/freedesktop/{ident}"))); let svc = default_service.or_else(|| Some(iface_name.clone())); (path, svc) } else { (default_path, default_service) }; let mut methods = TokenStream::new(); let mut stream_types = TokenStream::new(); let mut has_properties = false; let mut uncached_properties: Vec = vec![]; let async_opts = AsyncOpts::new(blocking); let visibility = &input.vis; for i in input.items.iter() { if let syn::TraitItem::Fn(m) = i { let method_attrs = MethodAttributes::parse(&m.attrs)?; let property = method_attrs.property.as_ref(); let method_name = m.sig.ident.to_string(); let is_signal = method_attrs.signal; let is_property = property.is_some(); let has_inputs = m.sig.inputs.len() > 1; let member_name = method_attrs.name.clone().unwrap_or_else(|| { case::pascal_or_camel_case( if is_property && has_inputs { assert!(method_name.starts_with("set_")); &method_name[4..] } else { &method_name }, true, ) }); let m = if let Some(prop_attrs) = property { has_properties = true; let emits_changed_signal = if let Some(s) = &prop_attrs.emits_changed_signal { PropertyEmitsChangedSignal::parse(s, m.span())? } else { PropertyEmitsChangedSignal::True }; if let PropertyEmitsChangedSignal::False = emits_changed_signal { uncached_properties.push(member_name.clone()); } gen_proxy_property( &member_name, &method_name, m, &async_opts, emits_changed_signal, ) } else if is_signal { let (method, types) = gen_proxy_signal( &proxy_name, &iface_name, &member_name, &method_name, m, &async_opts, visibility, gen_sig_args, ); stream_types.extend(types); method } else { gen_proxy_method_call(&member_name, &method_name, m, method_attrs, &async_opts)? }; methods.extend(m); } } let AsyncOpts { usage, wait, .. } = async_opts; let (proxy_struct, connection, builder, proxy_trait) = if blocking { let connection = quote! { #zbus::blocking::Connection }; let proxy = quote! { #zbus::blocking::Proxy }; let builder = quote! { #zbus::blocking::proxy::Builder }; let proxy_trait = quote! { #zbus::blocking::proxy::ProxyImpl }; (proxy, connection, builder, proxy_trait) } else { let connection = quote! { #zbus::Connection }; let proxy = quote! { #zbus::Proxy }; let builder = quote! { #zbus::proxy::Builder }; let proxy_trait = quote! { #zbus::proxy::ProxyImpl }; (proxy, connection, builder, proxy_trait) }; let proxy_method_new = match (&default_path, &default_service) { (None, None) => { quote! { /// Creates a new proxy with the given service destination and path. pub #usage fn new(conn: &#connection, destination: D, path: P) -> #zbus::Result<#proxy_name<'p>> where D: ::std::convert::TryInto<#zbus::names::BusName<'static>>, D::Error: ::std::convert::Into<#zbus::Error>, P: ::std::convert::TryInto<#zbus::zvariant::ObjectPath<'static>>, P::Error: ::std::convert::Into<#zbus::Error>, { let obj_path = path.try_into().map_err(::std::convert::Into::into)?; let obj_destination = destination.try_into().map_err(::std::convert::Into::into)?; Self::builder(conn) .path(obj_path)? .destination(obj_destination)? .build()#wait } } } (Some(_), None) => { quote! { /// Creates a new proxy with the given destination, and the default path. pub #usage fn new(conn: &#connection, destination: D) -> #zbus::Result<#proxy_name<'p>> where D: ::std::convert::TryInto<#zbus::names::BusName<'static>>, D::Error: ::std::convert::Into<#zbus::Error>, { let obj_dest = destination.try_into().map_err(::std::convert::Into::into)?; Self::builder(conn) .destination(obj_dest)? .build()#wait } } } (None, Some(_)) => { quote! { /// Creates a new proxy with the given path, and the default destination. pub #usage fn new

(conn: &#connection, path: P) -> #zbus::Result<#proxy_name<'p>> where P: ::std::convert::TryInto<#zbus::zvariant::ObjectPath<'static>>, P::Error: ::std::convert::Into<#zbus::Error>, { let obj_path = path.try_into().map_err(::std::convert::Into::into)?; Self::builder(conn) .path(obj_path)? .build()#wait } } } (Some(_), Some(_)) => { quote! { /// Creates a new proxy with the default service and path. pub #usage fn new(conn: &#connection) -> #zbus::Result<#proxy_name<'p>> { Self::builder(conn).build()#wait } } } }; let default_path = match default_path { Some(p) => quote! { &Some(#zbus::zvariant::ObjectPath::from_static_str_unchecked(#p)) }, None => quote! { &None }, }; let default_service = match default_service { Some(d) => { if d.starts_with(':') || d == "org.freedesktop.DBus" { quote! { &Some(#zbus::names::BusName::Unique( #zbus::names::UniqueName::from_static_str_unchecked(#d), )) } } else { quote! { &Some(#zbus::names::BusName::WellKnown( #zbus::names::WellKnownName::from_static_str_unchecked(#d), )) } } } None => quote! { &None }, }; Ok(quote! { impl<'a> #zbus::proxy::Defaults for #proxy_name<'a> { const INTERFACE: &'static Option<#zbus::names::InterfaceName<'static>> = &Some(#zbus::names::InterfaceName::from_static_str_unchecked(#iface_name)); const DESTINATION: &'static Option<#zbus::names::BusName<'static>> = #default_service; const PATH: &'static Option<#zbus::zvariant::ObjectPath<'static>> = #default_path; } #(#other_attrs)* #[derive(Clone, Debug)] #visibility struct #proxy_name<'p>(#proxy_struct<'p>); impl<'p> #proxy_name<'p> { #proxy_method_new /// Returns a customizable builder for this proxy. pub fn builder(conn: &#connection) -> #builder<'p, Self> { let mut builder = #builder::new(conn) ; if #has_properties { let uncached = vec![#(#uncached_properties),*]; builder.cache_properties(#zbus::proxy::CacheProperties::default()) .uncached_properties(&uncached) } else { builder.cache_properties(#zbus::proxy::CacheProperties::No) } } /// Consumes `self`, returning the underlying `zbus::Proxy`. pub fn into_inner(self) -> #proxy_struct<'p> { self.0 } /// The reference to the underlying `zbus::Proxy`. pub fn inner(&self) -> &#proxy_struct<'p> { &self.0 } /// The mutable reference to the underlying `zbus::Proxy`. pub fn inner_mut(&mut self) -> &mut #proxy_struct<'p> { &mut self.0 } #methods } impl<'p> #proxy_trait<'p> for #proxy_name<'p> { fn builder(conn: &#connection) -> #builder<'p, Self> { Self::builder(conn) } fn into_inner(self) -> #proxy_struct<'p> { self.into_inner() } fn inner(&self) -> &#proxy_struct<'p> { self.inner() } } impl<'p> ::std::convert::From<#zbus::Proxy<'p>> for #proxy_name<'p> { fn from(proxy: #zbus::Proxy<'p>) -> Self { #proxy_name(::std::convert::Into::into(proxy)) } } impl<'p> ::std::convert::AsRef<#proxy_struct<'p>> for #proxy_name<'p> { fn as_ref(&self) -> &#proxy_struct<'p> { self.inner() } } impl<'p> ::std::convert::AsMut<#proxy_struct<'p>> for #proxy_name<'p> { fn as_mut(&mut self) -> &mut #proxy_struct<'p> { self.inner_mut() } } impl<'p> #zbus::zvariant::Type for #proxy_name<'p> { const SIGNATURE: &'static #zbus::zvariant::Signature = &#zbus::zvariant::Signature::ObjectPath; } impl<'p> #zbus::export::serde::ser::Serialize for #proxy_name<'p> { fn serialize(&self, serializer: S) -> ::std::result::Result where S: #zbus::export::serde::ser::Serializer, { ::std::string::String::serialize( &::std::string::ToString::to_string(self.inner().path()), serializer, ) } } #stream_types }) } fn gen_proxy_method_call( method_name: &str, snake_case_name: &str, m: &TraitItemFn, method_attrs: MethodAttributes, async_opts: &AsyncOpts, ) -> Result { let AsyncOpts { usage, wait, blocking, } = async_opts; let zbus = zbus_path(); let other_attrs: Vec<_> = m .attrs .iter() .filter(|a| !a.path().is_ident("zbus")) .collect(); let args: Vec<_> = m .sig .inputs .iter() .filter_map(typed_arg) .filter_map(pat_ident) .collect(); let proxy_object = method_attrs.object.as_ref().map(|o| { if *blocking { // FIXME: for some reason Rust doesn't let us move `blocking_proxy_object` so we've to // clone. method_attrs .blocking_object .as_ref() .cloned() .unwrap_or_else(|| format!("{o}ProxyBlocking")) } else { method_attrs .async_object .as_ref() .cloned() .unwrap_or_else(|| format!("{o}Proxy")) } }); let method_flags = match ( method_attrs.no_reply, method_attrs.no_autostart, method_attrs.allow_interactive_auth, ) { (true, false, false) => Some(quote!(::std::convert::Into::into( zbus::proxy::MethodFlags::NoReplyExpected ))), (false, true, false) => Some(quote!(::std::convert::Into::into( zbus::proxy::MethodFlags::NoAutoStart ))), (false, false, true) => Some(quote!(::std::convert::Into::into( zbus::proxy::MethodFlags::AllowInteractiveAuth ))), (true, true, false) => Some(quote!( zbus::proxy::MethodFlags::NoReplyExpected | zbus::proxy::MethodFlags::NoAutoStart )), (true, false, true) => Some(quote!( zbus::proxy::MethodFlags::NoReplyExpected | zbus::proxy::MethodFlags::AllowInteractiveAuth )), (false, true, true) => Some(quote!( zbus::proxy::MethodFlags::NoAutoStart | zbus::proxy::MethodFlags::AllowInteractiveAuth )), (true, true, true) => Some(quote!( zbus::proxy::MethodFlags::NoReplyExpected | zbus::proxy::MethodFlags::NoAutoStart | zbus::proxy::MethodFlags::AllowInteractiveAuth )), _ => None, }; let method = Ident::new(snake_case_name, Span::call_site()); let inputs = &m.sig.inputs; let mut generics = m.sig.generics.clone(); let where_clause = generics.where_clause.get_or_insert(parse_quote!(where)); for param in generics .params .iter() .filter(|a| matches!(a, syn::GenericParam::Type(_))) { let is_input_type = inputs.iter().any(|arg| { // FIXME: We want to only require `Serialize` from input types and `DeserializeOwned` // from output types but since we don't have type introspection, we employ this // workaround of matching on the string representation of the the types to figure out // which generic types are input types. if let FnArg::Typed(pat) = arg { let pat = pat.ty.to_token_stream().to_string(); if let Some(ty_name) = pat.strip_prefix('&') { let ty_name = ty_name.trim_start(); ty_name == param.to_token_stream().to_string() } else { false } } else { false } }); let serde_bound: TokenStream = if is_input_type { parse_quote!(#zbus::export::serde::ser::Serialize) } else { parse_quote!(#zbus::export::serde::de::DeserializeOwned) }; where_clause.predicates.push(parse_quote!( #param: #serde_bound + #zbus::zvariant::Type )); } let (_, ty_generics, where_clause) = generics.split_for_impl(); if let Some(proxy_path) = proxy_object { let proxy_path = parse_str::(&proxy_path)?; let signature = quote! { fn #method #ty_generics(#inputs) -> #zbus::Result<#proxy_path<'p>> #where_clause }; Ok(quote! { #(#other_attrs)* pub #usage #signature { let object_path: #zbus::zvariant::OwnedObjectPath = self.0.call( #method_name, &#zbus::zvariant::DynamicTuple((#(#args,)*)), ) #wait?; #proxy_path::builder(&self.0.connection()) .path(object_path)? .build() #wait } }) } else { let body = if args.len() == 1 { // Wrap single arg in a tuple so if it's a struct/tuple itself, zbus will only remove // the '()' from the signature that we add and not the actual intended ones. let arg = &args[0]; quote! { &#zbus::zvariant::DynamicTuple((#arg,)) } } else { quote! { &#zbus::zvariant::DynamicTuple((#(#args),*)) } }; let output = &m.sig.output; let signature = quote! { fn #method #ty_generics(#inputs) #output #where_clause }; if let Some(method_flags) = method_flags { if method_attrs.no_reply { Ok(quote! { #(#other_attrs)* pub #usage #signature { self.0.call_with_flags::<_, _, ()>(#method_name, #method_flags, #body)#wait?; ::std::result::Result::Ok(()) } }) } else { Ok(quote! { #(#other_attrs)* pub #usage #signature { let reply = self.0.call_with_flags(#method_name, #method_flags, #body)#wait?; // SAFETY: This unwrap() cannot fail due to the guarantees in // call_with_flags, which can only return Ok(None) if the // NoReplyExpected is set. By not passing NoReplyExpected, // we are guaranteed to get either an Err variant (handled // in the previous statement) or Ok(Some(T)) which is safe to // unwrap ::std::result::Result::Ok(reply.unwrap()) } }) } } else { Ok(quote! { #(#other_attrs)* pub #usage #signature { let reply = self.0.call(#method_name, #body)#wait?; ::std::result::Result::Ok(reply) } }) } } } fn gen_proxy_property( property_name: &str, method_name: &str, m: &TraitItemFn, async_opts: &AsyncOpts, emits_changed_signal: PropertyEmitsChangedSignal, ) -> TokenStream { let AsyncOpts { usage, wait, blocking, } = async_opts; let zbus = zbus_path(); let other_attrs: Vec<_> = m .attrs .iter() .filter(|a| !a.path().is_ident("zbus")) .collect(); let signature = &m.sig; if signature.inputs.len() > 1 { let value = pat_ident(typed_arg(signature.inputs.last().unwrap()).unwrap()).unwrap(); quote! { #(#other_attrs)* #[allow(clippy::needless_question_mark)] pub #usage #signature { ::std::result::Result::Ok(self.0.set_property(#property_name, #value)#wait?) } } } else { // This should fail to compile only if the return type is wrong, // so use that as the span. let body_span = if let ReturnType::Type(_, ty) = &signature.output { ty.span() } else { signature.span() }; let body = quote_spanned! {body_span => ::std::result::Result::Ok(self.0.get_property(#property_name)#wait?) }; let ret_type = if let ReturnType::Type(_, ty) = &signature.output { Some(ty) } else { None }; let (proxy_name, prop_stream) = if *blocking { ( "zbus::blocking::Proxy", quote! { #zbus::blocking::proxy::PropertyIterator }, ) } else { ("zbus::Proxy", quote! { #zbus::proxy::PropertyStream }) }; let receive_method = match emits_changed_signal { PropertyEmitsChangedSignal::True | PropertyEmitsChangedSignal::Invalidates => { let (_, ty_generics, where_clause) = m.sig.generics.split_for_impl(); let receive = format_ident!("receive_{}_changed", method_name); let gen_doc = format!( "Create a stream for the `{property_name}` property changes. \ This is a convenient wrapper around [`{proxy_name}::receive_property_changed`]." ); quote! { #[doc = #gen_doc] pub #usage fn #receive #ty_generics( &self ) -> #prop_stream<'p, <#ret_type as #zbus::ResultAdapter>::Ok> #where_clause { self.0.receive_property_changed(#property_name)#wait } } } PropertyEmitsChangedSignal::False | PropertyEmitsChangedSignal::Const => { quote! {} } }; let cached_getter_method = match emits_changed_signal { PropertyEmitsChangedSignal::True | PropertyEmitsChangedSignal::Invalidates | PropertyEmitsChangedSignal::Const => { let cached_getter = format_ident!("cached_{}", method_name); let cached_doc = format!( " Get the cached value of the `{property_name}` property, or `None` if the property is not cached.", ); quote! { #[doc = #cached_doc] pub fn #cached_getter(&self) -> ::std::result::Result< ::std::option::Option<<#ret_type as #zbus::ResultAdapter>::Ok>, <#ret_type as #zbus::ResultAdapter>::Err> { self.0.cached_property(#property_name).map_err(::std::convert::Into::into) } } } PropertyEmitsChangedSignal::False => quote! {}, }; quote! { #(#other_attrs)* #[allow(clippy::needless_question_mark)] pub #usage #signature { #body } #cached_getter_method #receive_method } } } struct SetLifetimeS; impl Fold for SetLifetimeS { fn fold_type_reference(&mut self, node: syn::TypeReference) -> syn::TypeReference { let mut t = syn::fold::fold_type_reference(self, node); t.lifetime = Some(syn::Lifetime::new("'s", Span::call_site())); t } fn fold_lifetime(&mut self, _node: syn::Lifetime) -> syn::Lifetime { syn::Lifetime::new("'s", Span::call_site()) } } #[allow(clippy::too_many_arguments)] fn gen_proxy_signal( proxy_name: &Ident, iface_name: &str, signal_name: &str, snake_case_name: &str, method: &TraitItemFn, async_opts: &AsyncOpts, visibility: &Visibility, gen_sig_args: bool, ) -> (TokenStream, TokenStream) { let AsyncOpts { usage, wait, blocking, } = async_opts; let zbus = zbus_path(); let other_attrs: Vec<_> = method .attrs .iter() .filter(|a| !a.path().is_ident("zbus")) .collect(); let input_types: Vec<_> = method .sig .inputs .iter() .filter_map(|arg| match arg { FnArg::Typed(p) => Some(&*p.ty), _ => None, }) .collect(); let input_types_s: Vec<_> = SetLifetimeS .fold_signature(method.sig.clone()) .inputs .iter() .filter_map(|arg| match arg { FnArg::Typed(p) => Some(p.ty.clone()), _ => None, }) .collect(); let args: Vec = method .sig .inputs .iter() .filter_map(typed_arg) .filter_map(|arg| pat_ident(arg).cloned()) .collect(); let args_nth: Vec = args .iter() .enumerate() .map(|(i, _)| Literal::usize_unsuffixed(i)) .collect(); let mut generics = method.sig.generics.clone(); let where_clause = generics.where_clause.get_or_insert(parse_quote!(where)); for param in generics .params .iter() .filter(|a| matches!(a, syn::GenericParam::Type(_))) { where_clause .predicates .push(parse_quote!(#param: #zbus::export::serde::de::Deserialize<'s> + #zbus::zvariant::Type + ::std::fmt::Debug)); } generics.params.push(parse_quote!('s)); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let ( proxy_path, receive_signal_link, receive_signal_with_args_link, trait_name, trait_link, signal_type, ) = if *blocking { ( "zbus::blocking::Proxy", "https://docs.rs/zbus/latest/zbus/blocking/proxy/struct.Proxy.html#method.receive_signal", "https://docs.rs/zbus/latest/zbus/blocking/proxy/struct.Proxy.html#method.receive_signal_with_args", "Iterator", "https://doc.rust-lang.org/std/iter/trait.Iterator.html", quote! { blocking::proxy::SignalIterator }, ) } else { ( "zbus::Proxy", "https://docs.rs/zbus/latest/zbus/proxy/struct.Proxy.html#method.receive_signal", "https://docs.rs/zbus/latest/zbus/proxy/struct.Proxy.html#method.receive_signal_with_args", "Stream", "https://docs.rs/futures/0.3.15/futures/stream/trait.Stream.html", quote! { proxy::SignalStream }, ) }; let receiver_name = format_ident!("receive_{snake_case_name}"); let receiver_with_args_name = format_ident!("receive_{snake_case_name}_with_args"); let stream_name = format_ident!("{signal_name}{trait_name}"); let signal_args = format_ident!("{signal_name}Args"); let signal_name_ident = format_ident!("{signal_name}"); let receive_gen_doc = format!( "Create a stream that receives `{signal_name}` signals.\n\ \n\ This a convenient wrapper around [`{proxy_path}::receive_signal`]({receive_signal_link}).", ); let receive_with_args_gen_doc = format!( "Create a stream that receives `{signal_name}` signals.\n\ \n\ This a convenient wrapper around [`{proxy_path}::receive_signal_with_args`]({receive_signal_with_args_link}).", ); let receive_signal_with_args = if args.is_empty() { quote!() } else { quote! { #[doc = #receive_with_args_gen_doc] #(#other_attrs)* pub #usage fn #receiver_with_args_name(&self, args: &[(u8, &str)]) -> #zbus::Result<#stream_name> { self.0.receive_signal_with_args(#signal_name, args)#wait.map(#stream_name) } } }; let receive_signal = quote! { #[doc = #receive_gen_doc] #(#other_attrs)* pub #usage fn #receiver_name(&self) -> #zbus::Result<#stream_name> { self.0.receive_signal(#signal_name)#wait.map(#stream_name) } #receive_signal_with_args }; let stream_gen_doc = format!( "A [`{trait_name}`] implementation that yields [`{signal_name}`] signals.\n\ \n\ Use [`{proxy_name}::{receiver_name}`] to create an instance of this type.\n\ \n\ [`{trait_name}`]: {trait_link}", ); let signal_args_gen_doc = format!("`{signal_name}` signal arguments."); let args_struct_gen_doc = format!("A `{signal_name}` signal."); let args_struct_decl = if gen_sig_args { quote! { #[doc = #args_struct_gen_doc] #[derive(Debug, Clone)] #visibility struct #signal_name_ident(#zbus::message::Body); impl #signal_name_ident { #[doc = "Try to construct a "] #[doc = #signal_name] #[doc = " from a [`zbus::Message`]."] pub fn from_message(msg: M) -> ::std::option::Option where M: ::std::convert::Into<#zbus::message::Message>, { let msg = msg.into(); let hdr = msg.header(); let message_type = msg.message_type(); let interface = hdr.interface(); let member = hdr.member(); let interface = interface.as_ref().map(|i| i.as_str()); let member = member.as_ref().map(|m| m.as_str()); match (message_type, interface, member) { (#zbus::message::Type::Signal, Some(#iface_name), Some(#signal_name)) => { Some(Self(msg.body())) } _ => None, } } #[doc = "The reference to the underlying [`zbus::Message`]."] pub fn message(&self) -> &#zbus::message::Message { self.0.message() } } impl ::std::convert::From<#signal_name_ident> for #zbus::message::Message { fn from(signal: #signal_name_ident) -> Self { signal.0.message().clone() } } } } else { quote!() }; let args_impl = if args.is_empty() || !gen_sig_args { quote!() } else { let arg_fields_init = if args.len() == 1 { quote! { #(#args)*: args } } else { quote! { #(#args: args.#args_nth),* } }; quote! { impl #signal_name_ident { /// Retrieve the signal arguments. pub fn args #ty_generics(&'s self) -> #zbus::Result<#signal_args #ty_generics> #where_clause { ::std::convert::TryFrom::try_from(&self.0) } } #[doc = #signal_args_gen_doc] #visibility struct #signal_args #ty_generics { phantom: std::marker::PhantomData<&'s ()>, #( pub #args: #input_types_s ),* } impl #impl_generics #signal_args #ty_generics #where_clause { #( pub fn #args(&self) -> &#input_types_s { &self.#args } )* } impl #impl_generics std::fmt::Debug for #signal_args #ty_generics #where_clause { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct(#signal_name) #( .field(stringify!(#args), &self.#args) )* .finish() } } impl #impl_generics ::std::convert::TryFrom<&'s #zbus::message::Body> for #signal_args #ty_generics #where_clause { type Error = #zbus::Error; fn try_from(msg_body: &'s #zbus::message::Body) -> #zbus::Result { msg_body.deserialize::<(#(#input_types),*)>() .map_err(::std::convert::Into::into) .map(|args| { #signal_args { phantom: ::std::marker::PhantomData, #arg_fields_init } }) } } } }; let stream_impl = if *blocking { quote! { impl ::std::iter::Iterator for #stream_name { type Item = #signal_name_ident; fn next(&mut self) -> ::std::option::Option { ::std::iter::Iterator::next(&mut self.0) .map(|msg| #signal_name_ident(msg.body())) } } } } else { quote! { impl #zbus::export::futures_core::stream::Stream for #stream_name { type Item = #signal_name_ident; fn poll_next( self: ::std::pin::Pin<&mut Self>, cx: &mut ::std::task::Context<'_>, ) -> ::std::task::Poll<::std::option::Option> { #zbus::export::futures_core::stream::Stream::poll_next( ::std::pin::Pin::new(&mut self.get_mut().0), cx, ) .map(|msg| msg.map(|msg| #signal_name_ident(msg.body()))) } } impl #zbus::export::ordered_stream::OrderedStream for #stream_name { type Data = #signal_name_ident; type Ordering = #zbus::message::Sequence; fn poll_next_before( self: ::std::pin::Pin<&mut Self>, cx: &mut ::std::task::Context<'_>, before: ::std::option::Option<&Self::Ordering> ) -> ::std::task::Poll<#zbus::export::ordered_stream::PollResult> { #zbus::export::ordered_stream::OrderedStream::poll_next_before( ::std::pin::Pin::new(&mut self.get_mut().0), cx, before, ) .map(|msg| msg.map_data(|msg| #signal_name_ident(msg.body()))) } } impl #zbus::export::futures_core::stream::FusedStream for #stream_name { fn is_terminated(&self) -> bool { self.0.is_terminated() } } #[#zbus::export::async_trait::async_trait] impl #zbus::AsyncDrop for #stream_name { async fn async_drop(self) { self.0.async_drop().await } } } }; let stream_types = quote! { #[doc = #stream_gen_doc] #[derive(Debug)] #visibility struct #stream_name(#zbus::#signal_type<'static>); #zbus::export::static_assertions::assert_impl_all!( #stream_name: ::std::marker::Send, ::std::marker::Unpin ); impl #stream_name { /// Consumes `self`, returning the underlying `zbus::#signal_type`. pub fn into_inner(self) -> #zbus::#signal_type<'static> { self.0 } /// The reference to the underlying `zbus::#signal_type`. pub fn inner(&self) -> & #zbus::#signal_type<'static> { &self.0 } } #stream_impl #args_struct_decl #args_impl }; (receive_signal, stream_types) } zbus_macros-5.3.0/src/utils.rs000064400000000000000000000052101046102023000144530ustar 00000000000000use std::fmt::Display; use proc_macro2::{Span, TokenStream}; use proc_macro_crate::{crate_name, FoundCrate}; use quote::{format_ident, quote}; use syn::{Attribute, FnArg, Ident, Pat, PatIdent, PatType}; pub fn zbus_path() -> TokenStream { if let Ok(FoundCrate::Name(name)) = crate_name("zbus") { let ident = format_ident!("{}", name); quote! { ::#ident } } else { quote! { ::zbus } } } pub fn typed_arg(arg: &FnArg) -> Option<&PatType> { match arg { FnArg::Typed(t) => Some(t), _ => None, } } pub fn pat_ident(pat: &PatType) -> Option<&Ident> { match &*pat.pat { Pat::Ident(PatIdent { ident, .. }) => Some(ident), _ => None, } } pub fn get_doc_attrs(attrs: &[Attribute]) -> Vec<&Attribute> { attrs.iter().filter(|x| x.path().is_ident("doc")).collect() } // Convert to pascal case, assuming snake case. // If `s` is already in pascal case, should yield the same result. 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 } pub fn is_blank(s: &str) -> bool { s.trim().is_empty() } /// Standard annotation `org.freedesktop.DBus.Property.EmitsChangedSignal`. /// /// See . #[derive(Debug, Default, Clone, PartialEq)] pub enum PropertyEmitsChangedSignal { #[default] True, Invalidates, Const, False, } impl Display for PropertyEmitsChangedSignal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let emits_changed_signal = match self { PropertyEmitsChangedSignal::True => "true", PropertyEmitsChangedSignal::Const => "const", PropertyEmitsChangedSignal::False => "false", PropertyEmitsChangedSignal::Invalidates => "invalidates", }; write!(f, "{}", emits_changed_signal) } } impl PropertyEmitsChangedSignal { pub fn parse(s: &str, span: Span) -> syn::Result { use PropertyEmitsChangedSignal::*; match s { "true" => Ok(True), "invalidates" => Ok(Invalidates), "const" => Ok(Const), "false" => Ok(False), other => Err(syn::Error::new( span, format!("invalid value \"{other}\" for attribute `property(emits_changed_signal)`"), )), } } } zbus_macros-5.3.0/tests/tests.rs000064400000000000000000000262371046102023000150440ustar 00000000000000use futures_util::{ future::{select, Either}, stream::StreamExt, }; use std::future::ready; use zbus::{block_on, fdo, object_server::SignalEmitter, proxy::CacheProperties}; use zbus_macros::{interface, proxy, DBusError}; mod param { #[zbus_macros::proxy( interface = "org.freedesktop.zbus_macros.ProxyParam", default_service = "org.freedesktop.zbus_macros", default_path = "/org/freedesktop/zbus_macros/test" )] trait ProxyParam { #[zbus(object = "super::test::Test")] fn some_method(&self, test: &T); } } mod test { use zbus::{ fdo, zvariant::{OwnedStructure, Structure}, }; #[zbus_macros::proxy( assume_defaults = false, interface = "org.freedesktop.zbus_macros.Test", default_service = "org.freedesktop.zbus_macros" )] pub(super) trait Test { /// comment for a_test() fn a_test(&self, val: &str) -> zbus::Result; /// The generated proxies implement both `zvariant::Type` and `serde::ser::Serialize` /// which is useful to pass in a proxy as a param. It serializes it as an `ObjectPath`. fn some_method(&self, object_path: &T) -> zbus::Result<()>; /// A call accepting an argument that only implements DynamicType and Serialize. fn test_dyn_type(&self, arg: Structure<'_>, arg2: u32) -> zbus::Result<()>; /// A call returning an type that only implements DynamicDeserialize fn test_dyn_ret(&self) -> zbus::Result; #[zbus(name = "CheckRENAMING")] fn check_renaming(&self) -> zbus::Result>; #[zbus(property)] fn property(&self) -> fdo::Result>; #[zbus(property(emits_changed_signal = "const"))] fn a_const_property(&self) -> fdo::Result>; #[zbus(property(emits_changed_signal = "false"))] fn a_live_property(&self) -> fdo::Result>; #[zbus(property)] fn set_property(&self, val: u16) -> fdo::Result<()>; #[zbus(signal)] fn a_signal(&self, arg: u8, other: T) -> fdo::Result<()> where T: AsRef; } } #[test] fn test_proxy() { block_on(async move { let connection = zbus::Connection::session().await.unwrap(); let proxy = test::TestProxy::builder(&connection) .path("/org/freedesktop/zbus_macros/test") .unwrap() .cache_properties(CacheProperties::No) .build() .await .unwrap(); fdo::DBusProxy::builder(&connection) .build() .await .unwrap() .request_name( "org.freedesktop.zbus_macros".try_into().unwrap(), fdo::RequestNameFlags::DoNotQueue.into(), ) .await .unwrap(); let mut stream = proxy.receive_a_signal().await.unwrap(); let left_future = async move { // These calls will never happen so just testing the build mostly. let signal = stream.next().await.unwrap(); let args = signal.args::<&str>().unwrap(); assert_eq!(*args.arg(), 0u8); assert_eq!(*args.other(), "whatever"); }; futures_util::pin_mut!(left_future); let right_future = async { ready(()).await; }; futures_util::pin_mut!(right_future); if let Either::Left((_, _)) = select(left_future, right_future).await { panic!("Shouldn't be receiving our dummy signal: `ASignal`"); } }); } #[ignore] #[test] fn test_derive_error() { #[allow(unused)] #[derive(Debug, DBusError)] #[zbus(prefix = "org.freedesktop.zbus")] enum Test { #[zbus(error)] ZBus(zbus::Error), SomeExcuse, #[zbus(name = "I.Am.Sorry.Dave")] IAmSorryDave(String), LetItBe { desc: String, }, } } #[test] fn test_interface() { use serde::{Deserialize, Serialize}; use zbus::{ object_server::Interface, zvariant::{Type, Value}, }; struct Test { something: String, generic: T, } #[derive(Serialize, Deserialize, Type, Value)] struct MyCustomPropertyType(u32); #[interface(name = "org.freedesktop.zbus.Test", spawn = false)] impl Test where T: serde::ser::Serialize + zbus::zvariant::Type + Send + Sync, { /// Testing `no_arg` documentation is reflected in XML. fn no_arg(&self) { unimplemented!() } // Also tests that mut argument bindings work for regular methods #[allow(unused_assignments)] fn str_u32(&self, mut val: &str) -> zbus::fdo::Result { let res = val .parse() .map_err(|e| zbus::fdo::Error::Failed(format!("Invalid val: {e}"))); val = "test mut"; res } // TODO: naming output arguments after "RFC: Structural Records #2584" fn many_output(&self) -> zbus::fdo::Result<(&T, String)> { Ok((&self.generic, self.something.clone())) } fn pair_output(&self) -> zbus::fdo::Result<((u32, String),)> { unimplemented!() } #[zbus(property)] fn my_custom_property(&self) -> MyCustomPropertyType { unimplemented!() } // Also tests that mut argument bindings work for properties #[zbus(property)] fn set_my_custom_property(&self, mut _value: MyCustomPropertyType) { _value = MyCustomPropertyType(42); } // Test that the emits_changed_signal property results in the correct annotation #[zbus(property(emits_changed_signal = "false"))] fn my_custom_property_emits_false(&self) -> MyCustomPropertyType { unimplemented!() } #[zbus(property(emits_changed_signal = "invalidates"))] fn my_custom_property_emits_invalidates(&self) -> MyCustomPropertyType { unimplemented!() } #[zbus(property(emits_changed_signal = "const"))] fn my_custom_property_emits_const(&self) -> MyCustomPropertyType { unimplemented!() } #[zbus(name = "CheckVEC")] fn check_vec(&self) -> Vec { unimplemented!() } /// Testing my_prop documentation is reflected in XML. /// /// And that too. #[zbus(property)] fn my_prop(&self) -> u16 { unimplemented!() } #[zbus(property)] fn set_my_prop(&mut self, _val: u16) { unimplemented!() } /// Emit a signal. #[zbus(signal)] async fn signal(emitter: &SignalEmitter<'_>, arg: u8, other: &str) -> zbus::Result<()>; } const EXPECTED_XML: &str = r#" "#; let t = Test { something: String::from("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 { block_on(async { // check compilation let c = zbus::Connection::session().await.unwrap(); let s = c.object_server(); let m = zbus::message::Message::method_call("/", "StrU32") .unwrap() .build(&(42,)) .unwrap(); let _ = t.call(s, &c, &m, "StrU32".try_into().unwrap()); let ctxt = SignalEmitter::new(&c, "/does/not/matter").unwrap(); ctxt.signal(23, "ergo sum").await.unwrap(); }); } } mod signal_from_message { use super::*; use zbus::message::Message; #[proxy( interface = "org.freedesktop.zbus_macros.Test", default_service = "org.freedesktop.zbus_macros", default_path = "/org/freedesktop/zbus_macros/test" )] trait Test { #[zbus(signal)] fn signal_u8(&self, arg: u8) -> fdo::Result<()>; #[zbus(signal)] fn signal_string(&self, arg: String) -> fdo::Result<()>; } #[test] fn signal_u8() { let message = Message::signal( "/org/freedesktop/zbus_macros/test", "org.freedesktop.zbus_macros.Test", "SignalU8", ) .expect("Failed to create signal message builder") .build(&(1u8,)) .expect("Failed to build signal message"); assert!( SignalU8::from_message(message.clone()).is_some(), "Message is a SignalU8" ); assert!( SignalString::from_message(message).is_none(), "Message is not a SignalString" ); } #[test] fn signal_string() { let message = Message::signal( "/org/freedesktop/zbus_macros/test", "org.freedesktop.zbus_macros.Test", "SignalString", ) .expect("Failed to create signal message builder") .build(&(String::from("test"),)) .expect("Failed to build signal message"); assert!( SignalString::from_message(message.clone()).is_some(), "Message is a SignalString" ); assert!( SignalU8::from_message(message).is_none(), "Message is not a SignalU8" ); } #[test] fn wrong_data() { let message = Message::signal( "/org/freedesktop/zbus_macros/test", "org.freedesktop.zbus_macros.Test", "SignalU8", ) .expect("Failed to create signal message builder") .build(&(String::from("test"),)) .expect("Failed to build signal message"); let signal = SignalU8::from_message(message).expect("Message is a SignalU8"); signal .args() .expect_err("Message does not have correct data"); } }