Class DBus::Connection
In: lib/dbus/bus.rb
Parent: Object

D-Bus main connection class

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

Methods

Classes and Modules

Class DBus::Connection::NameRequestError

Constants

NAME_FLAG_ALLOW_REPLACEMENT = 0x1   FIXME: describe the following names, flags and constants. See DBus spec for definition
NAME_FLAG_REPLACE_EXISTING = 0x2
NAME_FLAG_DO_NOT_QUEUE = 0x4
REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1
REQUEST_NAME_REPLY_IN_QUEUE = 0x2
REQUEST_NAME_REPLY_EXISTS = 0x3
REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4
DBUSXMLINTRO = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> <node> <interface name="org.freedesktop.DBus.Introspectable"> <method name="Introspect"> <arg name="data" direction="out" type="s"/> </method> </interface> <interface name="org.freedesktop.DBus"> <method name="RequestName"> <arg direction="in" type="s"/> <arg direction="in" type="u"/> <arg direction="out" type="u"/> </method> <method name="ReleaseName"> <arg direction="in" type="s"/> <arg direction="out" type="u"/> </method> <method name="StartServiceByName"> <arg direction="in" type="s"/> <arg direction="in" type="u"/> <arg direction="out" type="u"/> </method> <method name="Hello"> <arg direction="out" type="s"/> </method> <method name="NameHasOwner"> <arg direction="in" type="s"/> <arg direction="out" type="b"/> </method> <method name="ListNames"> <arg direction="out" type="as"/> </method> <method name="ListActivatableNames"> <arg direction="out" type="as"/> </method> <method name="AddMatch"> <arg direction="in" type="s"/> </method> <method name="RemoveMatch"> <arg direction="in" type="s"/> </method> <method name="GetNameOwner"> <arg direction="in" type="s"/> <arg direction="out" type="s"/> </method> <method name="ListQueuedOwners"> <arg direction="in" type="s"/> <arg direction="out" type="as"/> </method> <method name="GetConnectionUnixUser"> <arg direction="in" type="s"/> <arg direction="out" type="u"/> </method> <method name="GetConnectionUnixProcessID"> <arg direction="in" type="s"/> <arg direction="out" type="u"/> </method> <method name="GetConnectionSELinuxSecurityContext"> <arg direction="in" type="s"/> <arg direction="out" type="ay"/> </method> <method name="ReloadConfig"> </method> <signal name="NameOwnerChanged"> <arg type="s"/> <arg type="s"/> <arg type="s"/> </signal> <signal name="NameLost"> <arg type="s"/> </signal> <signal name="NameAcquired"> <arg type="s"/> </signal> </interface> </node> '
MSG_BUF_SIZE = 4096   The buffer size for messages.

Attributes

socket  [R]  The socket that is used to connect with the bus.
unique_name  [R]  The unique name (by specification) of the message.

Public Class methods

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"

[Source]

# File lib/dbus/bus.rb, line 198
    def initialize(path)
      @path = path
      @unique_name = nil
      @buffer = ""
      @method_call_replies = Hash.new
      @method_call_msgs = Hash.new
      @signal_matchrules = Hash.new
      @proxy = nil
      @object_root = Node.new("/")
      @is_tcp = false
    end

Public Instance methods

[](name)

Alias for service

Asks bus to send us messages matching mr, and execute slot when received

[Source]

# File lib/dbus/bus.rb, line 591
    def add_match(mr, &slot)
      # check this is a signal.
      mrs = mr.to_s
      puts "#{@signal_matchrules.size} rules, adding #{mrs.inspect}" if $DEBUG
      # don't ask for the same match if we override it
      unless @signal_matchrules.key?(mrs)
        puts "Asked for a new match" if $DEBUG
        proxy.AddMatch(mrs)
      end
      @signal_matchrules[mrs] = slot
    end

Connect to the bus and initialize the connection.

[Source]

# File lib/dbus/bus.rb, line 211
    def connect
      addresses = @path.split ";"
      # connect to first one that succeeds
      worked = addresses.find do |a|
        transport, keyvaluestring = a.split ":"
        kv_list = keyvaluestring.split ","
        kv_hash = Hash.new
        kv_list.each do |kv|
          key, escaped_value = kv.split "="
          value = escaped_value.gsub(/%(..)/) {|m| [$1].pack "H2" }
          kv_hash[key] = value
        end
        case transport
          when "unix"
          connect_to_unix kv_hash
          when "tcp"
          connect_to_tcp kv_hash
          else
          # ignore, report?
        end
      end
      worked
      # returns the address that worked or nil.
      # how to report failure?
    end

