# D-Bus ###### tags: `DBus` [D-Bus](https://www.freedesktop.org/wiki/Software/dbus/) is: * a message bus system, a simple way for applications to talk to one another. * a system for interprocess communication (IPC). Architecturally, it has several layers: * A library, _libdbus_, that allows two applications to connect to each other and exchange messages. * A _message bus daemon_ executable, built on libdbus, that multiple applications can connect to. The daemon can route messages from one application to zero or more other applications. * Wrapper libraries or bindings based on particular application frameworks. For example, libdbus-glib and libdbus-qt. There are also bindings to languages such as Python. D-Bus supplies both: * System daemon (for events such as "new hardware device added" or "printer queue changed") * Per-user-login-session daemon (for general IPC needs among user applications) The message bus is built on top of a general **one-to-one message passing framework**, which can be used by any two apps to communicate directly (**without going through the message bus daemon**). Although, many applications use it with the message bus daemon. Currently the communicating applications are on one computer, or through unencrypted TCP/IP suitable for use behind a firewall with shared NFS home directories. The low-level [libdbus](https://gitlab.freedesktop.org/dbus/dbus) reference library has no required dependencies; the reference bus daemon's only required dependency is an XML parser ([expat](https://libexpat.github.io/)). Higher-level bindings specific to particular frameworks (Qt, GLib, Java, C#, Python, etc.) add more dependencies, but can make more assumptions and are thus much simpler to use. [Bindings Page](https://www.freedesktop.org/wiki/Software/DBusBindings/). libdbus only supports one-to-one connections, just like a raw network socket. ## [Valid Names](https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names) There is a maximum name length of **255** which applies to bus names, interfaces, and members. ### Interface names Interfaces have names with type STRING, meaning that they must be valid UTF-8. * Interface names are composed of 2 or more elements separated by a period ('.') character. All elements must contain at least one character. * Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_" and must not begin with a digit. * Interface names should start with the reversed DNS domain name of the author of the interface (in lower-case). For example, "com.example". There are the standard interfaces for each Object. #### [Standard Interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces) ##### org.freedesktop.DBus.Peer The `org.freedesktop.DBus.Peer` interface has two methods: * org.freedesktop.DBus.Peer.Ping () * org.freedesktop.DBus.Peer.GetMachineId (out STRING machine_uuid) ##### org.freedesktop.DBus.Introspectable This `org.freedesktop.DBus.Introspectable` has one method: * org.freedesktop.DBus.Introspectable.Introspect (out STRING xml_data) Objects instances may implement Introspect which returns an XML description of the object, including its interfaces (with signals and methods), objects below it in the object path tree, and its properties. The section [“Introspection Data Format”](https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format) describes the format of this XML string. ##### org.freedesktop.DBus.Properties Many native APIs will have a concept of object properties or attributes. These can be exposed via the `org.freedesktop.DBus.Properties` interface. * org.freedesktop.DBus.Properties.**Get (in STRING interface_name, in STRING property_name, out VARIANT value)** * org.freedesktop.DBus.Properties.**Set (in STRING interface_name, in STRING property_name, in VARIANT value)** * org.freedesktop.DBus.Properties.**GetAll (in STRING interface_name, out ARRAY of DICT_ENTRY<STRING,VARIANT> props)** If one or more properties change on an object, the org.freedesktop.DBus.Properties.PropertiesChanged signal may be emitted: * org.freedesktop.DBus.Properties.**PropertiesChanged (STRING interface_name, ARRAY of DICT_ENTRY<STRING,VARIANT> changed_properties, ARRAY<STRING> invalidated_properties)** where **changed_properties** is a **dictionary** containing the changed properties with the new values and **invalidated_properties** is an **array** of properties that changed but the value is not conveyed. ##### org.freedesktop.DBus.ObjectManager An API can optionally make use of this interface for one or more sub-trees of objects. The root of each sub-tree implements this interface so other applications can get all objects, interfaces and properties in a single method call. It is appropriate to use this interface if users of the tree of objects are expected to be interested in all interfaces of all objects in the tree; a more granular API should be used if users of the objects are expected to be interested in a small subset of the objects, a small subset of their interfaces, or both. The method that applications can use to get all objects and properties is GetManagedObjects: * org.freedesktop.DBus.ObjectManager.**GetManagedObjects (out ARRAY of DICT_ENTRY<OBJPATH,ARRAY of DICT_ENTRY<STRING,ARRAY of DICT_ENTRY<STRING,VARIANT>>> objpath_interfaces_and_properties)** The return value of this method is a dict whose keys are object paths. All returned object paths are children of the object path implementing this interface, i.e. their object paths start with the ObjectManager's object path plus '/'. Each value is a dict whose keys are interfaces names. Each value in this inner dict is the same dict that would be returned by the `org.freedesktop.DBus.Properties.GetAll()` method for that combination of object path and interface. If an interface has no properties, the empty dict is returned. Changes are emitted using the following two signals: * org.freedesktop.DBus.ObjectManager.**InterfacesAdded (OBJPATH object_path, ARRAY of DICT_ENTRY<STRING,ARRAY of DICT_ENTRY<STRING,VARIANT>> interfaces_and_properties)** * org.freedesktop.DBus.ObjectManager.**InterfacesRemoved (OBJPATH object_path, ARRAY<STRING> interfaces)** ### Bus names Connections have one or more bus names associated with them. **A connection has exactly one bus name that is a unique connection name**. The unique connection name remains with the connection for its entire lifetime. A bus name is of type STRING, meaning that it must be valid UTF-8. * Bus names that start with a colon (':') character are unique connection names. Other bus names are called _well-known bus names_. * Bus names are composed of 1 or more elements separated by a period ('.') character. All elements must contain at least one character. * Each element must only contain the ASCII characters "[A-Z][a-z][0-9]_-", with "-" discouraged in new bus names. Only elements that are part of a unique connection name may begin with a digit, elements in other bus names must not begin with a digit. * Bus names must contain at least one '.' (period) character (and thus at least two elements). * Bus names must not begin with a '.' (period) character. * Like interface names, well-known bus names should start with the reversed DNS domain name of the author of the interface (in lower-case). ### Member names Member (i.e. method or signal) names: * Must only contain the ASCII characters "[A-Z][a-z][0-9]_" and may not begin with a digit. * Must not contain the '.' (period) character. * Must be at least 1 byte in length. It is conventional for member names on D-Bus to consist of capitalized words with no punctuation ("camel-case"). * **Method** names should usually be verbs, such as `GetItems` * **Signal** names should usually be a description of an event, such as `ItemsChanged`. ### Error names Error names have the same restrictions as interface names. Error names have the same naming conventions as interface names, and often contain .Error.; for instance, the owner of `example.com` might define the errors `com.example.MusicPlayer1.Error.FileNotFound` and `com.example.MusicPlayer1.Error.OutOfMemory`. The errors defined by D-Bus itself, such as `org.freedesktop.DBus.Error.Failed`, follow a similar pattern. ## Message Types Each of the message types (`METHOD_CALL`, `METHOD_RETURN`, `ERROR`, and `SIGNAL`) has its own expected usage conventions and header fields. ### Method Calls, Method Return & ERROR ```sequence "D-Bus client"->"D-Bus server": METHOD_CALL "D-Bus server"-->"D-Bus client": METHOD_RETURN, or ERROR ``` Some messages invoke an operation on a remote object. These are called method call messages and have the type tag METHOD_CALL. Optionally, the message has an `INTERFACE` field giving the interface the method is a part of. Including the `INTERFACE` in all method call messages is strongly recommended. Method call messages also include a `PATH` field indicating the object to invoke the method on. If the call is passing through a message bus, the message will also have a DESTINATION field giving the name of the connection to receive the message. When an application handles a method call message, it is required to return a reply. The reply is identified by a REPLY_SERIAL header field indicating the serial number of the METHOD_CALL being replied to. The reply can have one of two types; either `METHOD_RETURN` or `ERROR`. * If the reply has type `METHOD_RETURN`, the arguments to the reply message are the return value(s) or "out parameters" of the method call. * If the reply has type `ERROR`, then an "exception" has been thrown, and the call fails; no return value will be provided. ### Signals ```sequence "D-Bus server"->"D-Bus client A, B, C ...": broadcast SIGNAL ``` A signal emission is simply a single message of type `SIGNAL`. It must have three header fields: `PATH` giving the object the signal was emitted from, plus `INTERFACE` and `MEMBER` giving the fully-qualified name of the signal. The `INTERFACE` header is required for signals, though it is optional for method calls. ## [Message Bus Specification](https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus) ### Message Bus Overview The message bus accepts connections from one or more applications. Once connected, applications can exchange messages with other applications that are also connected to the bus. In order to route messages among connections, the message bus keeps a mapping from names to connections. Each connection has one unique-for-the-lifetime-of-the-bus name automatically assigned. Applications may request additional names for a connection. Additional names are usually "well-known names" such as "com.example.TextEditor1". When a name is bound to a connection, that connection is said to own the name. The bus itself owns a special name, `org.freedesktop.DBus`, with an object located at `/org/freedesktop/DBus` that implements the `org.freedesktop.DBus` interface. This service allows applications to make administrative requests of the bus itself. For example, applications can ask the bus to assign a name to a connection. Each name may have queued owners. When an application requests a name for a connection and the name is already in use, the bus will optionally add the connection to a queue waiting for the name. If the current owner of the name disconnects or releases the name, the next connection in the queue will become the new owner. This feature causes the right thing to happen if you start two text editors for example; the first one may request "com.example.TextEditor1", and the second will be queued as a possible owner of that name. When the first exits, the second will take over. Applications may send unicast messages to a specific recipient or to the message bus itself, or broadcast messages to all interested recipients. ### Message Bus Names Each connection has at least one name, assigned at connection time and returned in response to the `org.freedesktop.DBus.Hello` method call. This automatically-assigned name is called the connection's unique name. Unique names are never reused for two different connections to the same bus. Ownership of a unique name is a prerequisite for interaction with the message bus. It logically follows that the unique name is always the first name that an application comes to own, and the last one that it loses ownership of. When a connection is closed, all the names that it owns are deleted (or transferred to the next connection in the queue if any). ### Message Bus Message Routing Messages may have a `DESTINATION` field, resulting in a _unicast message_. If the `DESTINATION` field is present, it specifies a message recipient by name. Method calls and replies normally specify this field. The message bus must send messages (of any type) with the `DESTINATION` field set to the specified recipient, regardless of whether the recipient has set up a match rule matching the message. When the message bus receives a **signal**, if the `DESTINATION` field is absent, it is considered to be a _broadcast signal_, and is sent to all applications with _message matching rules_ that match the message. Most signal messages are broadcasts, and no other message types currently defined in this specification may be broadcast. ### System message bus A computer may have a system message bus, accessible to all applications on the system. This message bus may be used to broadcast system events, such as adding new hardware devices, changes in the printer queue, and so forth. ## [D-Bus applications](https://dbus.freedesktop.org/doc/dbus-tutorial.html#uses) D-Bus is designed for two specific cases: * Communication between desktop applications in the same desktop session; to allow integration of the desktop session as a whole, and address issues of process lifecycle (when do desktop components start and stop running). * Communication between the desktop session and the operating system, where the operating system would typically include the kernel and any system daemons or processes. ### Bus Names When each application connects to the bus daemon, the daemon immediately assigns it a name, called the _unique connection name_. A unique name begins with a ':' (colon) character. These names are never reused during the lifetime of the bus daemon - that is, you know a given name will always refer to the same application. An example of a unique name might be :34-907. The numbers after the colon have no meaning other than their uniqueness. When a name is mapped to a particular application's connection, that application is said to own that name. Applications may ask to own additional well-known names. For example, you could write a specification to define a name called com.mycompany.TextEditor. Your definition could specify that to own this name, an application should have an object at the path /com/mycompany/TextFileManager supporting the interface org.freedesktop.FileHandler. Applications could then send messages to this bus name, object, and interface to execute method calls. You could think of the unique names as IP addresses, and the well-known names as domain names. So `com.mycompany.TextEditor` might map to something like `:34-907` just as `mycompany.com` maps to something like `192.168.0.5`. Names have a second important use, other than routing messages. They are used to track lifecycle. When an application exits (or crashes), its connection to the message bus will be closed by the operating system kernel. The message bus then sends out notification messages telling remaining applications that the application's names have lost their owner. By tracking these notifications, your application can reliably monitor the lifetime of other applications. Bus names can also be used to coordinate single-instance applications. If you want to be sure only one `com.mycompany.TextEditor` application is running for example, have the text editor application exit if the bus name already has an owner. ### Addresses Applications using D-Bus are either servers or clients. A server listens for incoming connections; a client connects to a server. Once the connection is established, it is a symmetric flow of messages; the client-server distinction only matters when setting up the connection. If you're using the bus daemon, as you probably are, your application will be a client of the bus daemon. That is, the bus daemon listens for connections and your application initiates a connection to the bus daemon. A D-Bus address specifies where a server will listen, and where a client will connect. For example, the address `unix:path=/tmp/abcdef` specifies that the server will listen on a UNIX domain socket at the path `/tmp/abcdef` and the client will connect to that socket. An address can also specify TCP/IP sockets, or any other transport defined in future iterations of the D-Bus specification. When using D-Bus with a message bus daemon, libdbus automatically discovers the address of the per-session bus daemon by reading an environment variable. It discovers the systemwide bus daemon by checking a well-known UNIX domain socket path (though you can override this address with an environment variable). ### [Big Conceptual Picture](https://dbus.freedesktop.org/doc/dbus-tutorial.html#bigpicture) Pulling all these concepts together, to specify a particular method call on a particular object instance, a number of nested components have to be named: **Address** -> **[Bus Name]** -> **Object Path** -> **Interface** -> **Method** ![D-Bus](https://hackmd.io/_uploads/HJqxgyvyA.svg) ### Messages - Behind the Scenes D-Bus works by sending messages between processes. There are 4 message types: * Method call messages ask to invoke a method on an object. * Method return messages return the results of invoking a method. * Error messages return an exception caused by invoking a method. * Signal messages are notifications that a given signal has been emitted (that an event has occurred). You could also think of these as "event" messages. ## Example ### [dbus-send](https://dbus.freedesktop.org/doc/dbus-send.1.html) asks Power Sources ```shell $ dbus-send --print-reply --system --dest=org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower.EnumerateDevices method return time=1710584709.846161 sender=:1.24 -> destination=:1.293 serial=982 reply_serial=2 array [ object path "/org/freedesktop/UPower/devices/battery_BAT0" object path "/org/freedesktop/UPower/devices/line_power_AC" object path "/org/freedesktop/UPower/devices/line_power_ucsi_source_psy_USBC000o001" ] $ dbus-send --print-reply --system --dest=org.freedesktop.UPower /org/freedesktop/UPower/devices/battery_BAT0 org.freedesktop.DBus.Properties.GetAll string:org.freedesktop.UPower.Device method return time=1710585125.085607 sender=:1.24 -> destination=:1.299 serial=1001 reply_serial=2 array [ dict entry( string "NativePath" variant string "BAT0" ) dict entry( string "Vendor" variant string "BYD" ) dict entry( string "Model" variant string "DELL WV3K832" ) dict entry( string "Serial" variant string "11861" ) dict entry( string "UpdateTime" variant uint64 1710585119 ) dict entry( string "Type" variant uint32 2 ) dict entry( string "PowerSupply" variant boolean true ) dict entry( string "HasHistory" variant boolean true ) dict entry( string "HasStatistics" variant boolean true ) dict entry( string "Online" variant boolean false ) dict entry( string "Energy" variant double 53.715 ) dict entry( string "EnergyEmpty" variant double 0 ) dict entry( string "EnergyFull" variant double 53.715 ) dict entry( string "EnergyFullDesign" variant double 54 ) dict entry( string "EnergyRate" variant double 0.015 ) dict entry( string "Voltage" variant double 16.306 ) dict entry( string "ChargeCycles" variant int32 3 ) dict entry( string "Luminosity" variant double 0 ) dict entry( string "TimeToEmpty" variant int64 12891600 ) dict entry( string "TimeToFull" variant int64 0 ) dict entry( string "Percentage" variant double 100 ) dict entry( string "Temperature" variant double 25.9 ) dict entry( string "IsPresent" variant boolean true ) dict entry( string "State" variant uint32 4 ) dict entry( string "IsRechargeable" variant boolean true ) dict entry( string "Capacity" variant double 99.4722 ) dict entry( string "Technology" variant uint32 0 ) dict entry( string "WarningLevel" variant uint32 1 ) dict entry( string "BatteryLevel" variant uint32 1 ) dict entry( string "IconName" variant string "battery-full-charged-symbolic" ) ] ``` ### [dbus-monitor](https://dbus.freedesktop.org/doc/dbus-monitor.1.html) Monitors (Un)Plugging USB Storage Signals ```shell $ sudo dbus-monitor --system "path='/org/freedesktop/UDisks2'" signal time=1711795576.609034 sender=org.freedesktop.DBus -> destination=:1.612 serial=4294967295 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired string ":1.612" signal time=1711795576.609065 sender=org.freedesktop.DBus -> destination=:1.612 serial=4294967295 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameLost string ":1.612" signal time=1711795582.184807 sender=:1.62 -> destination=(null destination) serial=205 path=/org/freedesktop/UDisks2; interface=org.freedesktop.DBus.ObjectManager; member=InterfacesAdded object path "/org/freedesktop/UDisks2/drives/General_UDisk_General_UDisk_0_3a0" array [ dict entry( string "org.freedesktop.UDisks2.Drive" array [ dict entry( string "Vendor" variant string "General" ) dict entry( string "Model" variant string "UDisk" ) dict entry( string "Revision" variant string "5.00" ) dict entry( string "Serial" variant string "General_UDisk-0:0" ... ``` ### D-Bus Simple Server & Client Application * D-Bus server with [GDBus](https://docs.gtk.org/gio/): [gdbus-server](https://github.com/starnight/gdbus-server) * Bus Name: `io.starnight.dbus_test.TestServer` * Object Path: `/io/starnight/dbus_test/TestObject` * Interface Name: `io.starnight.dbus_test.TestInterface` ```sh $ dbus-send --session --print-reply \ --type=method_call \ --dest=io.starnight.dbus_test.TestServer \ /io/starnight/dbus_test/TestObject \ org.freedesktop.DBus.Introspectable.Introspect method return time=1711180494.668633 sender=:1.188 -> destination=:1.189 serial=3 reply_serial=2 string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> <!-- GDBus 2.78.4 --> <node> <interface name="org.freedesktop.DBus.Properties"> <method name="Get"> <arg type="s" name="interface_name" direction="in"/> <arg type="s" name="property_name" direction="in"/> <arg type="v" name="value" direction="out"/> </method> <method name="GetAll"> <arg type="s" name="interface_name" direction="in"/> <arg type="a{sv}" name="properties" direction="out"/> </method> <method name="Set"> <arg type="s" name="interface_name" direction="in"/> <arg type="s" name="property_name" direction="in"/> <arg type="v" name="value" direction="in"/> </method> <signal name="PropertiesChanged"> <arg type="s" name="interface_name"/> <arg type="a{sv}" name="changed_properties"/> <arg type="as" name="invalidated_properties"/> </signal> </interface> <interface name="org.freedesktop.DBus.Introspectable"> <method name="Introspect"> <arg type="s" name="xml_data" direction="out"/> </method> </interface> <interface name="org.freedesktop.DBus.Peer"> <method name="Ping"/> <method name="GetMachineId"> <arg type="s" name="machine_uuid" direction="out"/> </method> </interface> <interface name="io.starnight.dbus_test.TestInterface"> <method name="Login"> <arg type="s" name="greeting" direction="in"> </arg> <arg type="s" name="response" direction="out"> </arg> </method> <method name="SendMsg"> <arg type="s" name="msg" direction="in"> </arg> </method> <signal name="MsgNotification"> <arg type="s" name="sender"> </arg> <arg type="s" name="msg"> </arg> </signal> <property type="s" name="Title" access="readwrite"> </property> </interface> </node> " ``` * D-Bus client with [GDBus](https://docs.gtk.org/gio/): [gdbus-client](https://github.com/starnight/gdbus-client) * D-Bus client with [pydbus](https://github.com/LEW21/pydbus): [pydbus-client](https://github.com/starnight/pydbus-client) #### Debug Tools ##### [dbus-monitor](https://dbus.freedesktop.org/doc/dbus-monitor.1.html) ```shell $ dbus-monitor --session "path='/io/starnight/dbus_test/TestObject'" signal time=1711295334.594089 sender=org.freedesktop.DBus -> destination=:1.207 serial=4294967295 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameAcquired string ":1.207" signal time=1711295334.594132 sender=org.freedesktop.DBus -> destination=:1.207 serial=4294967295 path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameLost string ":1.207" method call time=1711295345.911629 sender=:1.210 -> destination=:1.206 serial=36 path=/io/starnight/dbus_test/TestObject; interface=org.freedesktop.DBus.Properties; member=GetAll string "io.starnight.dbus_test.TestInterface" method call time=1711295355.430745 sender=:1.210 -> destination=:1.206 serial=45 path=/io/starnight/dbus_test/TestObject; interface=io.starnight.dbus_test.TestInterface; member=SendMsg string "Hello world!" signal time=1711295355.431750 sender=:1.206 -> destination=(null destination) serial=4 path=/io/starnight/dbus_test/TestObject; interface=io.starnight.dbus_test.TestInterface; member=MsgNotification string ":1.210" string "Hello world!" ``` ##### [Bustle](https://gitlab.gnome.org/World/bustle) ![image](https://hackmd.io/_uploads/rJLZ3pTAT.png) ## Reference * [Python DBus 教學精要](https://www.rocksaying.tw/archives/15534603.html) * [D-Bus Tutorial](https://www.softprayog.in/programming/d-bus-tutorial) * [D-Bus API Design Guidelines](https://dbus.freedesktop.org/doc/dbus-api-design.html)