Packages

An Elixir implementation of the D-Bus message protocol.

Current section

Files

Jump to
rebus lib rebus.ex
Raw

lib/rebus.ex

defmodule Rebus do
@moduledoc """
An Elixir implementation of the D-Bus message protocol.
Rebus provides a clean, Elixir-native interface for communicating over D-Bus,
the inter-process communication (IPC) and remote procedure call (RPC) mechanism
that is standard on Linux desktop systems.
## Overview
D-Bus is a message bus system that allows multiple processes to communicate with
each other in a structured way. Rebus implements the D-Bus wire protocol and provides
an easy-to-use API for:
- Connecting to D-Bus message buses (system and session buses)
- Sending method calls and receiving replies
- Emitting and receiving signals
- Publishing and consuming D-Bus services
## Quick Start
# Connect to the session bus
{:ok, conn} = Rebus.connect(:session)
# Add a signal handler to receive all signals
ref = Rebus.add_signal_handler(conn)
# Later, remove the signal handler
Rebus.remove_signal_handler(conn, ref)
## Connection Types
Rebus supports connecting to different types of D-Bus endpoints:
- `:system` - Connects to the system bus using the address specified in
application config (see below) or the `/run/dbus/system_bus_socket` by default.
- `:session` - Connects to the session bus using the address specified in
the `DBUS_SESSION_BUS_ADDRESS` environment variable.
- `%{family: :local, path: path}` - Unix domain socket connection to a local D-Bus daemon
- `%{family: :inet, addr: {ip, port}}` - TCP/IP connection to a remote D-Bus daemon
## Configuration
You can configure the system bus address in your application's config:
config :rebus, :system_bus_address, "unix:path=/run/dbus/system_bus_socket"
## Architecture
When you connect to a D-Bus bus using `connect/2`, Rebus creates a supervised
connection process that handles the low-level protocol details. The connection
manages authentication, message serialization/deserialization, and maintains
the persistent connection to the bus.
## Error Handling
All functions return standard Elixir `{:ok, result}` or `{:error, reason}` tuples.
Connection failures, authentication errors, and protocol violations are properly
propagated as error tuples.
## Examples
# Connect to session bus with default options
{:ok, conn} = Rebus.connect(:session)
# Connect to a Unix domain socket
{:ok, conn} = Rebus.connect(%{family: :local, path: "/tmp/dbus-socket"})
For more advanced usage, see the documentation for `Rebus.Message` and other
modules in this package.
"""
@type address :: :system | :session | :socket.sockaddr_in() | :socket.sockaddr_un()
@default_system_bus_address "unix:path=/run/dbus/system_bus_socket"
@doc """
Establishes a connection to a D-Bus message bus.
Creates a supervised connection process that handles D-Bus protocol communication.
The connection automatically handles authentication and maintains the persistent
connection to the specified D-Bus endpoint.
## Parameters
- `address` - The D-Bus endpoint to connect to:
- `:system` - Connects to the system bus using the address specified in
application config (see below) or the `/run/dbus/system_bus_socket` by default.
- `:session` - Connects to the session bus using the address specified in
the `DBUS_SESSION_BUS_ADDRESS` environment variable.
- `%{family: :local, path: path}` - Unix domain socket connection to a local D-Bus daemon
- `%{family: :inet, addr: {ip, port}}` - TCP/IP connection to a remote D-Bus daemon
- `opts` - Optional keyword list of connection options:
- `:timeout` - Connection timeout in milliseconds (default: 5000)
- `:name` - Optional name for the connection process
- Additional options are passed to the underlying connection process
## Return Values
- `{:ok, pid}` - Returns the PID of the connection process
- `{:error, reason}` - Connection failed due to the specified reason
## Examples
# Connect to a custom Unix socket
{:ok, conn} = Rebus.connect(%{family: :local, path: "/tmp/my-dbus"})
# Connect to a TCP endpoint
address = %{family: :inet, addr: {127, 0, 0, 1}, port: 12345}
{:ok, conn} = Rebus.connect(address)
## Notes
The returned PID is for the connection process, which is the main interface for
sending and receiving D-Bus messages.
"""
@spec connect(address(), keyword()) :: DynamicSupervisor.on_start_child()
def connect(address, opts \\ [])
def connect(:system, opts) do
case Application.get_env(:rebus, :system_bus_address, @default_system_bus_address) do
nil ->
{:error, :no_system_bus_address}
"unix:path=" <> address ->
connect(%{family: :local, path: address}, opts)
end
end
def connect(:session, opts) do
case System.get_env("DBUS_SESSION_BUS_ADDRESS") do
nil ->
{:error, :no_session_bus_address}
"unix:path=" <> address ->
connect(%{family: :local, path: address}, opts)
end
end
def connect(%{family: family} = addr, opts) when family in [:inet, :local] do
args =
opts
|> Keyword.put(:addr, addr)
child_spec = {Rebus.Connection, args}
DynamicSupervisor.start_child(Rebus.ConnectionSupervisor, child_spec)
end
@doc """
Same as `connect/2`, but raises an exception on failure.
"""
@spec connect!(address(), keyword()) :: pid()
def connect!(address, opts \\ []) do
case connect(address, opts) do
{:ok, pid} -> pid
{:error, reason} -> raise "Failed to connect to D-Bus: #{inspect(reason)}"
end
end
@doc """
Adds a signal handler to receive D-Bus signals on the connection.
Signal handlers receive all D-Bus signals that arrive on the connection.
Multiple signal handlers can be registered on the same connection, and each
will receive copies of all signals.
## Parameters
- `conn` - The connection PID returned from `connect/2`
## Return Values
- `reference()` - A unique reference that identifies this signal handler
## Examples
{:ok, conn} = Rebus.connect(%{family: :local, path: "/tmp/my-dbus"})
ref = Rebus.add_signal_handler(conn)
# The calling process will now receive messages like:
# {^ref, %Rebus.Message{type: :signal, ...}}
## Signal Message Format
When a D-Bus signal is received, registered signal handlers will receive
a message in the format:
{^ref, %Rebus.Message{
type: :signal,
header_fields: %{
path: "/path/to/object",
interface: "com.example.Interface",
member: "SignalName",
sender: "com.example.Service"
},
body: [signal_args...],
signature: "signal_signature"
}}
## Notes
Signal handlers should be prepared to handle a potentially high volume of
messages depending on the activity on the D-Bus. Consider using selective
receive or GenServer message handling for robust signal processing.
Remember to call `remove_signal_handler/2` when you no longer need to
receive signals to avoid message queue buildup.
Signal handlers are automatically cleaned up when the connection is closed
or when the handler exits.
"""
defdelegate add_signal_handler(conn), to: Rebus.Connection
@doc """
Removes a previously registered signal handler from the connection.
Stops the specified signal handler from receiving future D-Bus signals.
The handler is identified by the reference returned from `add_signal_handler/1`.
## Parameters
- `conn` - The connection PID returned from `connect/2`
- `ref` - The reference returned from `add_signal_handler/1`
## Return Values
- `:ok` - The signal handler was successfully removed
## Examples
{:ok, conn} = Rebus.connect(%{family: :local, path: "/tmp/my-dbus"})
ref = Rebus.add_signal_handler(conn)
# ... handle signals ...
# Remove the handler when done
:ok = Rebus.delete_signal_handler(conn, ref)
## Notes
After deleting a signal handler, the calling process will no longer receive
signal messages for that handler. Other signal handlers on the same connection
(if any) will continue to receive signals normally.
It's safe to call this function multiple times with the same reference -
subsequent calls will simply return `:ok` without error.
"""
defdelegate delete_signal_handler(conn, ref), to: Rebus.Connection
end