D-Bus - System integration | vasak-desktop

Complete guide to D-Bus and how Vasak Desktop integrates with the Linux system.

What is D-Bus?#

D-Bus (Desktop Bus) is the standard inter-process communication (IPC) system in Linux.

Purpose: It lets applications and system services talk to each other in a standardized way.

Example:

  • PulseAudio exposes audio services through D-Bus
  • NetworkManager exposes network services
  • UPower exposes battery information
  • Freedesktop exposes notifications

D-Bus Architecture#

graph TB Daemon["D-Bus Daemon
(dbus-daemon)"] App1["App 1"] DBusSrv["D-Bus
Service"] Service["Service"] Daemon --> App1 Daemon --> DBusSrv Daemon --> Service style Daemon fill:#ffe0b2 style App1 fill:#c8e6c9 style DBusSrv fill:#c8e6c9 style Service fill:#c8e6c9

Bus Types#

  1. Session Bus - Per-user communication

    • Runs as dbus-daemon --session
    • Usually already running
    • Location: unix:abstract=/tmp/dbus-XXXXXX
  2. System Bus - System-wide communication

    • Runs as root
    • For privileged operations
    • Location: unix:/var/run/dbus/system_bus_socket

Vasak Desktop mainly uses the Session Bus.

Key Concepts#

Service Name (Bus Name)#

Unique identifier for a service:

1
2
3
org.freedesktop.AudioManager
org.freedesktop.NetworkManager
org.freedesktop.DBus.Properties

Format: org.domain.interface

Object Path#

Location of the object within the service:

1
2
/org/freedesktop/NetworkManager
/org/freedesktop/NetworkManager/ActiveConnection/0

Hierarchical format, similar to file paths.

Interface#

Defines an object’s methods, signals and properties:

1
2
org.freedesktop.NetworkManager.Device
org.freedesktop.DBus.Properties

Methods#

Functions that can be called:

1
2
interface: org.freedesktop.NetworkManager
method: Activate(objpath: in, objpath: in) -> (objpath: out)

Signals#

Events that can be listened to:

1
2
signal: StateChanged(uint32: state)
signal: PropertiesChanged(dict: properties)

Properties#

Values that can be read/written:

1
2
property: State (read) -> uint32
property: Connectivity (read) -> uint32

D-Bus in Vasak Desktop#

D-Bus Module#

Location: src-tauri/src/dbus_service.rs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Example D-Bus connection
use zbus::Connection;

pub struct DbusService {
    connection: Connection,
}

impl DbusService {
    pub async fn new() -> Result<Self> {
        let connection = Connection::session().await?;
        Ok(DbusService { connection })
    }
}

Services Used#

Audio (PulseAudio / PipeWire)#

Service: org.pulseaudio.Server or org.PipeWire.Core1

Functionality:

  • Get audio devices
  • Change the volume
  • Switch the audio input/output
  • Mute

Code: src-tauri/src/audio.rs

Bluetooth#

Service: org.bluez

Paths:

  • /org/bluez/hci0 - Adapter
  • /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX - Device

Functionality:

  • Scan for devices
  • Pair devices
  • Connect/Disconnect
  • Read properties

Code: src-tauri/src/bluetooth.rs

Network (NetworkManager)#

Service: org.freedesktop.NetworkManager

Paths:

  • /org/freedesktop/NetworkManager - Main manager
  • /org/freedesktop/NetworkManager/Device/0 - Network device
  • /org/freedesktop/NetworkManager/ActiveConnection/0 - Active connection

Functionality:

  • List network devices
  • List WiFi connections
  • Connect to WiFi
  • Get connection properties

Code: src-tauri/src/network.rs

Notifications (Freedesktop)#

Service: org.freedesktop.Notifications

Path: /org/freedesktop/Notifications

Functionality:

  • Show notifications
  • Close notifications
  • Listen to user actions

Code: src-tauri/src/notifications.rs

Power (UPower)#

Service: org.freedesktop.UPower

Functionality:

  • Battery information
  • AC adapter information

Code: Partially spread across several modules

D-Bus Debugging Tools#

busctl - Command line tool#

1
2
3
4
5
6
7
8
9
# List the services on the session bus
busctl list --user

# See the interfaces of a service
busctl introspect --user org.freedesktop.NetworkManager /org/freedesktop/NetworkManager

# Call a method
busctl call --user org.freedesktop.DBus /org/freedesktop/DBus \
  org.freedesktop.DBus ListNames

dbus-send - Send D-Bus messages#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Get the current volume
dbus-send --print-reply --system \
  /org/pulseaudio/core1 \
  org.freedesktop.DBus.Properties.Get \
  string:'org.PulseAudio.Core1' \
  string:'Volume'

# Change the volume
dbus-send --system /org/pulseaudio/core1 \
  org.PulseAudio.Core1.SetVolume \
  uint32:50000

dbus-monitor - D-Bus monitor#

1
2
3
4
5
6
7
8
9
# Monitor all messages
dbus-monitor --session

# Monitor only NetworkManager messages
dbus-monitor --session \
  "interface='org.freedesktop.NetworkManager'"

# Monitor only signals
dbus-monitor --session type='signal'

gdbus - GNOME D-Bus client#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# List services
gdbus call --session \
  --dest org.freedesktop.DBus \
  --object-path /org/freedesktop/DBus \
  --method org.freedesktop.DBus.ListNames

# Spy on properties
gdbus introspect --session \
  --dest org.freedesktop.NetworkManager \
  --object-path /org/freedesktop/NetworkManager

Implementing a D-Bus Command#

Example: Getting the Audio Volume#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src-tauri/src/commands/audio.rs

use zbus::Connection;

#[tauri::command]
pub async fn get_volume() -> Result<u32, String> {
    // Connect to the session bus
    let connection = Connection::session()
        .await
        .map_err(|e| format!("Failed to connect to D-Bus: {}", e))?;
    
    // Get a proxy for the service
    let proxy = connection
        .call_method(
            Some("org.pulseaudio.Server"),           // Service
            "/org/pulseaudio/core1",                 // Path
            Some("org.freedesktop.DBus.Properties"), // Interface
            "Get",                                   // Method
            &("org.PulseAudio.Core1", "Volume"),    // Parameters
        )
        .await
        .map_err(|e| format!("D-Bus call failed: {}", e))?;
    
    Ok(volume)
}

Example: Listening to D-Bus Signals#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Listen for volume changes

use zbus::MessageStream;

pub async fn listen_volume_changes() -> Result<(), Box<dyn std::error::Error>> {
    let connection = Connection::session().await?;
    
    // Create a message stream
    let mut stream = MessageStream::from(connection.clone());
    
    // Filter by signal
    while let Some(msg) = stream.next().await {
        match msg {
            zbus::Message::Signal(signal) => {
                if signal.interface() == Some(&"org.PulseAudio.Core1".into()) {
                    println!("Volume changed");
                }
            }
            _ => {}
        }
    }
    
    Ok(())
}

Common D-Bus Services#

NetworkManager#

1
2
3
4
5
6
7
8
# See the devices
busctl --user call org.freedesktop.NetworkManager \
  /org/freedesktop/NetworkManager org.freedesktop.NetworkManager GetDevices

# See the available WiFi connections
dbus-send --system --print-reply \
  /org/freedesktop/NetworkManager \
  org.freedesktop.NetworkManager.GetDevices

PulseAudio / PipeWire#

1
2
3
4
5
6
7
# See the sinks (audio outputs)
pacmd list-sinks

# Or with D-Bus
busctl --user call org.pulseaudio.Server \
  /org/pulseaudio/core1 \
  org.PulseAudio.Core1.GetSinks

BlueZ (Bluetooth)#

1
2
3
4
5
6
7
8
# See the paired Bluetooth devices
busctl --system list --match "type='signal',interface='org.bluez.Device1'"

# See the adapter
busctl --system call org.bluez \
  /org/bluez/hci0 \
  org.freedesktop.DBus.Properties.GetAll \
  s "org.bluez.Adapter1"

Common D-Bus Errors#

Error: “Service not available”#

1
org.freedesktop.DBus.Error.ServiceUnknown

Cause: The service is not running or does not exist

Fix:

1
2
3
4
# Start the service
systemctl --user start pulseaudio
sudo systemctl start bluetooth
sudo systemctl start NetworkManager

Error: “No such object path”#

1
org.freedesktop.DBus.Error.ObjectPathNotFound

Cause: The object path does not exist

Fix:

1
2
# Check the available paths
busctl --user tree org.freedesktop.NetworkManager

Error: “Access Denied”#

1
org.freedesktop.DBus.Error.AccessDenied

Cause: Insufficient permissions

Fix:

1
2
3
# Use the system bus instead of the session bus
# Or add the user to the appropriate group
sudo usermod -a -G audio $USER

Debugging D-Bus in Vasak#

Enabling D-Bus Logs#

1
2
3
4
5
# Run with D-Bus debugging
DBUS_VERBOSE=1 vasak-desktop

# Or only for specific modules
RUST_LOG=vasak_desktop::dbus=debug vasak-desktop

Monitoring D-Bus While You Run#

1
2
3
4
5
# Terminal 1: D-Bus monitor
dbus-monitor --session

# Terminal 2: run Vasak with logs
RUST_LOG=debug vasak-desktop

Step by Step Debugging#

1
2
3
4
5
6
7
8
9
# In the Rust code, add prints
eprintln!("Connecting to D-Bus...");
let connection = Connection::session().await?;
eprintln!("Connected!");

// Build with debug
cargo build
# Run
RUST_LOG=debug ./target/debug/vasak_desktop

Best Practices#

✅ Do:#

  • Check that the service exists before using it
  • Handle D-Bus connection errors
  • Use timeouts on D-Bus calls
  • Listen to signals for system changes
  • Document which service you use

❌ Don’t:#

  • Assume a service is always available
  • Block the main thread on D-Bus calls
  • Ignore D-Bus errors
  • Make D-Bus calls in uncontrolled loops
  • Use hardcoded paths

Additional Resources#