ruby-dbus 0.18.0.beta5 → 0.18.0.beta8

Sign up to get free protection for your applications and to get access to all the features.
data/lib/dbus/object.rb CHANGED
@@ -102,6 +102,18 @@ module DBus
102
102
  end
103
103
  end
104
104
 
105
+ # Declare the behavior of PropertiesChanged signal,
106
+ # common for all properties in this interface
107
+ # (individual properties may override it)
108
+ # @example
109
+ # self.emits_changed_signal = :invalidates
110
+ # @param [true,false,:const,:invalidates] value
111
+ def self.emits_changed_signal=(value)
112
+ raise UndefinedInterface, :emits_changed_signal if @@cur_intf.nil?
113
+
114
+ @@cur_intf.emits_changed_signal = EmitsChangedSignal.new(value)
115
+ end
116
+
105
117
  # A read-write property accessing an instance variable.
106
118
  # A combination of `attr_accessor` and {.dbus_accessor}.
107
119
  #
@@ -114,11 +126,13 @@ module DBus
114
126
  # @param dbus_name [String] if not given it is made
115
127
  # by CamelCasing the ruby_name. foo_bar becomes FooBar
116
128
  # to convert the Ruby convention to the DBus convention.
129
+ # @param emits_changed_signal [true,false,:const,:invalidates]
130
+ # see {EmitsChangedSignal}; if unspecified, ask the interface.
117
131
  # @return [void]
118
- def self.dbus_attr_accessor(ruby_name, type, dbus_name: nil)
132
+ def self.dbus_attr_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
119
133
  attr_accessor(ruby_name)
120
134
 
