#dbus #api-bindings #xml #introspection #interface #generate #xml-data

bin+lib dbus-codegen

Binary crate to generate Rust code from XML introspection data

12 releases (7 breaking)

0.12.0 May 29, 2024
0.11.0 Feb 19, 2024
0.10.0 Oct 13, 2021
0.9.1 Jan 5, 2021
0.2.0 Nov 11, 2017

#268 in Unix APIs

Download history 2058/week @ 2024-07-21 2521/week @ 2024-07-28 1545/week @ 2024-08-04 2270/week @ 2024-08-11 1648/week @ 2024-08-18 2055/week @ 2024-08-25 1865/week @ 2024-09-01 1906/week @ 2024-09-08 1375/week @ 2024-09-15 1772/week @ 2024-09-22 1819/week @ 2024-09-29 2162/week @ 2024-10-06 1885/week @ 2024-10-13 1807/week @ 2024-10-20 2270/week @ 2024-10-27 2362/week @ 2024-11-03

8,445 downloads per month
Used in 12 crates (5 directly)

Apache-2.0/MIT

530KB
10K SLoC

dbus-codegen-rust

This program takes D-Bus XML Introspection data and generates Rust code for calling and implementing the interfaces in the introspection data.

Example

From a D-Bus interface like this:

<node>
    <interface name="org.example.test">
        <method name="Foo">
            <arg type="i" name="bar" direction="in"/>
            <arg type="s" name="baz" direction="out"/>
        </method>
        <signal name="Laundry">
            <arg type="b" name="eaten"/>
        </signal>
    </interface>
 </node>

You can choose to generate one of three things:

  • Client-side code (for calling the interface)
  • Server-side code for implementing the interface using dbus-crossroads
  • Server-side code for implementing the interface using dbus-tree

Common for client and server sides

  • A trait for calling/implementing the methods of the interfaces, like this:
pub trait OrgExampleTest {
    fn foo(&self, bar: i32) -> Result<String, dbus::Error>;
}
pub trait OrgExampleTest {
    fn foo(&self, bar: i32) -> Result<String, dbus::MethodErr>;
}

For properties, get_xx and set_xx methods will be generated. There is currently no get_all method.

  • A struct for each signal, like this:
#[derive(Debug, Default)]
pub struct OrgExampleTestLaundry {
    pub eaten: bool,
}

impl dbus::SignalArgs for OrgExampleTestLaundry { /* code here */ }

Client side

  • The trait will be implemented for blocking::Proxy, nonblock::Proxy or ffidisp::ConnPath, which makes methods easy to call for a client, like this:
use OrgExampleTest;
let myString = myProxy.foo(myInteger)?;
  • To catch signals emitted from the server, do like this:
use dbus::SignalArgs;
myConnection.add_match(OrgExampleTestLaundry::match_rule(None, None).into_static(), |laundrySignal| {
  println!("Laundry was eaten: {:?}", laundrySignal.eaten);
})

Server side - dbus-crossroads

  • A method will be generated that registers an IfaceToken, like this:
let token = register_org_example_test(&mut myCrossroads);
myCrossroads.insert("/", &[token], myData);

Where myData must be of a type that implements OrgExampleTest.

Server side - dbus-tree

  • A method will be generated, which you can call to get a tree::Interface, like this:
myInterface = orgexampletest_server(&myFactory, ());

This interface can then be added to a tree::ObjectPath, as shown in the main page.

In addition, you also need to implement the interface's methods, like this:

impl OrgExampleTest for MyStruct {
    type Err = tree::MethodErr;
    fn foo(&self, bar: i32) -> Result<String, Self::Err> {
        /* Your code here */
    }
}

I've been experimenting with different ways of how to make the generated server function reach the implementing struct, this is controlled by the command line parameter methodaccess.

  1. If methodaccess is MethodInfo, then you need to implement the interface for the MethodInfo struct, like this:
impl<M: tree::MethodType<D>, D> OrgExampleTest for tree::MethodInfo<M, D> {
    type Err = tree::MethodErr;
    fn foo(&self, bar: i32) -> Result<String, Self::Err> {
        /* Your code here */
    }
}
  1. If methodaccess is RefClosure, then you need to supply a closure that returns a reference to the implementing struct. This is a good option if the struct is stored in tree (this means implementing tree::DataType).
myInterface = orgexampletest_server(&myFactory, (), |m| m.path.get_data());
  1. If methodaccess is AsRefClosure, then you need to supply a closure that returns an object which can reference to the implementing struct. The object is dropped after the method is called. This works well with Arc/Rc, like this:
impl AsRef<dyn OrgExampleTest + 'static> for Rc<MyStruct> {
    fn as_ref(&self) -> &(dyn OrgExampleTest + 'static) { &**self }
}

let myRc = Rc::new(myStruct);
myInterface = orgexampletest_server(&myFactory, (), move |_| myRc.clone());

There is also a methodtype parameter that controls whether the server function will work well with MTFn, MTFnMut or MTSync trees, or all three (called Generic). Or not generate a server function at all (None).

  • To emit a signal, you can call SignalArgs::to_emit_message or ConnPath::emit to get a message which can be sent over the connection.

Usage

This code can be used both as a library and as a binary executable.

Binary executable

Once you have installed dbus-codegen-rust (cargo install dbus-codegen), use the following command to import your XML:

dbus-codegen-rust < mydefinition.xml

This will print the generated Rust code to stdout, so you can pipe it into another file if you want:

dbus-codegen-rust < mydefinition.xml > mod.rs

Dbus-codegen-rust can also fetch the xml definition for you. Here's an example that generates client definitions for PolicyKit:

dbus-codegen-rust -s -d org.freedesktop.PolicyKit1 -p "/org/freedesktop/PolicyKit1/Authority" > policykit.rs

Dbus-codegen-rust defaults to generating client definitions. Use the --crossroads switch to generate dbus-crossroads server definitions and --methodtype to generate dbus-tree definitions.

To see available options:

dbus-codegen-rust --help

Library usage

let opts = Default::default();
let code = dbus_codegen::generate(xml_str, &opts)?;

See documentation for what options are available.

Features

The dbus feature is enabled by default. If you turn it off (with the --no-default-features argument to cargo), this program (or library) no longer binds to the D-Bus C development headers, meaning you don't need these to be installed. This also means you can no longer fetch the xml definition from other programs when you run the binary.

Dependencies

~6MB
~126K SLoC