D-Bus main connection class
Main class that maintains a connection to a bus and can handle incoming and outgoing messages.
The buffer size for messages.
FIXME: describe the following names, flags and constants. See DBus spec for definition
The socket that is used to connect with the bus.
The unique name (by specification) of the message.
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 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
Asks bus to send us messages matching mr, and execute slot when received
# 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.
# 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(%r%(..)/) {|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.
# 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.
# 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 = "\11\\00\\00##{params['abstract']}" else sockaddr = "\00\\11\\00##{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.
# 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
# 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
# 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
# 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.
# 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.
# 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 #messages. Return the messages.
# 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.
# 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.
# 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.
# 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
# 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.
# 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.
# 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.
# 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
# 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.
# 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.
# 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.
# 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