Connect to a bus over tcp and initialize the connection.

[Source]

# File lib/dbus/bus.rb, line 238
    def connect_to_tcp(params)
      #check if the path is sufficient
      if params.key?("host") and params.key?("port")
        begin
          #initialize the tcp socket
          @socket = TCPSocket.new(params["host"],params["port"].to_i)
          @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
          init_connection
          @is_tcp = true
        rescue
          puts "Error: Could not establish connection to: #{@path}, will now exit."
          exit(0) #a little harsh
        end
      else
        #Danger, Will Robinson: the specified "path" is not usable
        puts "Error: supplied path: #{@path}, unusable! sorry."
      end
    end

Connect to an abstract unix bus and initialize the connection.

[Source]

# File lib/dbus/bus.rb, line 258
    def connect_to_unix(params)
      @socket = Socket.new(Socket::Constants::PF_UNIX,Socket::Constants::SOCK_STREAM, 0)
      @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
      if ! params['abstract'].nil?
        if HOST_END == LIL_END
          sockaddr = "\1\0\0#{params['abstract']}"
        else
          sockaddr = "\0\1\0#{params['abstract']}"
        end
      elsif ! params['path'].nil?
        sockaddr = Socket.pack_sockaddr_un(params['path'])
      end
      @socket.connect(sockaddr)
      init_connection
    end

Emit a signal event for the given service, object obj, interface intf and signal sig with arguments args.

[Source]

# File lib/dbus/bus.rb, line 674
    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
      send(m.marshall)
    end

Tell a bus to register itself on the glib main loop

[Source]

# File lib/dbus/bus.rb, line 280
    def glibize
      require 'glib2'
      # Circumvent a ruby-glib bug
      @channels ||= Array.new

      gio = GLib::IOChannel.new(@socket.fileno)
      @channels << gio
      gio.add_watch(GLib::IOChannel::IN) do |c, ch|
        update_buffer
        messages.each do |msg|
          process(msg)
        end
        true
      end
    end

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

[Source]

# File lib/dbus/bus.rb, line 443
    def introspect(dest, path)
      if not block_given?
        # introspect in synchronous !
        data = introspect_data(dest, path)
        pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
        return pof.build
      else
        introspect_data(dest, path) do |async_data|
          yield(DBus::ProxyObjectFactory.new(async_data, self, dest, path).build)
        end
      end
    end

[Source]

# File lib/dbus/bus.rb, line 416
    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

Retrieve all the messages that are currently in the buffer.

[Source]

# File lib/dbus/bus.rb, line 520
    def messages
      ret = Array.new
      while msg = pop_message
        ret << msg
      end
      ret
    end

Specify a code block that has to be executed when a reply for message m is received.

[Source]

# File lib/dbus/bus.rb, line 580
    def on_return(m, &retc)
      # Have a better exception here
      if m.message_type != Message::METHOD_CALL
        raise "on_return should only get method_calls"
      end
      @method_call_msgs[m.serial] = m
      @method_call_replies[m.serial] = retc
    end

Update the buffer and retrieve all messages using Connection#messages. Return the messages.

[Source]

# File lib/dbus/bus.rb, line 533
    def poll_messages
      ret = nil
      r, d, d = IO.select([@socket], nil, nil, 0)
      if r and r.size > 0
        update_buffer
      end
      messages
    end

Get one message from the bus and remove it from the buffer. Return the message.

[Source]

# File lib/dbus/bus.rb, line 507
    def pop_message
      return nil if @buffer.empty?
      ret = nil
      begin
        ret, size = Message.new.unmarshall_buffer(@buffer)
        @buffer.slice!(0, size)
      rescue IncompleteBufferException => e
        # fall through, let ret be null
      end
      ret
    end

Process a message m based on its type.

[Source]

