class DBus::Connection

D-Bus main connection class

Main class that maintains a connection to a bus and can handle incoming and outgoing messages.

Constants

DBUSXMLINTRO
NAME_FLAG_ALLOW_REPLACEMENT

FIXME: describe the following names, flags and constants. See DBus spec for definition

NAME_FLAG_DO_NOT_QUEUE
NAME_FLAG_REPLACE_EXISTING
REQUEST_NAME_REPLY_ALREADY_OWNER
REQUEST_NAME_REPLY_EXISTS
REQUEST_NAME_REPLY_IN_QUEUE
REQUEST_NAME_REPLY_PRIMARY_OWNER

Attributes

message_queue[R]

pop and push messages here

unique_name[R]

The unique name (by specification) of the message.

Public Class Methods

new(path) click to toggle source

Create a new connection to the bus for a given connect path. path format is described in the D-Bus specification: dbus.freedesktop.org/doc/dbus-specification.html#addresses and is something like: “transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2” e.g. “unix:path=/tmp/dbus-test” or “tcp:host=localhost,port=2687”

# File lib/dbus/bus.rb, line 264
def initialize(path)
  @message_queue = MessageQueue.new(path)
  @unique_name = nil

  # @return [Hash{Integer => Proc}]
  #   key: message serial
  #   value: block to be run when the reply to that message is received
  @method_call_replies = {}

  # @return [Hash{Integer => Message}]
  #   for debugging only: messages for which a reply was not received yet;
  #   key == value.serial
  @method_call_msgs = {}
  @signal_matchrules = {}
  @proxy = nil
end

Public Instance Methods

[](name)
Alias for: service
add_match(match_rule, &slot) click to toggle source

