defmodule ExDBus.Proxy do @moduledoc """ Client-side proxy for a remote D-Bus object. Wraps a Connection with a destination name and object path for ergonomic method calls. ## Usage {:ok, conn} = ExDBus.Connection.start_link(address: "unix:path=/tmp/gao_bus_socket") proxy = ExDBus.Proxy.new(conn, "org.freedesktop.DBus", "/org/freedesktop/DBus") {:ok, reply} = ExDBus.Proxy.call(proxy, "org.freedesktop.DBus", "ListNames") """ alias ExDBus.{Connection, Message} defstruct [:connection, :destination, :path] @type t :: %__MODULE__{ connection: pid() | GenServer.name(), destination: String.t(), path: String.t() } @doc """ Create a new proxy for a remote D-Bus object. """ @spec new(pid() | GenServer.name(), String.t(), String.t()) :: t() def new(connection, destination, path \\ "/") do %__MODULE__{ connection: connection, destination: destination, path: path } end @doc """ Call a method on the remote object. Returns `{:ok, reply_message}` or `{:error, reason}`. """ @spec call(t(), String.t(), String.t(), keyword()) :: {:ok, Message.t()} | {:error, term()} def call(%__MODULE__{} = proxy, interface, method, opts \\ []) do signature = Keyword.get(opts, :signature) body = Keyword.get(opts, :body, []) timeout = Keyword.get(opts, :timeout, 5_000) msg = Message.method_call(proxy.path, interface, method, destination: proxy.destination, signature: signature, body: body ) Connection.call(proxy.connection, msg, timeout) end @doc """ Get a property value via org.freedesktop.DBus.Properties.Get. Returns `{:ok, {signature, value}}` or `{:error, reason}`. """ @spec get_property(t(), String.t(), String.t(), keyword()) :: {:ok, {String.t(), term()}} | {:error, term()} def get_property(%__MODULE__{} = proxy, interface, property, opts \\ []) do timeout = Keyword.get(opts, :timeout, 5_000) case call(proxy, "org.freedesktop.DBus.Properties", "Get", signature: "ss", body: [interface, property], timeout: timeout ) do {:ok, %Message{body: [variant]}} -> {:ok, variant} {:error, _} = err -> err end end @doc """ Set a property value via org.freedesktop.DBus.Properties.Set. The value must be a `{signature, value}` variant tuple. """ @spec set_property(t(), String.t(), String.t(), {String.t(), term()}, keyword()) :: {:ok, Message.t()} | {:error, term()} def set_property(%__MODULE__{} = proxy, interface, property, {_sig, _val} = variant, opts \\ []) do timeout = Keyword.get(opts, :timeout, 5_000) call(proxy, "org.freedesktop.DBus.Properties", "Set", signature: "ssv", body: [interface, property, variant], timeout: timeout ) end @doc """ Get all properties via org.freedesktop.DBus.Properties.GetAll. Returns `{:ok, [{key, {sig, value}}]}` or `{:error, reason}`. """ @spec get_all_properties(t(), String.t(), keyword()) :: {:ok, list()} | {:error, term()} def get_all_properties(%__MODULE__{} = proxy, interface, opts \\ []) do timeout = Keyword.get(opts, :timeout, 5_000) case call(proxy, "org.freedesktop.DBus.Properties", "GetAll", signature: "s", body: [interface], timeout: timeout ) do {:ok, %Message{body: [props]}} -> {:ok, props} {:error, _} = err -> err end end @doc """ Introspect the remote object. Returns `{:ok, xml_string}` or `{:error, reason}`. """ @spec introspect(t(), keyword()) :: {:ok, String.t()} | {:error, term()} def introspect(%__MODULE__{} = proxy, opts \\ []) do timeout = Keyword.get(opts, :timeout, 5_000) case call(proxy, "org.freedesktop.DBus.Introspectable", "Introspect", timeout: timeout) do {:ok, %Message{body: [xml]}} -> {:ok, xml} {:error, _} = err -> err end end @doc """ Emit a signal from this proxy's connection. """ @spec emit_signal(t(), String.t(), String.t(), keyword()) :: :ok def emit_signal(%__MODULE__{} = proxy, interface, member, opts \\ []) do signature = Keyword.get(opts, :signature) body = Keyword.get(opts, :body, []) signal = Message.signal(proxy.path, interface, member, destination: proxy.destination, signature: signature, body: body ) Connection.send_signal(proxy.connection, signal) end end