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

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 @return [MessageQueue]

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/connection.rb, line 28
def initialize(path)
  @message_queue = MessageQueue.new(path)

  # @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 = {}
end

Public Instance Methods

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] @return [void] actually return whether the rule existed, internal detail

# File lib/dbus/connection.rb, line 253
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}"
  rule_existed = @signal_matchrules.key?(mrs)
  @signal_matchrules[mrs] = slot
  rule_existed
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/connection.rb, line 50
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 unused @param obj [DBus::Object] @param intf [Interface] @param sig [Signal] @param args arguments for the signal

# File lib/dbus/connection.rb, line 337
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
  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/connection.rb, line 57
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
handle_return_of_request_name(ret, name) click to toggle source

In case RequestName did not succeed, raise an exception but first ask the bus who owns the name instead of us @param ret [Integer] what RequestName returned @param name Name that was requested @return [REQUEST_NAME_REPLY_PRIMARY_OWNER,REQUEST_NAME_REPLY_ALREADY_OWNER] on success @raise [NameRequestError] with error_code REQUEST_NAME_REPLY_EXISTS or REQUEST_NAME_REPLY_IN_QUEUE, on failure @api private

# File lib/dbus/connection.rb, line 173
def handle_return_of_request_name(ret, name)
  if [REQUEST_NAME_REPLY_EXISTS, REQUEST_NAME_REPLY_IN_QUEUE].include?(ret)
    other = proxy.GetNameOwner(name).first
    other_creds = proxy.GetConnectionCredentials(other).first
    message = "Could not request #{name}, already owned by #{other}, #{other_creds.inspect}"
    raise NameRequestError.new(ret), message
  end

  ret
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/connection.rb, line 141
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/connection.rb, line 113
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
object_server() click to toggle source
# File lib/dbus/connection.rb, line 43
def object_server
  @object_server ||= ObjectServer.new(self)
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/connection.rb, line 239
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/connection.rb, line 272
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 = object_server.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
remove_match(match_rule) click to toggle source

@param match_rule [MatchRule,#to_s] @return [void] actually return whether the rule existed, internal detail

# File lib/dbus/connection.rb, line 264
def remove_match(match_rule)
  mrs = match_rule.to_s
  @signal_matchrules.delete(mrs).nil?
end
request_service(name) click to toggle source

Attempt to request a service name. @raise NameRequestError which cannot really be rescued as it will be raised when dispatching a later call. @return [ObjectServer] @deprecated Use {BusConnection#request_name}.

# File lib/dbus/connection.rb, line 188
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)

    handle_return_of_request_name(r, name)
  end
  object_server
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/connection.rb, line 215
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/connection.rb, line 91
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
wait_for_message() click to toggle source

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

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