# File lib/dbus/bus.rb, line 614
    def process(m)
      return if m.nil? #check if somethings wrong
      case m.message_type
      when Message::ERROR, Message::METHOD_RETURN
        raise InvalidPacketException if m.reply_serial == nil
        mcs = @method_call_replies[m.reply_serial]
        if not mcs
          puts "DEBUG: no return code for mcs: #{mcs.inspect} m: #{m.inspect}" if $DEBUG
        else
          if m.message_type == Message::ERROR
            mcs.call(Error.new(m))
          else
            mcs.call(m)
          end
          @method_call_replies.delete(m.reply_serial)
          @method_call_msgs.delete(m.reply_serial)
        end
      when DBus::Message::METHOD_CALL
        if m.path == "/org/freedesktop/DBus"
          puts "DEBUG: Got method call on /org/freedesktop/DBus" if $DEBUG
        end
        node = @service.get_node(m.path)
        if not node
          reply = Message.error(m, "org.freedesktop.DBus.Error.UnknownObject",
                                "Object #{m.path} doesn't exist")
          send(reply.marshall)
        # handle introspectable as an exception:
        elsif m.interface == "org.freedesktop.DBus.Introspectable" and
            m.member == "Introspect"
          reply = Message.new(Message::METHOD_RETURN).reply_to(m)
          reply.sender = @unique_name
          reply.add_param(Type::STRING, node.to_xml)
          send(reply.marshall)
        else
          obj = node.object
          return if obj.nil?    # FIXME, sends no reply
          obj.dispatch(m) if obj
        end
      when DBus::Message::SIGNAL
        # the signal can match multiple different rules
        @signal_matchrules.each do |mrs, slot|
          if DBus::MatchRule.new.from_s(mrs).match(m)
            slot.call(m)
          end
        end
      else
        puts "DEBUG: Unknown message type: #{m.message_type}" if $DEBUG
      end
    end

Set up a ProxyObject for the bus itself, since the bus is introspectable. Returns the object.

[Source]

# File lib/dbus/bus.rb, line 482
    def proxy
      if @proxy == nil
        path = "/org/freedesktop/DBus"
        dest = "org.freedesktop.DBus"
        pof = DBus::ProxyObjectFactory.new(DBUSXMLINTRO, self, dest, path)
        @proxy = pof.build["org.freedesktop.DBus"]
      end
      @proxy
    end

[Source]

# File lib/dbus/bus.rb, line 603
    def remove_match(mr)
      mrs = mr.to_s
      unless @signal_matchrules.delete(mrs).nil?
        # don't remove nonexisting matches.
        # 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
    end

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.

[Source]

# File lib/dbus/bus.rb, line 464
    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|
        if rmsg.is_a?(Error)  # check and report errors first
          raise rmsg
        elsif r != REQUEST_NAME_REPLY_PRIMARY_OWNER
          raise NameRequestError
        end
      end
      @service = Service.new(name, self)
      @service
    end

Send the buffer buf to the bus using Connection#writel.

[Source]

# File lib/dbus/bus.rb, line 275
    def send(buf)
      @socket.write(buf) unless @socket.nil?
    end

Send a message m on to the bus. This is done synchronously, thus the call will block until a reply message arrives.

[Source]

# File lib/dbus/bus.rb, line 561
    def send_sync(m, &retc) # :yields: reply/return message
      return if m.nil? #check if somethings wrong
      send(m.marshall)
      @method_call_msgs[m.serial] = m
      @method_call_replies[m.serial] = retc

      retm = wait_for_message
      
      return if retm.nil? #check if somethings wrong
      
      process(retm)
      while @method_call_replies.has_key? m.serial
        retm = wait_for_message
        process(retm)
      end
    end

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

[Source]

# File lib/dbus/bus.rb, line 393
    def send_sync_or_async(message, &reply_handler)
      ret = nil
      if reply_handler.nil?
        send_sync(message) do |rmsg|
          if rmsg.is_a?(Error)
            raise rmsg
          else
            ret = rmsg.params
          end
        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
        send(message.marshall)
      end
      ret
    end

Retrieves the Service with the given name.

[Source]

# File lib/dbus/bus.rb, line 665
    def service(name)
      # The service might not exist at this time so we cannot really check
      # anything
      Service.new(name, self)
    end

Fill (append) the buffer from data that might be available on the socket.

[Source]

# File lib/dbus/bus.rb, line 494
    def update_buffer
      @buffer += @socket.read_nonblock(MSG_BUF_SIZE)  
    rescue EOFError
      raise                     # the caller expects it
    rescue Exception => e
      puts "Oops:", e
      raise if @is_tcp          # why?
      puts "WARNING: read_nonblock failed, falling back to .recv"
      @buffer += @socket.recv(MSG_BUF_SIZE)  
    end

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

[Source]

# File lib/dbus/bus.rb, line 543
    def wait_for_message
      if @socket.nil?
        puts "ERROR: Can't wait for messages, @socket is nil."
        return
      end
      ret = pop_message
      while ret == nil
        r, d, d = IO.select([@socket])
        if r and r[0] == @socket
          update_buffer
          ret = pop_message
        end
      end
      ret
    end

[Validate]