zbus

repository·main·Indexed 20 days ago

https://github.com/z-galaxy/zbus

A high-level, safe Rust API for D-Bus communication that avoids dependencies on C libraries. It provides tools for creating D-Bus services and consuming them via proxies, including specialized subcrates like zvariant for data encoding, zbus_xml for introspection XML, and the zbus-xmlgen CLI tool for generating Rust code from D-Bus interface descriptions.

Tokens
62K
Snippets
171
Records
253
Agent score
71%

What's inside zbus

  1. Overview of zbus subcrates

    main

    zbus is a safe and simple Rust API for D-Bus communication that does not depend on C libraries. The project is organized into several specialized subcrates:

    • zbus and zbus_macros: The primary crates for interacting with D-Bus.
    • zvariant and zvariant_derive: Handles encoding and decoding of data to/from the D-Bus wire format.
    • zbus_names: Provides types for various D-Bus bus names.
    • zbus_xml: Provides an API to handle D-Bus introspection description XML.
    • zbus_xmlgen: A developer tool used to generate Rust code from D-Bus interface description XML.
  2. Understand the zbus and zvariant ecosystem

    main

    The zbus project is a 100% Rust-native implementation of the D-Bus protocol, used for inter-process communication (IPC) on Linux. It is split into two primary crates that work together:

    1. zbus: The main crate used to interact with D-Bus. It manages connection establishment and provides high-level APIs for sending and receiving D-Bus messages, such as method calls, signals, and properties.
    2. zvariant: A crate providing a serde-based API to serialize and deserialize Rust data types to and from the D-Bus marshalling format. It also supports GVariant, a modified version of this format used for efficient storage of arbitrary data.
  3. What is zbus_names?

    main
    zbus_names is a crate providing a collection of types representing various D-Bus bus names. It is primarily used by zbus and zbus_macros to ensure type-safe handling of D-Bus names according to the D-Bus specification. Other D-Bus related crates are encouraged to use this API to maintain compatibility.
  4. Simulate nullable types (Option<T>) in D-Bus

    main

    D-Bus does not have a native concept of nullable types. You can simulate Option<T> in two ways:

    1. Special Value (Default): Use a specific value to represent None (e.g., an empty string "" for String). zvariant provides an Optional<T> type to make this easier, especially if T implements Default.
    2. Array Encoding (a?): Represent None as an empty array and Some(T) as an array with one element. This requires enabling the option-as-array Cargo feature.

    Caveats for option-as-array:

    • It is not compatible with interface or proxy property methods.
    • Both sender and receiver must explicitly agree on this encoding.
    • It can be confusing to generic D-Bus tools like d-feet because the signature doesn't explicitly signal nullability.
  5. How to handle custom types and enums with zvariant

    main

    To use custom struct or enum types with zvariant, you must derive the Type trait.

    Structs

    Structs are encoded as D-Bus structures. You can inspect the D-Bus signature using the SIGNATURE constant.

    Enums

    • Complex Enums: By default, complex enums are encoded as a structure where the first field is a u32 representing the variant index, followed by the variant's fields. The signature follows the pattern (u(fields...)).
    • Unit Enums: Can be encoded as a single byte (e.g., signature y) using serde_repr and #[repr(u8)].
    • String Enums: You can force an enum to be serialized as a string by using the #[zvariant(signature = "s")] attribute.

    Note: For complex enums, all variants must have the same number and types of fields, though the field names themselves do not matter.

    #[derive(Deserialize, Serialize, Type, PartialEq, Debug)]
    struct MyStruct<'s> {
        field1: u16,
        field2: i64,
        field3: &'s str,
    }
    
    #[derive(Deserialize, Serialize, Type, PartialEq, Debug)]
    #[zvariant(signature = "s")]
    enum StrEnum {
        Variant1,
        Variant2,
        #[serde(untagged)]
        Other(String),
    }
  6. How enums are encoded in zvariant

    main

    By default, zvariant encodes enums using the following rules:

    1. Unit-type enums: Encoded as a u32 representing the variant index.
    2. Other enums: Encoded as a structure where the first field is the variant index and the subsequent fields are the variant's data.

    Constraint: All variants in the enum must have the same number and types of fields. If you need to encode different data types in different variants, use [Value] or [OwnedValue].

    Example of standard enum encoding:

    use zbus::zvariant::{serialized::Context, to_bytes, Type, LE};
    use serde::{Deserialize, Serialize};
    
    #[derive(Deserialize, Serialize, Type, PartialEq, Debug)]
    enum Enum<'s> {
        Variant1 { field1: u16, field2: i64, field3: &'s str },
        Variant2(u16, i64, &'s str),
        Variant3 { f1: u16, f2: i64, f3: &'s str },
    }
    
    let e = Enum::Variant3 {
        f1: 42,
        f2: i64::max_value(),
        f3: "hello",
    };
    let ctxt = Context::new_dbus(LE, 0);
    let encoded = to_bytes(ctxt, &e).unwrap();
    let decoded: Enum = encoded.deserialize().unwrap().0;
    assert_eq!(decoded, e);
  7. Handle nested D-Bus dictionaries

    main

    To represent nested D-Bus dictionaries like a{sa{sv}} (common in ObjectManager), nest one *Dict struct inside another. The outer struct's signature should reflect the full nested type (e.g., #[zvariant(signature = "a{sa{sv}")]), and its fields should be the inner *Dict structs. The outer derive handles the nesting by deferring to the inner struct's serialization implementation.

    #[derive(DeserializeDict, SerializeDict, Type, Default, Clone)]
    #[zvariant(signature = "a{sv}", rename_all = "PascalCase")]
    struct Adapter {
        address: Option<String>,
        name: Option<String>,
        powered: bool,
    }
    
    #[derive(DeserializeDict, SerializeDict, Type, Default)]
    #[zvariant(signature = "a{sa{sv}}")]
    struct Interfaces {
        #[zvariant(rename = "org.bluez.Adapter1")]
        adapter: Option<Adapter>,
        #[zvariant(rename = "org.bluez.Media1")]
        media: Media,
    }
  8. Understand the D-Bus Bus model

    main

    A D-Bus "bus" acts as a server in a bus-topology, relaying messages between connected endpoints. It enables endpoint discovery and the broadcasting of signals.

    On a typical Linux system, you will encounter two main types of buses:

    • System bus: A system-wide bus.
    • Session bus: A bus specific to the current user.

    It is also possible to use private buses or engage in direct peer-to-peer communication without a bus.

  9. Understand zbus async runtime compatibility

    main

    zbus is runtime-agnostic but manages internal tasks by spawning a thread per connection.

    Avoiding extra threads

    If you want to avoid zbus spawning threads, you must:

    1. Use connection::Builder and disable the internal_executor flag.
    2. Ensure the internal executor is manually ticked continuously.

    Runtime Selection Logic

    • Both tokio and async-io enabled: zbus selects the runtime at run time (using tokio if the current thread is driven by a tokio runtime, otherwise async-io).
    • Only tokio enabled: zbus must be used within a tokio runtime. There is no async-io fallback.
    • Only async-io enabled (default): zbus uses async-io and its internal executor thread.
  10. Stability and versioning of zvariant_utils

    main
    The API in zvariant_utils is currently NOT expected to be stable. While the crate follows semantic versioning (semver) rules—meaning breaking changes will trigger a major version bump—developers should be aware that the interface may change between minor releases.