D-Bus bindings for the Elixir language
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.

README.md

DBux

Build Status Coverage Status Hex.pm Hex.pm

DBux provides bindings for D-Bus IPC protocol for the Elixir programming language.

Project aims

DBux's aim is to provide low-level GenServer-like pattern to handle interaction with D-Bus daemon.

It is not going to provide high-level proxy that will magically map objects/interfaces exported over D-Bus to data structures used in your application. In my opinion it's a task for an another abstraction layer (read: another project built on top of DBux).

At the beginning it's going to provide only functionality needed to act as a client. Acting as a server may be added later.

Versioning

Project follows Semantic Versioning.

Status

Project in production use in at least one big app :) However, some things are still not implemented:

  • Marshalling variants that contain container types
  • Handling message timeouts
  • Handling introspection calls and other generic D-Bus methods
  • Other transports than TCP
  • Other authentication methods than anonymous

Installation

Add dependency to your mix.exs:

defp deps do
  [{:dbux, "~> 1.0.0"}]
end

Sample Usage

An example DBux.PeerConnection process:

defmodule MyApp.Bus do
  require Logger
  use DBux.PeerConnection

  @request_name_message_id :request_name
  @add_match_message_id    :add_match

  @introspection """
  <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
   "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
  <node name="/com/example/sample_object">
    <interface name="com.example.SampleInterface">
      <method name="Frobate">
        <arg name="foo" type="i" direction="in"/>
        <arg name="bar" type="s" direction="out"/>
        <arg name="baz" type="a{us}" direction="out"/>
        <annotation name="org.freedesktop.DBus.Deprecated" value="true"/>
      </method>
      <method name="Bazify">
        <arg name="bar" type="(iiu)" direction="in"/>
        <arg name="bar" type="v" direction="out"/>
      </method>
      <method name="Mogrify">
        <arg name="bar" type="(iiav)" direction="in"/>
      </method>
      <signal name="Changed">
        <arg name="new_value" type="b"/>
      </signal>
      <property name="Bar" type="y" access="readwrite"/>
    </interface>
    <node name="child_of_sample_object"/>
    <node name="another_child_of_sample_object"/>
  </node>
  """

  def start_link(hostname, options \\ []) do
    DBux.PeerConnection.start_link(__MODULE__, hostname, options)
  end

  def init(hostname) do
    initial_state = %{hostname: hostname}

    {:ok, "tcp:host=" <> hostname <> ",port=8888", [:anonymous], initial_state}
  end

  def handle_up(state) do
    Logger.info("Up")

    {:send, [
      DBux.Message.build_signal("/", "org.example.dbux.MyApp", "Connected", []),
      {@add_match_message_id,    DBux.MessageTemplate.add_match(:signal, nil, "org.example.dbux.OtherIface")},
      {@request_name_message_id, DBux.MessageTemplate.request_name("org.example.dbux.MyApp", 0x4)}
    ], state}
  end

  def handle_down(state) do
    Logger.warn("Down")
    {:backoff, 1000, state}
  end

  def handle_method_call(serial, sender, "/", "Introspect", "org.freedesktop.DBus.Introspectable", _body, _flags, state) do
    Logger.debug("Got Introspect call")

    {:send, [
      DBux.Message.build_method_return(serial, sender, [%DBux.Value{type: :string, value: @introspection}])
    ], state}
  end

  def handle_method_return(_serial, _sender, _reply_serial, _body, @request_name_message_id, state) do
    Logger.info("Name acquired")
    {:noreply, state}
  end

  def handle_method_return(_serial, _sender, _reply_serial, _body, @add_match_message_id, state) do
    Logger.info("Match added")
    {:noreply, state}
  end

  def handle_error(_serial, _sender, _reply_serial, error_name, _body, @request_name_message_id, state) do
    Logger.warn("Failed to acquire name: " <> error_name)
    {:noreply, state}
  end

  def handle_error(_serial, _sender, _reply_serial, error_name, _body, @add_match_message_id, state) do
    Logger.warn("Failed to add match: " <> error_name)
    {:noreply, state}
  end

  def handle_signal(_serial, _sender, _path, _member, "org.example.dbux.OtherIface", _body, state) do
    Logger.info("Got signal from OtherIface")
    {:noreply, state}
  end

  def handle_signal(_serial, _sender, _path, _member, _member, _body, state) do
    Logger.info("Got other signal")
    {:noreply, state}
  end
end

And of the accompanying process that can control the connection:

defmodule MyApp.Core do
  def do_the_stuff do
    {:ok, connection} = MyApp.Bus.start_link("dbusserver.example.com")
  end
end

Authors

Marcin Lewandowski marcin@saepia.net

Debugging

If you encounter bugs, you may want to compile (not run, compile) the code with DBUX_DEBUG environment variable set to any value.

Contributing

You are welcome to open pull requests. Tests are mandatory.

Credits

Project is heavily inspired by Connection.

License

MIT