Class: DBus::Object
- Inherits:
-
Object
- Object
- DBus::Object
- Defined in:
- lib/dbus/object.rb
Overview
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.
Defined Under Namespace
Classes: UndefinedInterface
Constant Summary collapse
- @@cur_intf =
Interface
nil
- @@intfs_mutex =
Mutex.new
Instance Attribute Summary collapse
-
#path ⇒ ObjectPath
readonly
The path of the object.
Class Method Summary collapse
-
.camelize(str) ⇒ String
private
TODO: borrow a proven implementation.
-
.dbus_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
A read-write property using a pair of reader/writer methods (which must already exist).
-
.dbus_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
A read-write property accessing an instance variable.
-
.dbus_attr_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
A read-only property accessing an instance variable.
-
.dbus_attr_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
A write-only property accessing an instance variable.
-
.dbus_interface(name) ⇒ Object
Select (and create) the interface that the following defined methods belong to.
-
.dbus_method(sym, prototype = "", &block) ⇒ Object
Defines an exportable method on the object with the given name sym, prototype and the code in a block.
-
.dbus_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
A read-only property accessing a reader method (which must already exist).
-
.dbus_reader_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
A read-only property accessing a read-write instance variable.
-
.dbus_signal(sym, prototype = "") ⇒ Object
Defines a signal for the object with a given name sym and prototype.
-
.dbus_watcher(ruby_name, dbus_name: nil, emits_changed_signal: nil) ⇒ void
Enables automatic sending of the PropertiesChanged signal.
-
.dbus_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
A write-only property accessing a writer method (which must already exist).
-
.emits_changed_signal=(value) ⇒ Object
Declare the behavior of PropertiesChanged signal, common for all properties in this interface (individual properties may override it).
-
.make_dbus_name(ruby_name, dbus_name: nil) ⇒ Symbol
Make a D-Bus conventional name, CamelCased.
-
.make_method_name(intfname, methname) ⇒ Object
private
Helper method that returns a method name generated from the interface name intfname and method name methname.
Instance Method Summary collapse
- #dbus_lookup_property(interface_name, property_name) ⇒ Property private
-
#dbus_properties_changed(interface_name, changed_props, invalidated_props) ⇒ Object
Use this instead of calling PropertiesChanged directly.
-
#dispatch(msg) ⇒ Object
private
Dispatch a message msg to call exported methods.
-
#emit(intf, sig, *args) ⇒ Object
Emits a signal from the object with the given interface, signal sig and arguments args.
-
#initialize(path) ⇒ Object
constructor
Create a new object with a given path.
-
#interfaces_and_properties ⇒ Hash{String => Hash{String => Data::Base}}
Generates information about interfaces and properties of the object.
-
#object_server ⇒ ObjectServer
The server the object is exported by.
- #object_server=(server) ⇒ Object
Constructor Details
#initialize(path) ⇒ Object
Create a new object with a given path. Use ObjectServer#export to export it.
35 36 37 38 39 |
# File 'lib/dbus/object.rb', line 35 def initialize(path) @path = path # TODO: what parts of our API are supposed to work before we're exported? self.object_server = nil end |
Instance Attribute Details
#path ⇒ ObjectPath (readonly)
Returns The path of the object.
23 24 25 |
# File 'lib/dbus/object.rb', line 23 def path @path end |
Class Method Details
.camelize(str) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
TODO: borrow a proven implementation
375 376 377 |
# File 'lib/dbus/object.rb', line 375 def self.camelize(str) str.split(/_/).map(&:capitalize).join("") end |
.dbus_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
This method returns an undefined value.
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.
213 214 215 216 217 218 219 220 221 |
# File 'lib/dbus/object.rb', line 213 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 |
.dbus_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
This method returns an undefined value.
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.
153 154 155 156 157 |
# File 'lib/dbus/object.rb', line 153 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 |
.dbus_attr_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
This method returns an undefined value.
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])
188 189 190 191 192 |
# File 'lib/dbus/object.rb', line 188 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 |
.dbus_attr_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
This method returns an undefined value.
A write-only property accessing an instance variable. A combination of ‘attr_writer` and dbus_writer.
199 200 201 202 203 |
# File 'lib/dbus/object.rb', line 199 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 |
.dbus_interface(name) ⇒ Object
Select (and create) the interface that the following defined methods belong to.
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/dbus/object.rb', line 97 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 begin yield ensure @@cur_intf = nil end end end |
.dbus_method(sym, prototype = "", &block) ⇒ Object
Defines an exportable method on the object with the given name sym, prototype and the code in a block.
329 330 331 332 333 334 335 336 337 |
# File 'lib/dbus/object.rb', line 329 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 |
.dbus_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
This method returns an undefined value.
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])
249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/dbus/object.rb', line 249 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 |
.dbus_reader_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
This method returns an undefined value.
A read-only property accessing a read-write instance variable. A combination of ‘attr_accessor` and dbus_reader.
164 165 166 167 168 |
# File 'lib/dbus/object.rb', line 164 def self.dbus_reader_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) attr_accessor(ruby_name) dbus_reader(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal) end |
.dbus_signal(sym, prototype = "") ⇒ Object
Defines a signal for the object with a given name sym and prototype.
351 352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/dbus/object.rb', line 351 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) # ::Module#define_method(name) { body } define_method(sym.to_s) do |*args| emit(cur_intf, signal, *args) end end |
.dbus_watcher(ruby_name, dbus_name: nil, emits_changed_signal: nil) ⇒ void
This method returns an undefined value.
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=`.
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/dbus/object.rb', line 291 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 |
.dbus_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) ⇒ void
This method returns an undefined value.
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.
269 270 271 272 273 274 275 276 277 |
# File 'lib/dbus/object.rb', line 269 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 |
.emits_changed_signal=(value) ⇒ Object
Declare the behavior of PropertiesChanged signal, common for all properties in this interface (individual properties may override it)
131 132 133 134 135 |
# File 'lib/dbus/object.rb', line 131 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_dbus_name(ruby_name, dbus_name: nil) ⇒ Symbol
Make a D-Bus conventional name, CamelCased.
383 384 385 386 |
# File 'lib/dbus/object.rb', line 383 def self.make_dbus_name(ruby_name, dbus_name: nil) dbus_name ||= camelize(ruby_name.to_s) dbus_name.to_sym end |
.make_method_name(intfname, methname) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Helper method that returns a method name generated from the interface name intfname and method name methname.
367 368 369 |
# File 'lib/dbus/object.rb', line 367 def self.make_method_name(intfname, methname) "#{intfname}%%#{methname}" end |
Instance Method Details
#dbus_lookup_property(interface_name, property_name) ⇒ Property
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 |
# File 'lib/dbus/object.rb', line 412 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 |
#dbus_properties_changed(interface_name, changed_props, invalidated_props) ⇒ Object
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.
396 397 398 399 400 401 402 403 404 405 |
# File 'lib/dbus/object.rb', line 396 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(msg) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Dispatch a message msg to call exported methods
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/dbus/object.rb', line 58 def dispatch(msg) case msg. 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 # TODO: this method chain is too long, # we should probably just return reply [Message] like we get a [Message] object_server.connection..push(reply) end end |
#emit(intf, sig, *args) ⇒ Object
Emits a signal from the object with the given interface, signal sig and arguments args.
344 345 346 347 348 |
# File 'lib/dbus/object.rb', line 344 def emit(intf, sig, *args) raise "Cannot emit signal #{intf.name}.#{sig.name} before #{path} is exported" if object_server.nil? object_server.connection.emit(nil, self, intf, sig, *args) end |
#interfaces_and_properties ⇒ Hash{String => Hash{String => Data::Base}}
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.
440 441 442 443 444 445 446 |
# File 'lib/dbus/object.rb', line 440 def interfaces_and_properties get_all_method = self.class.make_method_name("org.freedesktop.DBus.Properties", :GetAll) intfs.keys.each_with_object({}) do |interface, hash| hash[interface] = public_send(get_all_method, interface).first end end |
#object_server ⇒ ObjectServer
Returns the server the object is exported by.
42 43 44 45 |
# File 'lib/dbus/object.rb', line 42 def object_server # tests may mock the old ivar @object_server || @service end |
#object_server=(server) ⇒ Object
only the server itself should call this in its #export/#unexport
49 50 51 52 53 |
# File 'lib/dbus/object.rb', line 49 def object_server=(server) # until v0.22.1 there was attr_writer :service # so subclasses only could use @service @object_server = @service = server end |