121
- dbus_accessor(ruby_name, type, dbus_name: dbus_name)
135
+ dbus_accessor(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
122
136
  end
123
137
 
124
138
  # A read-only property accessing an instance variable.
@@ -126,19 +140,20 @@ module DBus
126
140
  #
127
141
  # Whenever the property value gets changed from "inside" the object,
128
142
  # you should emit the `PropertiesChanged` signal by calling
143
+ # {#dbus_properties_changed}.
129
144
  #
130
- # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {dbus_name.to_s => value}, [])
145
+ # dbus_properties_changed(interface_name, {dbus_name.to_s => value}, [])
131
146
  #
132
147
  # or, omitting the value in the signal,
133
148
  #
134
- # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {}, [dbus_name.to_s])
149
+ # dbus_properties_changed(interface_name, {}, [dbus_name.to_s])
135
150
  #
136
151
  # @param (see .dbus_attr_accessor)
137
152
  # @return (see .dbus_attr_accessor)
138
- def self.dbus_attr_reader(ruby_name, type, dbus_name: nil)
153
+ def self.dbus_attr_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
139
154
  attr_reader(ruby_name)
140
155
 
141
- dbus_reader(ruby_name, type, dbus_name: dbus_name)
156
+ dbus_reader(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
142
157
  end
143
158
 
144
159
  # A write-only property accessing an instance variable.
@@ -146,10 +161,10 @@ module DBus
146
161
  #
147
162
  # @param (see .dbus_attr_accessor)
148
163
  # @return (see .dbus_attr_accessor)
149
- def self.dbus_attr_writer(ruby_name, type, dbus_name: nil)
164
+ def self.dbus_attr_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
150
165
  attr_writer(ruby_name)
151
166
 
152
- dbus_writer(ruby_name, type, dbus_name: dbus_name)
167
+ dbus_writer(ruby_name, type, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
153
168
  end
154
169
 
155
170
  # A read-write property using a pair of reader/writer methods
@@ -160,36 +175,49 @@ module DBus
160
175
  #
161
176
  # @param (see .dbus_attr_accessor)
162
177
  # @return (see .dbus_attr_accessor)
163
- def self.dbus_accessor(ruby_name, type, dbus_name: nil)
178
+ def self.dbus_accessor(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
164
179
  raise UndefinedInterface, ruby_name if @@cur_intf.nil?
165
180
 
166
181
  dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
167
182
  property = Property.new(dbus_name, type, :readwrite, ruby_name: ruby_name)
168
183
  @@cur_intf.define(property)
169
184
 
170
- dbus_watcher(ruby_name, dbus_name: dbus_name)
185
+ dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
171
186
  end
172
187
 
173
188
  # A read-only property accessing a reader method (which must already exist).
174
189
  # (To directly access an instance variable, use {.dbus_attr_reader} instead)
175
190
  #
176
- # Whenever the property value gets changed from "inside" the object,
191
+ # At the D-Bus side the property is read only but it makes perfect sense to
192
+ # implement it with a read-write attr_accessor. In that case this method
193
+ # uses {.dbus_watcher} to set up the PropertiesChanged signal.
194
+ #
195
+ # attr_accessor :foo_bar
196
+ # dbus_reader :foo_bar, "s"
197
+ #
198
+ # If the property value should change by other means than its attr_writer,
177
199
  # you should emit the `PropertiesChanged` signal by calling
200
+ # {#dbus_properties_changed}.
178
201
  #
179
- # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {dbus_name.to_s => value}, [])
202
+ # dbus_properties_changed(interface_name, {dbus_name.to_s => value}, [])
180
203
  #
181
204
  # or, omitting the value in the signal,
182
205
  #
183
- # object[DBus::PROPERTY_INTERFACE].PropertiesChanged(interface_name, {}, [dbus_name.to_s])
206
+ # dbus_properties_changed(interface_name, {}, [dbus_name.to_s])
184
207
  #
185
208
  # @param (see .dbus_attr_accessor)
186
209
  # @return (see .dbus_attr_accessor)
187
- def self.dbus_reader(ruby_name, type, dbus_name: nil)
210
+ def self.dbus_reader(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
188
211
  raise UndefinedInterface, ruby_name if @@cur_intf.nil?
189
212
 
190
213
  dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
191
214
  property = Property.new(dbus_name, type, :read, ruby_name: ruby_name)
192
215
  @@cur_intf.define(property)
216
+
217
+ ruby_name_eq = "#{ruby_name}=".to_sym
218
+ return unless method_defined?(ruby_name_eq)
219
+
220
+ dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
193
221
  end
194
222
 
195
223
  # A write-only property accessing a writer method (which must already exist).
@@ -199,14 +227,14 @@ module DBus
199
227
  #
200
228
  # @param (see .dbus_attr_accessor)
201
229
  # @return (see .dbus_attr_accessor)
202
- def self.dbus_writer(ruby_name, type, dbus_name: nil)
230
+ def self.dbus_writer(ruby_name, type, dbus_name: nil, emits_changed_signal: nil)
203
231
  raise UndefinedInterface, ruby_name if @@cur_intf.nil?
204
232
 
205
233
  dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
206
234
  property = Property.new(dbus_name, type, :write, ruby_name: ruby_name)
207
235
  @@cur_intf.define(property)
208
236
 
209
- dbus_watcher(ruby_name, dbus_name: dbus_name)
237
+ dbus_watcher(ruby_name, dbus_name: dbus_name, emits_changed_signal: emits_changed_signal)
210
238
  end
211
239
 
212
240
  # Enables automatic sending of the PropertiesChanged signal.
@@ -218,11 +246,13 @@ module DBus
218
246
  # @param dbus_name [String] if not given it is made
219
247
  # by CamelCasing the ruby_name. foo_bar becomes FooBar
220
248
  # to convert the Ruby convention to the DBus convention.
249
+ # @param emits_changed_signal [true,false,:const,:invalidates]
250
+ # see {EmitsChangedSignal}; if unspecified, ask the interface.
221
251
  # @return [void]
222
- def self.dbus_watcher(ruby_name, dbus_name: nil)
252
+ def self.dbus_watcher(ruby_name, dbus_name: nil, emits_changed_signal: nil)
223
253
  raise UndefinedInterface, ruby_name if @@cur_intf.nil?
224
254
 
225
- cur_intf = @@cur_intf
255
+ interface_name = @@cur_intf.name
226
256
 
227
257
  ruby_name = ruby_name.to_s.sub(/=$/, "").to_sym
228
258
  ruby_name_eq = "#{ruby_name}=".to_sym
@@ -230,14 +260,27 @@ module DBus
230
260
 
231
261
  dbus_name = make_dbus_name(ruby_name, dbus_name: dbus_name)
232
262
 
263
+ emits_changed_signal = EmitsChangedSignal.new(emits_changed_signal, interface: @@cur_intf)
264
+
233
265
  # the argument order is alias_method(new_name, existing_name)
234
266
  alias_method original_ruby_name_eq, ruby_name_eq
235
267
  define_method ruby_name_eq do |value|
236
- public_send(original_ruby_name_eq, value)
268
+ result = public_send(original_ruby_name_eq, value)
269
+
270
+ case emits_changed_signal.value
271
+ when true
272
+ # signature: "interface:s, changed_props:a{sv}, invalidated_props:as"
273
+ dbus_properties_changed(interface_name, { dbus_name.to_s => value }, [])
274
+ when :invalidates
275
+ dbus_properties_changed(interface_name, {}, [dbus_name.to_s])
276
+ when :const
277
+ # Oh my, seeing a value change of a supposedly constant property.
278
+ # Maybe should have raised at declaration time, don't make a fuss now.
279
+ when false
280
+ # Do nothing
281
+ end
237
282
 
238
- # TODO: respect EmitsChangedSignal to use invalidated_properties instead
239
- # PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
240
- PropertiesChanged(cur_intf.name, { dbus_name.to_s => value }, [])
283
+ result
241
284
  end
242
285
  end
243
286
 
@@ -301,6 +344,25 @@ module DBus
301
344
  dbus_name.to_sym
302
345
  end
303
346
 
347
+ # Use this instead of calling PropertiesChanged directly. This one
348
+ # considers not only the PC signature (which says that all property values
349
+ # are variants) but also the specific property type.
350
+ # @param interface_name [String] interface name like "org.example.ManagerManager"
351
+ # @param changed_props [Hash{String => ::Object}]
352
+ # changed properties (D-Bus names) and their values.
353
+ # @param invalidated_props [Array<String>]
354
+ # names of properties whose changed value is not specified
355
+ def dbus_properties_changed(interface_name, changed_props, invalidated_props)
356
+ typed_changed_props = changed_props.map do |dbus_name, value|
357
+ property = dbus_lookup_property(interface_name, dbus_name)
358
+ type = property.type
359
+ typed_value = Data.make_typed(type, value)
360
+ variant = Data::Variant.new(typed_value, member_type: type)
361
+ [dbus_name, variant]
362
+ end.to_h
363
+ PropertiesChanged(interface_name, typed_changed_props, invalidated_props)
364
+ end
365
+
304
366
  # @param interface_name [String]
305
367
  # @param property_name [String]
306
368
  # @return [Property]
@@ -29,11 +29,13 @@ module DBus
29
29
  # @param pobj [ProxyObject]
30
30
  # @param xml [String]
31
31
  def self.introspect_into(pobj, xml)
32
+ # intfs [Array<Interface>], subnodes [Array<String>]
32
33
  intfs, pobj.subnodes = IntrospectXMLParser.new(xml).parse
33
34
  intfs.each do |i|
34
35
  poi = ProxyObjectInterface.new(pobj, i.name)
35
36
  i.methods.each_value { |m| poi.define(m) }
36
37
  i.signals.each_value { |s| poi.define(s) }
38
+ i.properties.each_value { |p| poi.define(p) }
37
39
  pobj[i.name] = poi
38
40
  end
39
41
  pobj.introspected = true
@@ -15,13 +15,16 @@ module DBus
15
15
  # A class similar to the normal Interface used as a proxy for remote
16
16
  # object interfaces.
17
17
  class ProxyObjectInterface
18
- # The proxied methods contained in the interface.
19
- attr_accessor :methods
20
- # The proxied signals contained in the interface.
21
- attr_accessor :signals
22
- # The proxy object to which this interface belongs.
18
+ # @return [Hash{String => DBus::Method}]
19
+ attr_reader :methods
20
+ # @return [Hash{String => Signal}]
21
+ attr_reader :signals
22
+ # @return [Hash{Symbol => Property}]
23
+ attr_reader :properties
24
+
25
+ # @return [ProxyObject] The proxy object to which this interface belongs.
23
26
  attr_reader :object
24
- # The name of the interface.
27
+ # @return [String] The name of the interface.
25
28
  attr_reader :name
26
29
 
27
30
  # Creates a new proxy interface for the given proxy _object_
@@ -31,6 +34,7 @@ module DBus
31
34
  @name = name
32
35
  @methods = {}
33
36
  @signals = {}
37
+ @properties = {}
34
38
  end
35
39
 
36
40
  # Returns the string representation of the interface (the name).
@@ -81,13 +85,21 @@ module DBus
81
85
  @signals[sig.name] = sig
82
86
  end
83
87
 
88
+ # @param prop [Property]
89
+ def define_property_from_descriptor(prop)
90
+ @properties[prop.name] = prop
91
+ end
92
+
84
93
  # Defines a signal or method based on the descriptor _ifc_el_.
94
+ # @param ifc_el [DBus::Method,Signal,Property]
85
95
  def define(ifc_el)
86
96
  case ifc_el
87
97
  when Method
88
98
  define_method_from_descriptor(ifc_el)
89
99
  when Signal
90
100
  define_signal_from_descriptor(ifc_el)
101
+ when Property
102
+ define_property_from_descriptor(ifc_el)
91
103
  end
92
104
  end
93
105
 
@@ -129,10 +141,26 @@ module DBus
129
141
  end
130
142
 
131
143
  # Write a property.
132
- # @param propname [String]
144
+ # @param property_name [String]
133
145
  # @param value [Object]
134
- def []=(propname, value)
135
- object[PROPERTY_INTERFACE].Set(name, propname, value)
146
+ def []=(property_name, value)
147
+ property = properties[property_name.to_sym]
148
+ if !property
149
+ raise DBus.error("org.freedesktop.DBus.Error.UnknownProperty"),
150
+ "Property '#{name}.#{property_name}' (on object '#{object.path}') not found"
151
+ end
152
+
153
+ case value
154
+ # accommodate former need to explicitly make a variant with the right type
155
+ when Data::Variant
156
+ variant = value
157
+ else
158
+ type = property.type
159
+ typed_value = Data.make_typed(type, value)
160
+ variant = Data::Variant.new(typed_value, member_type: type)
161
+ end
162
+
163
+ object[PROPERTY_INTERFACE].Set(name, property_name, variant)
136
164
  end
137
165
 
138
166
  # Read all properties at once, as a hash.
data/lib/dbus/type.rb CHANGED
@@ -107,6 +107,29 @@ module DBus
107
107
  freeze
108
108
  end
109
109
 
110
+ # A Type is equal to
111
+ # - another Type with the same string representation
112
+ # - a String ({SingleCompleteType}) describing the type
113
+ def ==(other)
114
+ case other
115
+ when ::String
116
+ to_s == other
117
+ else
118
+ eql?(other)
119
+ end
120
+ end
121
+
122
+ # A Type is eql? to
123
+ # - another Type with the same string representation
124
+ #
125
+ # Hash key equality
126
+ # See https://ruby-doc.org/core-3.0.0/Object.html#method-i-eql-3F
127
+ def eql?(other)
128
+ return false unless other.is_a?(Type)
129
+
130
+ @sigtype == other.sigtype && @members == other.members
131
+ end
132
+
110
133
  # Return the required alignment for the type.
111
134
  def alignment
112
135
  TYPE_MAPPING[@sigtype].last
@@ -157,7 +180,7 @@ module DBus
157
180
 
158
181
  def inspect
159
182
  s = TYPE_MAPPING[@sigtype].first
160
- if [STRUCT, ARRAY].member?(@sigtype)
183
+ if [STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype)
161
184
  s += ": #{@members.inspect}"
162
185
  end
163
186
  s
@@ -392,8 +415,9 @@ module DBus
392
415
  # @param string_type [SingleCompleteType]
393
416
  # @param value [::Object]
394
417
  # @return [Array(DBus::Type::Type,::Object)]
418
+ # @deprecated Use {Data::Variant#initialize} instead
395
419
  def variant(string_type, value)
396
- [type(string_type), value]
420
+ Data::Variant.new(value, member_type: string_type)
397
421
  end
398
422
  module_function :variant
399
423
  end
data/lib/dbus/xml.rb CHANGED
@@ -134,6 +134,10 @@ module DBus
134
134
  parse_methsig(se, s)
135
135
  i << s
136
136
  end
137
+ e.each("property") do |pe|
138
+ p = Property.from_xml(pe)
139
+ i << p
140
+ end
137
141
  end
138
142
  d = Time.now - t
139
143
  if d > 2
data/lib/dbus.rb CHANGED
@@ -15,6 +15,7 @@ require_relative "dbus/auth"
15
15
  require_relative "dbus/bus"
16
16
  require_relative "dbus/bus_name"
17
17
  require_relative "dbus/data"
18
+ require_relative "dbus/emits_changed_signal"
18
19
  require_relative "dbus/error"
19
20
  require_relative "dbus/introspect"
20
21
  require_relative "dbus/logger"