class DBus::Object
Exported object type
Exportable D-Bus object class¶ ↑
Objects that are going to be exported by a D-Bus service should inherit from this class. At the client side, use {ProxyObject}.
Attributes
The path of the object.
The service that the object is exported by.
Public Class Methods
TODO: borrow a proven implementation @param str [String] @return [String] @api private
# File lib/dbus/object.rb, line 334 def self.camelize(str) str.split(/_/).map(&:capitalize).join("") end
A read-write property using a pair of reader/writer methods (which must already exist). (To directly access an instance variable, use {.dbus_attr_accessor} instead)
Uses {.dbus_watcher} to set up the PropertiesChanged signal.
@param (see .dbus_attr_accessor) @return (see .dbus_attr_accessor)
# File lib/dbus/object.rb, line 178 def self.dbus_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) raise UndefinedInterface, ruby_name if @@cur_intf.nil? dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) property = Property.new(dbus_name, type, :readwrite, ruby_name: ruby_name) @@cur_intf.define(property) dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end
A read-write property accessing an instance variable. A combination of ‘attr_accessor` and {.dbus_accessor}.
PropertiesChanged signal will be emitted whenever ‘foo_bar=` is used but not when @foo_bar is written directly.
@param ruby_name [Symbol] :foo_bar is exposed as FooBar;
use dbus_name to override
@param type a signature like “s” or “a(uus)” or Type::STRING @param dbus_name [String] if not given it is made
by CamelCasing the ruby_name. foo_bar becomes FooBar to convert the Ruby convention to the DBus convention.
@param emits_changed_signal [true,false,:const,:invalidates]
see {EmitsChangedSignal}; if unspecified, ask the interface.
@return [void]
# File lib/dbus/object.rb, line 132 def self.dbus_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) attr_accessor(ruby_name) dbus_accessor(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end
A read-only property accessing an instance variable. A combination of ‘attr_reader` and {.dbus_reader}.
Whenever the property value gets changed from “inside” the object, you should emit the ‘PropertiesChanged` signal by calling {#dbus_properties_changed}.
dbus_properties_changed(interface_name, {dbus_name.to_s => value}, [])
or, omitting the value in the signal,
dbus_properties_changed(interface_name, {}, [dbus_name.to_s])
@param (see .dbus_attr_accessor) @return (see .dbus_attr_accessor)
# File lib/dbus/object.rb, line 153 def self.dbus_attr_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) attr_reader(ruby_name) dbus_reader(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end
A write-only property accessing an instance variable. A combination of ‘attr_writer` and {.dbus_writer}.
@param (see .dbus_attr_accessor) @return (see .dbus_attr_accessor)
# File lib/dbus/object.rb, line 164 def self.dbus_attr_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) attr_writer(ruby_name) dbus_writer(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end
Select (and create) the interface that the following defined methods belong to. @param name [String] interface name like “org.example.ManagerManager” @see dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-interface
# File lib/dbus/object.rb, line 80 def self.dbus_interface(name) @@intfs_mutex.synchronize do @@cur_intf = intfs[name] if !@@cur_intf @@cur_intf = Interface.new(name) # validates the name # As this is a mutable class_attr, we cannot use # self.intfs[name] = @@cur_intf # Hash#[]= # as that would modify parent class attr in place. # Using the setter lets a subclass have the new value # while the superclass keeps the old one. self.intfs = intfs.merge(name => @@cur_intf) end yield @@cur_intf = nil end end
Defines an exportable method on the object with the given name sym, prototype and the code in a block. @param prototype [Prototype]
# File lib/dbus/object.rb, line 290 def self.dbus_method(sym, prototype = "", &block) raise UndefinedInterface, sym if @@cur_intf.nil? @@cur_intf.define(Method.new(sym.to_s).from_prototype(prototype)) ruby_name = Object.make_method_name(@@cur_intf.name, sym.to_s) # ::Module#define_method(name) { body } define_method(ruby_name, &block) end
A read-only property accessing a reader method (which must already exist). (To directly access an instance variable, use {.dbus_attr_reader} instead)
At the D-Bus side the property is read only but it makes perfect sense to implement it with a read-write attr_accessor. In that case this method uses {.dbus_watcher} to set up the PropertiesChanged signal.
attr_accessor :foo_bar dbus_reader :foo_bar, "s"
If the property value should change by other means than its attr_writer, you should emit the ‘PropertiesChanged` signal by calling {#dbus_properties_changed}.
dbus_properties_changed(interface_name, {dbus_name.to_s => value}, [])
or, omitting the value in the signal,
dbus_properties_changed(interface_name, {}, [dbus_name.to_s])
@param (see .dbus_attr_accessor) @return (see .dbus_attr_accessor)
# File lib/dbus/object.rb, line 210 def self.dbus_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) raise UndefinedInterface, ruby_name if @@cur_intf.nil? dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) property = Property.new(dbus_name, type, :read, ruby_name: ruby_name) @@cur_intf.define(property) ruby_name_eq = "#{ruby_name}=".to_sym return unless method_defined?(ruby_name_eq) dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end
Defines a signal for the object with a given name sym and prototype.
# File lib/dbus/object.rb, line 310 def self.dbus_signal(sym, prototype = "") raise UndefinedInterface, sym if @@cur_intf.nil? cur_intf = @@cur_intf signal = Signal.new(sym.to_s).from_prototype(prototype) cur_intf.define(Signal.new(sym.to_s).from_prototype(prototype)) # ::Module#define_method(name) { body } define_method(sym.to_s) do |*args| emit(cur_intf, signal, *args) end end
Enables automatic sending of the PropertiesChanged signal. For ruby_name ‘foo_bar`, wrap `foo_bar=` so that it sends the signal for FooBar. The original version remains as `_original_foo_bar=`.
@param ruby_name [Symbol] :foo_bar and :foo_bar= both mean the same thing @param dbus_name [String] if not given it is made
by CamelCasing the ruby_name. foo_bar becomes FooBar to convert the Ruby convention to the DBus convention.
@param emits_changed_signal [true,false,:const,:invalidates]
see {EmitsChangedSignal}; if unspecified, ask the interface.
@return [void]
# File lib/dbus/object.rb, line 252 def self.dbus_watcher(ruby_name, dbus_name: nil, emits_changed_signal: nil) raise UndefinedInterface, ruby_name if @@cur_intf.nil? interface_name = @@cur_intf.name ruby_name = ruby_name.to_s.sub(/=$/, "").to_sym ruby_name_eq = "#{ruby_name}=".to_sym original_ruby_name_eq = "_original_#{ruby_name_eq}" dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) emits_changed_signal = EmitsChangedSignal.new(emits_changed_signal, interface: @@cur_intf) # the argument order is alias_method(new_name, existing_name) alias_method original_ruby_name_eq, ruby_name_eq define_method ruby_name_eq do |value| result = public_send(original_ruby_name_eq, value) case emits_changed_signal.value when true # signature: "interface:s, changed_props:a{sv}, invalidated_props:as" dbus_properties_changed(interface_name, { dbus_name.to_s => value }, []) when :invalidates dbus_properties_changed(interface_name, {}, [dbus_name.to_s]) when :const # Oh my, seeing a value change of a supposedly constant property. # Maybe should have raised at declaration time, don't make a fuss now. when false # Do nothing end result end end
A write-only property accessing a writer method (which must already exist). (To directly access an instance variable, use {.dbus_attr_writer} instead)
Uses {.dbus_watcher} to set up the PropertiesChanged signal.
@param (see .dbus_attr_accessor) @return (see .dbus_attr_accessor)
# File lib/dbus/object.rb, line 230 def self.dbus_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) raise UndefinedInterface, ruby_name if @@cur_intf.nil? dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) property = Property.new(dbus_name, type, :write, ruby_name: ruby_name) @@cur_intf.define(property) dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end
Declare the behavior of PropertiesChanged signal, common for all properties in this interface (individual properties may override it) @example
self.emits_changed_signal = :invalidates
@param [true,false,:const,:invalidates] value
# File lib/dbus/object.rb, line 111 def self.emits_changed_signal=(value) raise UndefinedInterface, :emits_changed_signal if @@cur_intf.nil? @@cur_intf.emits_changed_signal = EmitsChangedSignal.new(value) end
Make a D-Bus conventional name, CamelCased. @param ruby_name [String,Symbol] eg :do_something @param dbus_name [String,Symbol,nil] use this if given @return [Symbol] eg DoSomething
# File lib/dbus/object.rb, line 342 def self.make_dbus_name(ruby_name, dbus_name: nil) dbus_name ||= camelize(ruby_name.to_s) dbus_name.to_sym end
Helper method that returns a method name generated from the interface name intfname and method name methname. @api private
# File lib/dbus/object.rb, line 326 def self.make_method_name(intfname, methname) "#{intfname}%%#{methname}" end
Create a new object with a given path. Use Service#export
to export it.
# File lib/dbus/object.rb, line 37 def initialize(path) @path = path @service = nil end
Public Instance Methods
@param interface_name [String] @param property_name [String] @return [Property] @raise [DBus::Error] @api private
# File lib/dbus/object.rb, line 371 def dbus_lookup_property(interface_name, property_name) # what should happen for unknown properties # plasma: InvalidArgs (propname), UnknownInterface (interface) # systemd: UnknownProperty interface = intfs[interface_name] if !interface raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"), "Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found: no such interface" end property = interface.properties[property_name.to_sym] if !property raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"), "Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found" end property end
Use this instead of calling PropertiesChanged directly. This one considers not only the PC signature (which says that all property values are variants) but also the specific property type. @param interface_name [String] interface name like “org.example.ManagerManager” @param changed_props [Hash{String => ::Object}]
changed properties (D-Bus names) and their values.
@param invalidated_props [Array<String>]
names of properties whose changed value is not specified
# File lib/dbus/object.rb, line 355 def dbus_properties_changed(interface_name, changed_props, invalidated_props) typed_changed_props = changed_props.map do |dbus_name, value| property = dbus_lookup_property(interface_name, dbus_name) type = property.type typed_value = Data.make_typed(type, value) variant = Data::Variant.new(typed_value, member_type: type) [dbus_name, variant] end.to_h PropertiesChanged(interface_name, typed_changed_props, invalidated_props) end
Dispatch a message msg to call exported methods
# File lib/dbus/object.rb, line 43 def dispatch(msg) case msg.message_type when Message::METHOD_CALL reply = nil begin iface = intfs[msg.interface] if !iface raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"), "Interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist" end member_sym = msg.member.to_sym meth = iface.methods[member_sym] if !meth raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"), "Method \"#{msg.member}\" on interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist" end methname = Object.make_method_name(msg.interface, msg.member) retdata = method(methname).call(*msg.params) retdata = [*retdata] reply = Message.method_return(msg) rsigs = meth.rets.map(&:type) rsigs.zip(retdata).each do |rsig, rdata| reply.add_param(rsig, rdata) end rescue StandardError => e dbus_msg_exc = msg.annotate_exception(e) reply = ErrorMessage.from_exception(dbus_msg_exc).reply_to(msg) end @service.bus.message_queue.push(reply) end end
Emits a signal from the object with the given interface, signal sig and arguments args. @param intf [Interface] @param sig [Signal] @param args arguments for the signal
# File lib/dbus/object.rb, line 305 def emit(intf, sig, *args) @service.bus.emit(@service, self, intf, sig, *args) end