Document not found (404)
-This URL is invalid, sorry. Please use the navigation bar or search to continue.
- -diff --git a/1.0/404.html b/1.0/404.html deleted file mode 100644 index 52a43decb..000000000 --- a/1.0/404.html +++ /dev/null @@ -1,221 +0,0 @@ - - -
- - -This URL is invalid, sorry. Please use the navigation bar or search to continue.
- ---Note
-This version of the book is based on older zbus 1.0 API. The 2.0 version of this book is available -here.
-
In this chapter, we are going to see how to make low-level D-Bus method calls. Then we are going to -dive in, and derive from a trait to make a convenient Rust binding. Finally, we will learn about -xmlgen, a tool to help us generate a boilerplate trait from the XML of an introspected service.
-To make this learning “hands-on”, we are going to call and bind the cross-desktop notification -service (please refer to this -reference -document for further details on this API).
-Let’s start by playing with the service from the shell, and notify the desktop with busctl
1:
busctl --user call \
- org.freedesktop.Notifications \
- /org/freedesktop/Notifications \
- org.freedesktop.Notifications \
- Notify \
- susssasa\{sv\}i \
- "my-app" 0 "dialog-information" "A summary" "Some body" 0 0 5000
-
-Note: busctl
has very good auto-completion support in bash or zsh.
Running this command should pop-up a notification dialog on your desktop. If it does not, your -desktop does not support the notification service, and this example will be less interactive. -Nonetheless you can use a similar approach for other services.
-This command shows us several aspects of the D-Bus communication:
---user
: Connect to and use the user/session bus.
call
: Send a method call message. (D-Bus also supports signals, error messages, and method
-replies)
destination: The name of the service (org.freedesktop.Notifications
).
object path: Object/interface path (/org/freedesktop/Notifications
).
interface: The interface name (methods are organized in interfaces, here
-org.freedesktop.Notifications
, same name as the service).
method: The name of the method to call, Notify
.
signature: That susssasa{sv}i
means the method takes 8 arguments of various types. ‘s’, for
-example, is for a string. ‘as’ is for array of strings.
The method arguments.
-See busctl
man page for more details.
zbus::Connection
zbus Connection
has a call_method()
method, which you can use directly:
-use std::collections::HashMap; -use std::error::Error; - -use zbus::Connection; -use zvariant::Value; - -fn main() -> Result<(), Box<dyn Error>> { - let connection = Connection::new_session()?; - - let m = connection.call_method( - Some("org.freedesktop.Notifications"), - "/org/freedesktop/Notifications", - Some("org.freedesktop.Notifications"), - "Notify", - &("my-app", 0u32, "dialog-information", "A summary", "Some body", - vec![""; 0], HashMap::<&str, &Value>::new(), 5000), - )?; - let reply: u32 = m.body().unwrap(); - dbg!(reply); - Ok(()) -}
Although this is already quite flexible, and handles various details for you (such as the message
-signature), it is also somewhat inconvenient and error-prone: you can easily miss arguments, or give
-arguments with the wrong type or other kind of errors (what would happen if you typed 0
, instead
-of 0u32
?).
Instead, we want to wrap this Notify
D-Bus method in a Rust function. Let’s see how next.
A trait declaration T
with a dbus_proxy
attribute will have a derived TProxy
implemented
-thanks to procedural macros. The trait methods will have respective impl
methods wrapping the
-D-Bus calls:
-use std::collections::HashMap; -use std::error::Error; - -use zbus::dbus_proxy; -use zvariant::Value; - -#[dbus_proxy] -trait Notifications { - /// Call the org.freedesktop.Notifications.Notify D-Bus method - fn notify(&self, - app_name: &str, - replaces_id: u32, - app_icon: &str, - summary: &str, - body: &str, - actions: &[&str], - hints: HashMap<&str, &Value>, - expire_timeout: i32) -> zbus::Result<u32>; -} - -fn main() -> Result<(), Box<dyn Error>> { - let connection = zbus::Connection::new_session()?; - - let proxy = NotificationsProxy::new(&connection)?; - let reply = proxy.notify("my-app", 0, "dialog-information", "A summary", "Some body", - &[], HashMap::new(), 5000)?; - dbg!(reply); - - Ok(()) -}
A TProxy
has a few associated methods, such as new(connection)
, using the default associated
-service name and object path, and new_for(connection, service_name, object_path)
if you need to
-specify something different.
This should help to avoid the kind of mistakes we saw earlier. It’s also a bit easier to use, thanks -to Rust type inference. This makes it also possible to have higher-level types, they fit more -naturally with the rest of the code. You can further document the D-Bus API or provide additional -helpers.
-Interfaces can have associated properties, which can be read or set with the
-org.freedesktop.DBus.Properties
interface. Here again, the #[dbus_proxy]
attribute comes to the
-rescue to help you. You can annotate a trait method to be a getter:
-#![allow(unused)] -fn main() { -use zbus::{dbus_proxy, Result}; - -#[dbus_proxy] -trait MyInterface { - #[dbus_proxy(property)] - fn state(&self) -> Result<String>; -} -}
The state()
method will translate to a "State"
property Get
call.
To set the property, prefix the name of the property with set_
.
For a more real world example, let’s try and read two properties from systemd’s main service:
--use std::error::Error; -use zbus::dbus_proxy; - -#[dbus_proxy( - interface = "org.freedesktop.systemd1.Manager", - default_service = "org.freedesktop.systemd1", - default_path = "/org/freedesktop/systemd1" -)] -trait SystemdManager { - #[dbus_proxy(property)] - fn architecture(&self) -> zbus::Result<String>; - #[dbus_proxy(property)] - fn environment(&self) -> zbus::Result<Vec<String>>; -} - -fn main() -> Result<(), Box<dyn Error>> { - let connection = zbus::Connection::new_session()?; - - let proxy = SystemdManagerProxy::new(&connection)?; - println!("Host architecture: {}", proxy.architecture()?); - println!("Environment:"); - for env in proxy.environment()? { - println!(" {}", env); - } - - Ok(()) -}
You should get an output similar to this:
-Host architecture: x86-64
-Environment variables:
- HOME=/home/zeenix
- LANG=en_US.UTF-8
- LC_ADDRESS=de_DE.UTF-8
- LC_IDENTIFICATION=de_DE.UTF-8
- LC_MEASUREMENT=de_DE.UTF-8
- LC_MONETARY=de_DE.UTF-8
- LC_NAME=de_DE.UTF-8
- LC_NUMERIC=de_DE.UTF-8
- LC_PAPER=de_DE.UTF-8
- LC_TELEPHONE=de_DE.UTF-8
- LC_TIME=de_DE.UTF-8
- ...
-
-Signals are like methods, except they don’t expect a reply. They are typically emitted by services -to notify interested peers of any changes to the state of the service. zbus provides you with an API -to register signal handler functions, and to receive and call them.
-Let’s look at this API in action, with an example where we get our location from -Geoclue:
--#![allow(unused)] -fn main() { -use zbus::{Connection, dbus_proxy, Result}; -use zvariant::{ObjectPath, OwnedObjectPath}; - -#[dbus_proxy( - default_service = "org.freedesktop.GeoClue2", - interface = "org.freedesktop.GeoClue2.Manager", - default_path = "/org/freedesktop/GeoClue2/Manager" -)] -trait Manager { - fn get_client(&self) -> Result<OwnedObjectPath>; -} - -#[dbus_proxy( - default_service = "org.freedesktop.GeoClue2", - interface = "org.freedesktop.GeoClue2.Client" -)] -trait Client { - fn start(&self) -> Result<()>; - fn stop(&self) -> Result<()>; - - #[dbus_proxy(property)] - fn set_desktop_id(&mut self, id: &str) -> Result<()>; - - #[dbus_proxy(signal)] - fn location_updated(&self, old: ObjectPath<'_>, new: ObjectPath<'_>) -> Result<()>; -} - -#[dbus_proxy( - default_service = "org.freedesktop.GeoClue2", - interface = "org.freedesktop.GeoClue2.Location" -)] -trait Location { - #[dbus_proxy(property)] - fn latitude(&self) -> Result<f64>; - #[dbus_proxy(property)] - fn longitude(&self) -> Result<f64>; -} -let conn = Connection::new_system().unwrap(); -let manager = ManagerProxy::new(&conn).unwrap(); -let client_path = manager.get_client().unwrap(); -let mut client = ClientProxy::new_for_path(&conn, &client_path).unwrap(); -// Gotta do this, sorry! -client.set_desktop_id("org.freedesktop.zbus").unwrap(); - -client - .connect_location_updated(move |_old, new| { - let location = LocationProxy::new_for_path(&conn, &new); - println!( - "Latitude: {}\nLongitude: {}", - location.latitude()?, - location.longitude()?, - ); - - Ok(()) - }) - .unwrap(); - -client.start().unwrap(); - -// Wait till there is a signal that was handled. -while client.next_signal().unwrap().is_some() {} -}
While the Geoclue’s D-Bus API is a bit involved, we still ended-up with a not-so-complicated (~60 -LOC) code for getting our location. As you may’ve notice, we use a blocking call to wait for a -signal on one proxy. This works fine but in the real world, you would typically have many proxies -and you’d want to wait for signals from them all at once. Not to worry, zbus provides a way to wait -on multiple proxies at once as well.
-Let’s make use of SignalReceiver
and zbus::fdo
API to make sure the client is actually started
-by watching for Active
property (that we must set to be able to get location from Geoclue)
-actually getting set:
-#![allow(unused)] -fn main() { -use zbus::{Connection, dbus_proxy, Result}; -use zvariant::{ObjectPath, OwnedObjectPath}; - -#[dbus_proxy( - default_service = "org.freedesktop.GeoClue2", - interface = "org.freedesktop.GeoClue2.Manager", - default_path = "/org/freedesktop/GeoClue2/Manager" -)] -trait Manager { - fn get_client(&self) -> Result<OwnedObjectPath>; -} - -#[dbus_proxy(interface = "org.freedesktop.GeoClue2.Client")] -trait Client { - fn start(&self) -> Result<()>; - - #[dbus_proxy(property)] - fn set_desktop_id(&mut self, id: &str) -> Result<()>; - - #[dbus_proxy(signal)] - fn location_updated(&self, old: ObjectPath, new: ObjectPath) -> Result<()>; -} - -#[dbus_proxy( - default_service = "org.freedesktop.GeoClue2", - interface = "org.freedesktop.GeoClue2.Location" -)] -trait Location { - #[dbus_proxy(property)] - fn latitude(&self) -> Result<f64>; - #[dbus_proxy(property)] - fn longitude(&self) -> Result<f64>; -} - -let conn = Connection::new_system().unwrap(); -let manager = ManagerProxy::new(&conn).unwrap(); -let client_path = manager.get_client().unwrap(); -let mut client = - ClientProxy::new_for(&conn, "org.freedesktop.GeoClue2", &client_path); -// Gotta do this, sorry! -client.set_desktop_id("org.freedesktop.zbus").unwrap(); - -// Everything else remains the same before this point. - -let conn_clone = conn.clone(); -client.connect_location_updated(move |_old, new| { - let location = LocationProxy::new_for( - &conn_clone, - "org.freedesktop.GeoClue2", - &new, - )?; - println!( - "Latitude: {}\nLongitude: {}", - location.latitude()?, - location.longitude()?, - ); - - Ok(()) -}).unwrap(); - -let props = zbus::fdo::PropertiesProxy::new_for( - &conn, - "org.freedesktop.GeoClue2", - &client_path, -).unwrap(); -props.connect_properties_changed(|iface, changed, _| { - for (name, value) in changed.iter() { - println!("{}.{} changed to `{:?}`", iface, name, value); - } - - Ok(()) -}).unwrap(); - -let mut receiver = zbus::SignalReceiver::new(conn); -receiver.receive_for(&client); -receiver.receive_for(&props); - -client.start().unwrap(); - -// 3 signals will be emitted, that we handle -let mut num_handled = 0; -while num_handled < 3 { - if receiver.next_signal().unwrap().is_none() { - num_handled += 1; - } -} -}
The zbus_xmlgen
crate provides a developer-friendly tool, that can generate Rust traits from a
-given D-Bus introspection XML for you.
Note: This tool should not be considered a drop-in Rust-specific replacement for similar tools
-available for low-level languages, such as gdbus-codegen
. Unlike those tools, this is only meant
-as a starting point to generate the code, once. In many cases, you will want to tweak the generated
-code.
The tool can be used to generate rust code directly from a D-Bus service running on our system:
-zbus-xmlgen --session \
- org.freedesktop.Notifications \
- /org/freedesktop/Notifications
-
-Alternatively you can also get the XML interface from a different source and use it to generate the
-interface code. Some packages may also provide the XML directly as an installed file, allowing it to
-be queried using pkg-config
, for example.
We can fetch the XML interface of the notification service, using the --xml-interface
option of
-the busctl
1 command. This option was introduced to busctl
in systemd v243.
busctl --user --xml-interface introspect \
- org.freedesktop.Notifications \
- /org/freedesktop/Notifications
-
-You should get a similar output:
-<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
- "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
-<node>
- <!-- other interfaces omitted -->
- <interface name="org.freedesktop.Notifications">
- <method name="Notify">
- <arg type="s" name="arg_0" direction="in">
- </arg>
- <arg type="u" name="arg_1" direction="in">
- </arg>
- <arg type="s" name="arg_2" direction="in">
- </arg>
- <arg type="s" name="arg_3" direction="in">
- </arg>
- <arg type="s" name="arg_4" direction="in">
- </arg>
- <arg type="as" name="arg_5" direction="in">
- </arg>
- <arg type="a{sv}" name="arg_6" direction="in">
- </arg>
- <arg type="i" name="arg_7" direction="in">
- </arg>
- <arg type="u" name="arg_8" direction="out">
- </arg>
- </method>
- <method name="CloseNotification">
- <arg type="u" name="arg_0" direction="in">
- </arg>
- </method>
- <method name="GetCapabilities">
- <arg type="as" name="arg_0" direction="out">
- </arg>
- </method>
- <method name="GetServerInformation">
- <arg type="s" name="arg_0" direction="out">
- </arg>
- <arg type="s" name="arg_1" direction="out">
- </arg>
- <arg type="s" name="arg_2" direction="out">
- </arg>
- <arg type="s" name="arg_3" direction="out">
- </arg>
- </method>
- <signal name="NotificationClosed">
- <arg type="u" name="arg_0">
- </arg>
- <arg type="u" name="arg_1">
- </arg>
- </signal>
- <signal name="ActionInvoked">
- <arg type="u" name="arg_0">
- </arg>
- <arg type="s" name="arg_1">
- </arg>
- </signal>
- </interface>
-</node>
-
-Save the output to a notify.xml
file. Then call:
zbus-xmlgen notify.xml
-
-This will give back effortlessly the corresponding Rust traits boilerplate -code:
--#![allow(unused)] -fn main() { -use zbus::dbus_proxy; - -#[dbus_proxy(interface = "org.freedesktop.Notifications")] -trait Notifications { - /// CloseNotification method - fn close_notification(&self, arg_0: u32) -> zbus::Result<()>; - - /// GetCapabilities method - fn get_capabilities(&self) -> zbus::Result<Vec<String>>; - - /// GetServerInformation method - fn get_server_information(&self) -> zbus::Result<(String, String, String, String)>; - - /// Notify method - fn notify( - &self, - arg_0: &str, - arg_1: u32, - arg_2: &str, - arg_3: &str, - arg_4: &str, - arg_5: &[&str], - arg_6: std::collections::HashMap<&str, zvariant::Value>, - arg_7: i32, - ) -> zbus::Result<u32>; - - /// ActionInvoked signal - #[dbus_proxy(signal)] - fn action_invoked(&self, arg_0: u32, arg_1: &str) -> zbus::Result<()>; - - /// NotificationClosed signal - #[dbus_proxy(signal)] - fn notification_closed(&self, arg_0: u32, arg_1: u32) -> zbus::Result<()>; -} -}
It should be usable as such. But you may as well improve a bit the naming of the arguments, use
-better types (using BitFlags
, structs or other custom types), add extra documentation, and other
-functions to make the binding more pleasing to use from Rust.
For example, the generated GetServerInformation
method can be improved to a nicer version:
-#![allow(unused)] -fn main() { -use serde::{Serialize, Deserialize}; -use zvariant::derive::Type; -use zbus::dbus_proxy; - -#[derive(Debug, Type, Serialize, Deserialize)] -pub struct ServerInformation { - /// The product name of the server. - pub name: String, - - /// The vendor name. For example "KDE," "GNOME," "freedesktop.org" or "Microsoft". - pub vendor: String, - - /// The server's version number. - pub version: String, - - /// The specification version the server is compliant with. - pub spec_version: String, -} - -trait Notifications { - /// Get server information. - /// - /// This message returns the information on the server. - fn get_server_information(&self) -> zbus::Result<ServerInformation>; -} -}
You can learn more from the zbus-ify binding of -PolicyKit, for example, which was -implemented starting from the xmlgen output.
-There you have it, a Rust-friendly binding for your D-Bus service!
-busctl
is part of systemd
.
--Note
-This version of the book is based on older zbus 1.0 API. The 2.0 version of this book is available -here.
-
A D-Bus “bus” is a kind of server that handles several connections in a bus-topology fashion. As -such, it relays messages between connected endpoints, and allows to discover endpoints or sending -broadcast messages (signals).
-Typically, a Linux system has a system bus, and a session bus. The latter is per-user. It is also -possible to have private buses or no bus at all (i-e direct peer-to-peer communication instead).
-An endpoint can have various names, which allows to address messages to it on the bus. All endpoints -are assigned a unique name by the bus at start. Since this name is not static, most services use -something called a well-known bus name and typically it’s this name, that you’ll be concerned -with.
-An example would be the FreeDesktop Notifications Service that uses
-org.freedesktop.Notifications
as its well-known bus name.
For further details on bus names, please refer to the Bus names chapter of the D-Bus specification.
-An object is akin to the concept of an object or an instance in many programming languages. All -services expose at least one object on the bus and all clients interact with the service through -these objects. These objects can be ephemeral or they could live as long as the service itself.
-Every object is identified by a string, which is referred to as its path. An example of an object
-path is /org/freedesktop/Notifications
, which identities the only object exposed by the
-FreeDesktop Notifications Service.
For further details on object paths, please refer to the Basic types chapter of the D-Bus -specification.
-An interface defines the API exposed by object on the bus. They are akin to the concept of -interfaces in many programming languages and traits in Rust. Each object can (and typically do) -provide multiple interfaces at the same time. A D-Bus interface can have methods, properties and -signals.
-While each interface of a service is identified by a unique name, its API is described by an XML -description. It is mostly a machine-level detail. Most services can be queried for this description -through a D-Bus standard introspection interface.
-zbus provides convenient macro that implements the introspection interface for services, and helper -to generate client-side Rust API, given an XML description. We’ll see both of these in action in the -following chapters.
-It is recommended to organise the service name, object paths and interface name by using -fully-qualified domain names, in order to avoid potential conflicts.
-Please read the D-Bus API Design Guidelines carefully for other similar considerations.
-At the moment, we only provide low-level asynchronous API but high-level asynchronous API will -hopefully be added soon. It’s very high on our priority.
-Onwards to implementation details & examples!
- ---Note
-This version of the book is based on older zbus 1.0 API. The 2.0 version of this book is available -here.
-
The first thing you will have to do is to connect to a D-Bus bus or to a D-Bus peer. This is the -entry point of the zbus API.
-To connect to the session bus (the per-user bus), simply call Connection::new_session()
. It
-returns an instance of the connection (if all went well).
Similarly, to connect to the system bus (to communicate with services such as NetworkManager,
-BlueZ or PID1), use Connection::new_system()
.
Note: it is common for a D-Bus library to provide a “shared” connection to a bus for a process:
-all new_session()
share the same underlying connection for example. At the time of this writing,
-zbus doesn’t do that.
You may also specify a custom bus with Connection::new_for_address()
which takes a D-Bus address
-as specified in the
-specification.
Peer-to-peer connections are bus-less1, and the initial handshake protocol is a bit -different. There is the notion of client & server endpoints, but that distinction doesn’t matter -once the connection is established (both ends are equal, and can send any messages).
-To create a bus-less peer-to-peer connection on Unix, you can make a socketpair()
(or have a
-listening socket server, accepting multiple connections), and hand over the socket FDs to
-Connection::new_unix_server
and Connection::new_unix_client
for each side. After success, you
-can call the Connection
methods to send and receive messages on both ends.
See the unix_p2p
test in the zbus source code for a simple example.
1 Unless you implemented them, none of the bus methods will exist.
- ---Note
-This version of the book is based on older zbus 1.0 API. The 2.0 version of this book is available -here.
-
Here is a list of the contributors who have helped improving zbus. Big shout-out to them!
- -If you feel you’re missing from this list, feel free to add yourself in a PR.
- -
-
-
--Note
-This version of the book is based on older zbus 1.0 API. The 2.0 version of this book is available -here.
-
zbus is a Rust crate for D-Bus. If you are not familiar with D-Bus, you should -read what is D-Bus? first1. In short, zbus allows you to communicate from one program -to another, using the D-Bus protocol. In other words, it’s an inter-process communication (IPC) -solution. It is a very popular IPC solution on Linux and many Linux services (e.g systemd, -NetworkManager) and desktop environments (e.g GNOME and KDE), rely on D-Bus for their IPC needs. -There are many tools and implementations available, making it easy to interact with D-Bus programs -from different languages and environments.
-zbus is a 100% Rust-native implementation of the D-Bus protocol. It provides both an API to send -and receive messages over a connection, as well as API to interact with peers through high-level -concepts like method calls, signals and properties2. Thanks to the power of Rust -macros, zbus is able to make interacting with D-Bus very easy.
-zbus project provides two crates:
-D-Bus defines a marshalling format for its messages. The zvariant crate provides a serde-based -API to serialize/deserialize Rust data types to/from this format. Outside of D-Bus context, a -modified form of this format, GVariant -is very commonly used for efficient storage of arbitrary data and is also supported by this crate.
-The zbus crate provides the main API you will use to interact with D-Bus from Rust. It takes care -of the establishment of a connection, the creation, sending and receiving of different kind of D-Bus -messages (method calls, signals etc) for you.
-zbus crate is currently Linux-specific3.
-D-Bus is ~15y old, unfortunately many documents out there are -sometime aging or misleading.
-These concepts are explained in the -following chapter.
-Support for other OS exist, but it is not supported to the same -extent. D-Bus clients in javascript (running from any browser) do exist -though. And zbus may also be working from the browser sometime in the future -too, thanks to Rust 🦀 and WebAssembly 🕸.
-
-
-