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
pop and push messages here
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”
# 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
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 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
@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
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
@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
@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
@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
@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
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
@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
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
@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
@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
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
@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 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