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

path[R]

The path of the object.

service[W]

The service that the object is exported by.

Public Class Methods

camelize(str) click to toggle source

TODO: borrow a proven implementation @param str [String] @return [String] @api private

# File lib/dbus/object.rb, line 338
def self.camelize(str)
  str.split(/_/).map(&:capitalize).join("")
end
dbus_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil) click to toggle source

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 182
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) click to toggle source

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, line 136
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) click to toggle source

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 157
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) click to toggle source

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 168
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) { || ... } click to toggle source

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
    begin
      yield
    ensure
      @@cur_intf = nil
    end
  end
end
dbus_method(sym, prototype = "", &block) click to toggle source

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 294
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) click to toggle source

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 214
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_signal(sym, prototype = "") click to toggle source

Defines a signal for the object with a given name sym and prototype.

# File lib/dbus/object.rb, line 314
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
dbus_watcher(ruby_name, dbus_name: nil, emits_changed_signal: nil) click to toggle source

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 256
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) click to toggle source

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 234
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) click to toggle source

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 114
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) click to toggle source

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 346
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) click to toggle source

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 330
def self.make_method_name(intfname, methname)
  "#{intfname}%%#{methname}"
end
new(path) click to toggle source

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

dbus_lookup_property(interface_name, property_name) click to toggle source

@param interface_name [String] @param property_name [String] @return [Property] @raise [DBus::Error] @api private

# File lib/dbus/object.rb, line 375
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) click to toggle source

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 359
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) click to toggle source

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
emit(intf, sig, *args) click to toggle source

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 309
def emit(intf, sig, *args)
  @service.bus.emit(@service, self, intf, sig, *args)
end
interfaces_and_properties() click to toggle source

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, line 403
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