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
@return [ObjectPath] The path of the object.
Public Class Methods
TODO: borrow a proven implementation @param str [String] @return [String] @api private
# File lib/dbus/object.rb 375 def self.camelize(str) 376 str.split(/_/).map(&:capitalize).join("") 377 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 213 def self.dbus_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) 214 raise UndefinedInterface, ruby_name if @@cur_intf.nil? 215 216 dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) 217 property = Property.new(dbus_name, type, :readwrite, ruby_name: ruby_name) 218 @@cur_intf.define(property) 219 220 dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) 221 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 [Type,SingleCompleteType]
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 153 def self.dbus_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) 154 attr_accessor(ruby_name) 155 156 dbus_accessor(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) 157 end
A read-only property accessing an instance variable. A combination of ‘attr_reader` and {.dbus_reader}.
You may be instead looking for a variant which is read-write from the Ruby side: {.dbus_reader_attr_accessor}.
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 188 def self.dbus_attr_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) 189 attr_reader(ruby_name) 190 191 dbus_reader(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) 192 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 199 def self.dbus_attr_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) 200 attr_writer(ruby_name) 201 202 dbus_writer(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) 203 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 97 def self.dbus_interface(name) 98 @@intfs_mutex.synchronize do 99 @@cur_intf = intfs[name] 100 if !@@cur_intf 101 @@cur_intf = Interface.new(name) # validates the name 102 # As this is a mutable class_attr, we cannot use 103 # self.intfs[name] = @@cur_intf # Hash#[]= 104 # as that would modify parent class attr in place. 105 # Using the setter lets a subclass have the new value 106 # while the superclass keeps the old one. 107 self.intfs = intfs.merge(name => @@cur_intf) 108 end 109 begin 110 yield 111 ensure 112 @@cur_intf = nil 113 end 114 end 115 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 329 def self.dbus_method(sym, prototype = "", &block) 330 raise UndefinedInterface, sym if @@cur_intf.nil? 331 332 @@cur_intf.define(Method.new(sym.to_s).from_prototype(prototype)) 333 334 ruby_name = Object.make_method_name(@@cur_intf.name, sym.to_s) 335 # ::Module#define_method(name) { body } 336 define_method(ruby_name, &block) 337 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"
The above two declarations have a shorthand:
dbus_reader_attr_accessor :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 249 def self.dbus_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) 250 raise UndefinedInterface, ruby_name if @@cur_intf.nil? 251 252 dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) 253 property = Property.new(dbus_name, type, :read, ruby_name: ruby_name) 254 @@cur_intf.define(property) 255 256 ruby_name_eq = :"#{ruby_name}=" 257 return unless method_defined?(ruby_name_eq) 258 259 dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) 260 end
A read-only property accessing a read-write instance variable. A combination of ‘attr_accessor` and {.dbus_reader}.
@param (see .dbus_attr_accessor) @return (see .dbus_attr_accessor)
# File lib/dbus/object.rb 164 def self.dbus_reader_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) 165 attr_accessor(ruby_name) 166 167 dbus_reader(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) 168 end
Defines a signal for the object with a given name sym and prototype.
# File lib/dbus/object.rb 351 def self.dbus_signal(sym, prototype = "") 352 raise UndefinedInterface, sym if @@cur_intf.nil? 353 354 cur_intf = @@cur_intf 355 signal = Signal.new(sym.to_s).from_prototype(prototype) 356 cur_intf.define(signal) 357 358 # ::Module#define_method(name) { body } 359 define_method(sym.to_s) do |*args| 360 emit(cur_intf, signal, *args) 361 end 362 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 291 def self.dbus_watcher(ruby_name, dbus_name: nil, emits_changed_signal: nil) 292 raise UndefinedInterface, ruby_name if @@cur_intf.nil? 293 294 interface_name = @@cur_intf.name 295 296 ruby_name = ruby_name.to_s.sub(/=$/, "").to_sym 297 ruby_name_eq = :"#{ruby_name}=" 298 original_ruby_name_eq = "_original_#{ruby_name_eq}" 299 300 dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) 301 302 emits_changed_signal = EmitsChangedSignal.new(emits_changed_signal, interface: @@cur_intf) 303 304 # the argument order is alias_method(new_name, existing_name) 305 alias_method original_ruby_name_eq, ruby_name_eq 306 define_method ruby_name_eq do |value| 307 result = public_send(original_ruby_name_eq, value) 308 309 case emits_changed_signal.value 310 when true 311 # signature: "interface:s, changed_props:a{sv}, invalidated_props:as" 312 dbus_properties_changed(interface_name, { dbus_name.to_s => value }, []) 313 when :invalidates 314 dbus_properties_changed(interface_name, {}, [dbus_name.to_s]) 315 when :const 316 # Oh my, seeing a value change of a supposedly constant property. 317 # Maybe should have raised at declaration time, don't make a fuss now. 318 when false 319 # Do nothing 320 end 321 322 result 323 end 324 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 269 def self.dbus_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) 270 raise UndefinedInterface, ruby_name if @@cur_intf.nil? 271 272 dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name) 273 property = Property.new(dbus_name, type, :write, ruby_name: ruby_name) 274 @@cur_intf.define(property) 275 276 dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) 277 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 131 def self.emits_changed_signal=(value) 132 raise UndefinedInterface, :emits_changed_signal if @@cur_intf.nil? 133 134 @@cur_intf.emits_changed_signal = EmitsChangedSignal.new(value) 135 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 383 def self.make_dbus_name(ruby_name, dbus_name: nil) 384 dbus_name ||= camelize(ruby_name.to_s) 385 dbus_name.to_sym 386 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 367 def self.make_method_name(intfname, methname) 368 "#{intfname}%%#{methname}" 369 end
Create a new object with a given path. Use ObjectServer#export to export it. @param path [ObjectPath] The path of the object.
# File lib/dbus/object.rb 35 def initialize(path) 36 @path = path 37 # TODO: what parts of our API are supposed to work before we're exported? 38 self.object_server = nil 39 end
Public Instance Methods
@param interface_name [String] @param property_name [String] @return [Property] @raise [DBus::Error] @api private
# File lib/dbus/object.rb 412 def dbus_lookup_property(interface_name, property_name) 413 # what should happen for unknown properties 414 # plasma: InvalidArgs (propname), UnknownInterface (interface) 415 # systemd: UnknownProperty 416 interface = intfs[interface_name] 417 if !interface 418 raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"), 419 "Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found: no such interface" 420 end 421 422 property = interface.properties[property_name.to_sym] 423 if !property 424 raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"), 425 "Property '#{interface_name}.#{property_name}' (on object '#{@path}') not found" 426 end 427 428 property 429 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 396 def dbus_properties_changed(interface_name, changed_props, invalidated_props) 397 typed_changed_props = changed_props.map do |dbus_name, value| 398 property = dbus_lookup_property(interface_name, dbus_name) 399 type = property.type 400 typed_value = Data.make_typed(type, value) 401 variant = Data::Variant.new(typed_value, member_type: type) 402 [dbus_name, variant] 403 end.to_h 404 PropertiesChanged(interface_name, typed_changed_props, invalidated_props) 405 end
Dispatch a message msg to call exported methods @param msg [Message] only METHOD_CALLS do something @api private
# File lib/dbus/object.rb 58 def dispatch(msg) 59 case msg.message_type 60 when Message::METHOD_CALL 61 reply = nil 62 begin 63 iface = intfs[msg.interface] 64 if !iface 65 raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"), 66 "Interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist" 67 end 68 member_sym = msg.member.to_sym 69 meth = iface.methods[member_sym] 70 if !meth 71 raise DBus.error("org.freedesktop.DBus.Error.UnknownMethod"), 72 "Method \"#{msg.member}\" on interface \"#{msg.interface}\" of object \"#{msg.path}\" doesn't exist" 73 end 74 methname = Object.make_method_name(msg.interface, msg.member) 75 retdata = method(methname).call(*msg.params) 76 retdata = [*retdata] 77 78 reply = Message.method_return(msg) 79 rsigs = meth.rets.map(&:type) 80 rsigs.zip(retdata).each do |rsig, rdata| 81 reply.add_param(rsig, rdata) 82 end 83 rescue StandardError => e 84 dbus_msg_exc = msg.annotate_exception(e) 85 reply = ErrorMessage.from_exception(dbus_msg_exc).reply_to(msg) 86 end 87 # TODO: this method chain is too long, 88 # we should probably just return reply [Message] like we get a [Message] 89 object_server.connection.message_queue.push(reply) 90 end 91 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 344 def emit(intf, sig, *args) 345 raise "Cannot emit signal #{intf.name}.#{sig.name} before #{path} is exported" if object_server.nil? 346 347 object_server.connection.emit(nil, self, intf, sig, *args) 348 end
Generates information about interfaces and properties of the object
Returns a hash containing interfaces names as keys. Each value is the same hash that would be returned by the org.freedesktop.DBus.Properties.GetAll() method for that combination of object path and interface. If an interface has no properties, the empty hash is returned.
@return [Hash{String => Hash{String => Data::Base}}] interface -> property -> value
# File lib/dbus/object.rb 440 def interfaces_and_properties 441 get_all_method = self.class.make_method_name("org.freedesktop.DBus.Properties", :GetAll) 442 443 intfs.keys.each_with_object({}) do |interface, hash| 444 hash[interface] = public_send(get_all_method, interface).first 445 end 446 end
@return [ObjectServer] the server the object is exported by
# File lib/dbus/object.rb 42 def object_server 43 # tests may mock the old ivar 44 @object_server || @service 45 end
@param server [ObjectServer] the server the object is exported by @note only the server itself should call this in its export/#unexport
# File lib/dbus/object.rb 49 def object_server=(server) 50 # until v0.22.1 there was attr_writer :service 51 # so subclasses only could use @service 52 @object_server = @service = server 53 end