Asks bus to send us messages matching mr, and execute slot when received @param match_rule [MatchRule,#to_s]

# File lib/dbus/bus.rb, line 583
def add_match(match_rule, &slot)
  # check this is a signal.
  mrs = match_rule.to_s
  DBus.logger.debug "#{@signal_matchrules.size} rules, adding #{mrs.inspect}"
  # don't ask for the same match if we override it
  unless @signal_matchrules.key?(mrs)
    DBus.logger.debug "Asked for a new match"
    proxy.AddMatch(mrs)
  end
  @signal_matchrules[mrs] = slot
end
dispatch_message_queue() click to toggle source

Dispatch all messages that are available in the queue, but do not block on the queue. Called by a main loop when something is available in the queue

# File lib/dbus/bus.rb, line 284
def dispatch_message_queue
  while (msg = @message_queue.pop(blocking: false)) # FIXME: EOFError
    process(msg)
  end
end
emit(service, obj, intf, sig, *args) click to toggle source

@api private Emit a signal event for the given service, object obj, interface intf and signal sig with arguments args. @param service [Service] @param obj [DBus::Object] @param intf [Interface] @param sig [Signal] @param args arguments for the signal

# File lib/dbus/bus.rb, line 684
def emit(service, obj, intf, sig, *args)
  m = Message.new(DBus::Message::SIGNAL)
  m.path = obj.path
  m.interface = intf.name
  m.member = sig.name
  m.sender = service.name
  i = 0
  sig.params.each do |par|
    m.add_param(par.type, args[i])
    i += 1
  end
  @message_queue.push(m)
end
glibize() click to toggle source

Tell a bus to register itself on the glib main loop

# File lib/dbus/bus.rb, line 291
def glibize
  require "glib2"
  # Circumvent a ruby-glib bug
  @channels ||= []

  gio = GLib::IOChannel.new(@message_queue.socket.fileno)
  @channels << gio
  gio.add_watch(GLib::IOChannel::IN) do |_c, _ch|
    dispatch_message_queue
    true
  end
end
introspect(dest, path) { |proxy_object_factory.build| ... } click to toggle source

@api private Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method dest is the service and path the object path you want to introspect If a code block is given, the introspect call in asynchronous. If not data is returned

FIXME: link to ProxyObject data definition The returned object is a ProxyObject that has methods you can call to issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN

# File lib/dbus/bus.rb, line 472
def introspect(dest, path)
  if !block_given?
    # introspect in synchronous !
    data = introspect_data(dest, path)
    pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
    pof.build
  else
    introspect_data(dest, path) do |async_data|
      yield(DBus::ProxyObjectFactory.new(async_data, self, dest, path).build)
    end
  end
end
introspect_data(dest, path, &reply_handler) click to toggle source

@api private

# File lib/dbus/bus.rb, line 444
def introspect_data(dest, path, &reply_handler)
  m = DBus::Message.new(DBus::Message::METHOD_CALL)
  m.path = path
  m.interface = "org.freedesktop.DBus.Introspectable"
  m.destination = dest
  m.member = "Introspect"
  m.sender = unique_name
  if reply_handler.nil?
    send_sync_or_async(m).first
  else
    send_sync_or_async(m) do |*args|
      # TODO: test async introspection, is it used at all?
      args.shift # forget the message, pass only the text
      reply_handler.call(*args)
      nil
    end
  end
end
on_return(msg, &retc) click to toggle source

@api private Specify a code block that has to be executed when a reply for message msg is received. @param msg [Message]

# File lib/dbus/bus.rb, line 570
def on_return(msg, &retc)
  # Have a better exception here
  if msg.message_type != Message::METHOD_CALL
    raise "on_return should only get method_calls"
  end

  @method_call_msgs[msg.serial] = msg
  @method_call_replies[msg.serial] = retc
end
process(msg) click to toggle source

@api private Process a message msg based on its type. @param msg [Message]

# File lib/dbus/bus.rb, line 610
def process(msg)
  return if msg.nil? # check if somethings wrong

  case msg.message_type
  when Message::ERROR, Message::METHOD_RETURN
    raise InvalidPacketException if msg.reply_serial.nil?

    mcs = @method_call_replies[msg.reply_serial]
    if !mcs
      DBus.logger.debug "no return code for mcs: #{mcs.inspect} msg: #{msg.inspect}"
    else
      if msg.message_type == Message::ERROR
        mcs.call(Error.new(msg))
      else
        mcs.call(msg)
      end
      @method_call_replies.delete(msg.reply_serial)
      @method_call_msgs.delete(msg.reply_serial)
    end
  when DBus::Message::METHOD_CALL
    if msg.path == "/org/freedesktop/DBus"
      DBus.logger.debug "Got method call on /org/freedesktop/DBus"
    end
    node = @service.get_node(msg.path, create: false)
    # introspect a known path even if there is no object on it
    if node &&
       msg.interface == "org.freedesktop.DBus.Introspectable" &&
       msg.member == "Introspect"
      reply = Message.new(Message::METHOD_RETURN).reply_to(msg)
      reply.sender = @unique_name
      xml = node.to_xml(msg.path)
      reply.add_param(Type::STRING, xml)
      @message_queue.push(reply)
    # dispatch for an object
    elsif node&.object
      node.object.dispatch(msg)
    else
      reply = Message.error(msg, "org.freedesktop.DBus.Error.UnknownObject",
                            "Object #{msg.path} doesn't exist")
      @message_queue.push(reply)
    end
  when DBus::Message::SIGNAL
    # the signal can match multiple different rules
    # clone to allow new signale handlers to be registered
    @signal_matchrules.dup.each do |mrs, slot|
      if DBus::MatchRule.new.from_s(mrs).match(msg)
        slot.call(msg)
      end
    end
  else
    # spec(Message Format): Unknown types must be ignored.
    DBus.logger.debug "Unknown message type: #{msg.message_type}"
  end
rescue Exception => e
  raise msg.annotate_exception(e)
end
proxy() click to toggle source

Set up a ProxyObject for the bus itself, since the bus is introspectable. @return [ProxyObject] that always returns an array

({DBus::ApiOptions#proxy_method_returns_array})

Returns the object.

# File lib/dbus/bus.rb, line 520
def proxy
  if @proxy.nil?
    path = "/org/freedesktop/DBus"
    dest = "org.freedesktop.DBus"
    pof = DBus::ProxyObjectFactory.new(
      DBUSXMLINTRO, self, dest, path,
      api: ApiOptions::A0
    )
    @proxy = pof.build["org.freedesktop.DBus"]
  end
  @proxy
end
remove_match(match_rule) click to toggle source

@param match_rule [MatchRule,#to_s]

# File lib/dbus/bus.rb, line 596
def remove_match(match_rule)
  mrs = match_rule.to_s
  rule_existed = @signal_matchrules.delete(mrs).nil?
  # don't remove nonexisting matches.
  return if rule_existed

  # FIXME: if we do try, the Error.MatchRuleNotFound is *not* raised
  # and instead is reported as "no return code for nil"
  proxy.RemoveMatch(mrs)
end
request_service(name) click to toggle source

Attempt to request a service name.

FIXME, NameRequestError cannot really be rescued as it will be raised when dispatching a later call. Rework the API to better match the spec. @return [Service]

# File lib/dbus/bus.rb, line 494
def request_service(name)
  # Use RequestName, but asynchronously!
  # A synchronous call would not work with service activation, where
  # method calls to be serviced arrive before the reply for RequestName
  # (Ticket#29).
  proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING) do |rmsg, r|
    # check and report errors first
    raise rmsg if rmsg.is_a?(Error)

    details = if r == REQUEST_NAME_REPLY_IN_QUEUE
                other = proxy.GetNameOwner(name).first
                other_creds = proxy.GetConnectionCredentials(other).first
                "already owned by #{other}, #{other_creds.inspect}"
              else
                "error code #{r}"
              end
    raise NameRequestError, "Could not request #{name}, #{details}" unless r == REQUEST_NAME_REPLY_PRIMARY_OWNER
  end
  @service = Service.new(name, self)
  @service
end
send_sync(msg) { |reply| ... } click to toggle source

@api private Send a message msg on to the bus. This is done synchronously, thus the call will block until a reply message arrives. @param msg [Message] @param retc [Proc] the reply handler @yieldparam rmsg [MethodReturnMessage] the reply @yieldreturn [Array<Object>] the reply (out) parameters

# File lib/dbus/bus.rb, line 546
def send_sync(msg, &retc) # :yields: reply/return message
  return if msg.nil? # check if somethings wrong

  @message_queue.push(msg)
  @method_call_msgs[msg.serial] = msg
  @method_call_replies[msg.serial] = retc

  retm = wait_for_message
  return if retm.nil? # check if somethings wrong

  process(retm)
  while @method_call_replies.key? msg.serial
    retm = wait_for_message
    process(retm)
  end
rescue EOFError
  new_err = DBus::Error.new("Connection dropped after we sent #{msg.inspect}")
  raise new_err
end
send_sync_or_async(message, &reply_handler) click to toggle source

@api private Send a message. If reply_handler is not given, wait for the reply and return the reply, or raise the error. If reply_handler is given, it will be called when the reply eventually arrives, with the reply message as the 1st param and its params following

# File lib/dbus/bus.rb, line 422
def send_sync_or_async(message, &reply_handler)
  ret = nil
  if reply_handler.nil?
    send_sync(message) do |rmsg|
      raise rmsg if rmsg.is_a?(Error)

      ret = rmsg.params
    end
  else
    on_return(message) do |rmsg|
      if rmsg.is_a?(Error)
        reply_handler.call(rmsg)
      else
        reply_handler.call(rmsg, * rmsg.params)
      end
    end
    @message_queue.push(message)
  end
  ret
end
service(name) click to toggle source

Retrieves the Service with the given name. @return [Service]

# File lib/dbus/bus.rb, line 669
def service(name)
  # The service might not exist at this time so we cannot really check
  # anything
  Service.new(name, self)
end
Also aliased as: []
wait_for_message() click to toggle source

@api private Wait for a message to arrive. Return it once it is available.

# File lib/dbus/bus.rb, line 535
def wait_for_message
  @message_queue.pop # FIXME: EOFError
end

Private Instance Methods

send_hello() click to toggle source

Send a hello messages to the bus to let it know we are here.

# File lib/dbus/bus.rb, line 702
def send_hello
  m = Message.new(DBus::Message::METHOD_CALL)
  m.path = "/org/freedesktop/DBus"
  m.destination = "org.freedesktop.DBus"
  m.interface = "org.freedesktop.DBus"
  m.member = "Hello"
  send_sync(m) do |rmsg|
    @unique_name = rmsg.destination
    DBus.logger.debug "Got hello reply. Our unique_name is #{@unique_name}"
  end
  @service = Service.new(@unique_name, self)
end