ruby-dbus 0.18.0.beta6 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54a45385340217271e3fb445cddf6afcddb9892c4bf75b503e7859390f4d2ed7
4
- data.tar.gz: 3c4f71745992c5afece87e34909e67f451717c9398ac6efb6235cc64ad1d1b0c
3
+ metadata.gz: 07f1f2fd26d13923b2601c9c3f29ccf57afd42c0348b937bceea0804eece6d66
4
+ data.tar.gz: 5ad80792234951e7ed422b6eba16e7f6e55b41f24a116ef40afaa7f32cdeef74
5
5
  SHA512:
6
- metadata.gz: 864aaaaa867ea04247515d875d836560a558b7a3d780712a41dcd52214d95e47e857d640d66a3573eaf4df778a0320f89ad2c59918c005a2fc80adb8a63074cb
7
- data.tar.gz: cabd9d699d8b7aad7c5f77a83aa07389b59c11b34b1219822909a031774b078d0bf001a1bce9bb5912be42caa4adba7608906ebc76dde57ff9f06d13afd7850f
6
+ metadata.gz: acbe18b2d9695f8959ef6d51a3871e989600291076d6eef870a9215768ed6c7afd599d3af2741e13a733fe2f5cc9f75fdf075a130cafd0a9144b4ca00bd80dc9
7
+ data.tar.gz: 0363375dd8c4465aa2650649d0bd62a4f95431b78178edb624a84f5326e395476e0c54e4755b27fedc86caf94b7c059abbdc15af28832417ec1d3e4770ea9ae0
data/NEWS.md CHANGED
@@ -2,6 +2,74 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## Ruby D-Bus 0.18.1 - 2022-07-13
6
+
7
+ No changes since 0.18.0.beta8.
8
+ Repeating the most important changes since 0.17.0:
9
+
10
+ API:
11
+ * Introduced DBus::Data classes, use them in Properties.Get,
12
+ Properties.GetAll to return correct types as declared ([#97][]).
13
+ * Introduced Object#dbus_properties_changed to send correctly typed property
14
+ values ([#115][]). Avoid calling PropertiesChanged directly as it will
15
+ guess the types.
16
+ * Service side `emits_changed_signal` to control emission of
17
+ PropertiesChanged: can be assigned within `dbus_interface` or as an option
18
+ when declaring properties ([#117][]).
19
+ * DBus.variant(type, value) is deprecated in favor of
20
+ Data::Variant.new(value, member_type:)
21
+ * Added type factories
22
+ * Type::Array[type]
23
+ * Type::Hash[key_type, value_type]
24
+ * Type::Struct[type1, type2...]
25
+
26
+ Bug fixes:
27
+ * Fix Object.dbus_reader to work with attr_accessor and automatically produce
28
+ dbus_properties_changed for properties that are read-write at
29
+ implementation side and read-only at D-Bus side ([#96][])
30
+ * Service-side properties: Fix Properties.Get, Properties.GetAll
31
+ to use the specific property signature, not the generic
32
+ Variant ([#97][], [#105][], [#109][]).
33
+ * Client-side properties: When calling Properties.Set in
34
+ ProxyObjectInterface#[]=, use the correct type ([#108][]).
35
+ * Added thorough tests (`spec/data/marshall.yaml`) to detect nearly all
36
+ invalid data at unmarshalling time.
37
+
38
+ Requirements:
39
+ * Require Ruby 2.4, because of RuboCop 1.0.
40
+
41
+ ## Ruby D-Bus 0.18.0.beta8 - 2022-06-21
42
+
43
+ Bug fixes:
44
+ * Introduced Object#dbus_properties_changed to send correctly typed property
45
+ values ([#115][]). Avoid calling PropertiesChanged directly as it will
46
+ guess the types.
47
+ * Fix Object.dbus_reader to work with attr_accessor and automatically produce
48
+ dbus_properties_changed for properties that are read-write at
49
+ implementation side and read-only at D-Bus side ([#96][])
50
+
51
+ [#96]: https://github.com/mvidner/ruby-dbus/issues/96
52
+
53
+ API:
54
+ * Service side `emits_changed_signal` to control emission of
55
+ PropertiesChanged: can be assigned within `dbus_interface` or as an option
56
+ when declaring properties ([#117][]).
57
+
58
+ [#115]: https://github.com/mvidner/ruby-dbus/issues/115
59
+ [#117]: https://github.com/mvidner/ruby-dbus/pulls/117
60
+
61
+ ## Ruby D-Bus 0.18.0.beta7 - 2022-05-29
62
+
63
+ API:
64
+ * DBus.variant(type, value) is deprecated in favor of
65
+ Data::Variant.new(value, member_type:)
66
+
67
+ Bug fixes:
68
+ * Client-side properties: When calling Properties.Set in
69
+ ProxyObjectInterface#[]=, use the correct type ([#108][]).
70
+
71
+ [#108]: https://github.com/mvidner/ruby-dbus/issues/108
72
+
5
73
  ## Ruby D-Bus 0.18.0.beta6 - 2022-05-25
6
74
 
7
75
  API:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.18.0.beta6
1
+ 0.18.1
data/doc/Reference.md CHANGED
@@ -166,12 +166,21 @@ D-Bus has stricter typing than Ruby, so the library must decide
166
166
  which D-Bus type to choose. Most of the time the choice is dictated
167
167
  by the D-Bus signature.
168
168
 
169
+ For exact representation of D-Bus data types, use subclasses
170
+ of {DBus::Data::Base}, such as {DBus::Data::Int16} or {DBus::Data::UInt64}.
171
+
169
172
  ##### Variants
170
173
 
171
174
  If the signature expects a Variant
172
175
  (which is the case for all Properties!) then an explicit mechanism is needed.
173
176
 
174
- 1. A pair [{DBus::Type}, value] specifies to marshall *value* as
177
+ 1. Any {DBus::Data::Base}.
178
+
179
+ 2. A {DBus::Data::Variant} made by {DBus.variant}(signature, value).
180
+ (Formerly this produced the type+value pair below, now it is just an alias
181
+ to the Variant constructor.)
182
+
183
+ 3. A pair [{DBus::Type}, value] specifies to marshall *value* as
175
184
  that specified type.
176
185
  The pair can be produced by {DBus.variant}(signature, value) which
177
186
  gives the same result as [{DBus.type}(signature), value].
@@ -181,13 +190,13 @@ If the signature expects a Variant
181
190
 
182
191
  `foo_i["Bar"] = DBus.variant("au", [0, 1, 1, 2, 3, 5, 8])`
183
192
 
184
- 2. Other values are tried to fit one of these:
193
+ 4. Other values are tried to fit one of these:
185
194
  Boolean, Double, Array of Variants, Hash of String keyed Variants,
186
195
  String, Int32, Int64.
187
196
 
188
- 3. **Deprecated:** A pair [String, value], where String is a valid
197
+ 5. **Deprecated:** A pair [String, value], where String is a valid
189
198
  signature of a single complete type, marshalls value as that
190
- type. This will hit you when you rely on method (2) but happen to have
199
+ type. This will hit you when you rely on method (4) but happen to have
191
200
  a particular string value in an array.
192
201
 
193
202
  ##### Structs
data/lib/dbus/bus.rb CHANGED
@@ -171,15 +171,11 @@ module DBus
171
171
  "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
172
172
  '
173
173
  xml += "<node name=\"#{node_opath}\">\n"
174
- each_pair do |k, _v|
174
+ each_key do |k|
175
175
  xml += " <node name=\"#{k}\" />\n"
176
176
  end
177
- @object&.intfs&.each_pair do |_k, v|
178
- xml += " <interface name=\"#{v.name}\">\n"
179
- v.methods.each_value { |m| xml += m.to_xml }
180
- v.signals.each_value { |m| xml += m.to_xml }
181
- v.properties.each_value { |m| xml += m.to_xml }
182
- xml += " </interface>\n"
177
+ @object&.intfs&.each_value do |v|
178
+ xml += v.to_xml
183
179
  end
184
180
  xml += "</node>"
185
181
  xml
data/lib/dbus/data.rb CHANGED
@@ -601,8 +601,10 @@ module DBus
601
601
 
602
602
  typed_value = case value
603
603
  when Data::Array
604
- raise ArgumentError, "Specified type is #{type} but value type is #{value.type}" \
605
- unless value.type == type
604
+ unless value.type == type
605
+ raise ArgumentError,
606
+ "Specified type is #{type.inspect} but value type is #{value.type.inspect}"
607
+ end
606
608
 
607
609
  value.exact_value
608
610
  else
@@ -651,8 +653,10 @@ module DBus
651
653
 
652
654
  typed_value = case value
653
655
  when self.class
654
- raise ArgumentError, "Specified type is #{type} but value type is #{value.type}" \
655
- unless value.type == type
656
+ unless value.type == type
657
+ raise ArgumentError,
658
+ "Specified type is #{type.inspect} but value type is #{value.type.inspect}"
659
+ end
656
660
 
657
661
  value.exact_value
658
662
  else
@@ -751,13 +755,19 @@ module DBus
751
755
  # @return [Type]
752
756
  attr_reader :member_type
753
757
 
758
+ # Determine the type of *value*
759
+ # @param value [::Object]
760
+ # @return [Type]
761
+ # @api private
762
+ # See also {PacketMarshaller.make_variant}
754
763
  def self.guess_type(value)
755
764
  sct, = PacketMarshaller.make_variant(value)
756
765
  DBus.type(sct)
757
766
  end
758
767
 
759
- # @param member_type [Type,nil]
768
+ # @param member_type [SingleCompleteType,Type,nil]
760
769
  def initialize(value, member_type:)
770
+ member_type = Type::Factory.make_type(member_type) if member_type
761
771
  # TODO: validate that the given *member_type* matches *value*
762
772
  case value
763
773
  when Data::Variant
@@ -775,6 +785,30 @@ module DBus
775
785
  end
776
786
  super(value)
777
787
  end
788
+
789
+ # Internal helpers to keep the {DBus.variant} method working.
790
+ # Formerly it returned just a pair of [DBus.type(string_type), value]
791
+ # so let's provide [0], [1], .first, .last
792
+ def [](index)
793
+ case index
794
+ when 0
795
+ member_type
796
+ when 1
797
+ value
798
+ else
799
+ raise ArgumentError, "DBus.variant can only be indexed with 0 or 1, seen #{index.inspect}"
800
+ end
801
+ end
802
+
803
+ # @see #[]
804
+ def first
805
+ self[0]
806
+ end
807
+
808
+ # @see #[]
809
+ def last
810
+ self[1]
811
+ end
778
812
  end
779
813
 
780
814
  consts = constants.map { |c_sym| const_get(c_sym) }
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2022 Martin Vidner
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License, version 2.1 as published by the Free Software Foundation.
9
+ # See the file "COPYING" for the exact licensing terms.
10
+
11
+ module DBus
12
+ # Describes the behavior of PropertiesChanged signal, for a single property
13
+ # or for an entire interface.
14
+ #
15
+ # The possible values are:
16
+ #
17
+ # - *true*: the signal is emitted with the value included.
18
+ # - *:invalidates*: the signal is emitted but the value is not included
19
+ # in the signal.
20
+ # - *:const*: the property never changes value during the lifetime
21
+ # of the object it belongs to, and hence the signal
22
+ # is never emitted for it (but clients can cache the value)
23
+ # - *false*: the signal won't be emitted (clients should re-Get the property value)
24
+ #
25
+ # The default is:
26
+ # - for an interface: *true*
27
+ # - for a property: what the parent interface specifies
28
+ #
29
+ # @see DBus::Object.emits_changed_signal
30
+ # @see DBus::Object.dbus_attr_accessor
31
+ # @see https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format
32
+ #
33
+ # Immutable once constructed.
34
+ class EmitsChangedSignal
35
+ # @return [true,false,:const,:invalidates]
36
+ attr_reader :value
37
+
38
+ # @param value [true,false,:const,:invalidates,nil]
39
+ # See class-level description above, {EmitsChangedSignal}.
40
+ # @param interface [Interface,nil]
41
+ # If the (property-level) *value* is unspecified (nil), this is the
42
+ # containing {Interface} to get the value from.
43
+ def initialize(value, interface: nil)
44
+ if value.nil?
45
+ raise ArgumentError, "Both arguments are nil" if interface.nil?
46
+
47
+ @value = interface.emits_changed_signal.value
48
+ else
49
+ expecting = [true, false, :const, :invalidates]
50
+ unless expecting.include?(value)
51
+ raise ArgumentError, "Expecting one of #{expecting.inspect}. Seen #{value.inspect}"
52
+ end
53
+
54
+ @value = value
55
+ end
56
+
57
+ freeze
58
+ end
59
+
60
+ # Return introspection XML string representation
61
+ # @return [String]
62
+ def to_xml
63
+ return "" if @value == true
64
+
65
+ " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"#{@value}\"/>\n"
66
+ end
67
+
68
+ def to_s
69
+ @value.to_s
70
+ end
71
+
72
+ def ==(other)
73
+ if other.is_a?(self.class)
74
+ other.value == @value
75
+ else
76
+ other == value
77
+ end
78
+ end
79
+ alias eql? ==
80
+
81
+ DEFAULT_ECS = EmitsChangedSignal.new(true)
82
+ end
83
+ end
@@ -26,7 +26,7 @@ module DBus
26
26
  # method call instantiates and configures this class for us.
27
27
  #
28
28
  # It also is the local definition of interface exported by the program.
29
- # At the client side, see ProxyObjectInterface
29
+ # At the client side, see {ProxyObjectInterface}.
30
30
  class Interface
31
31
  # @return [String] The name of the interface.
32
32
  attr_reader :name
@@ -38,6 +38,9 @@ module DBus
38
38
  # @return [Hash{Symbol => Property}]
39
39
  attr_reader :properties
40
40
 
41
+ # @return [EmitsChangedSignal]
42
+ attr_reader :emits_changed_signal
43
+
41
44
  # Creates a new interface with a given _name_.
42
45
  def initialize(name)
43
46
  validate_name(name)
@@ -45,6 +48,20 @@ module DBus
45
48
  @methods = {}
46
49
  @signals = {}
47
50
  @properties = {}
51
+ @emits_changed_signal = EmitsChangedSignal::DEFAULT_ECS
52
+ end
53
+
54
+ # Helper for {Object.emits_changed_signal=}.
55
+ # @api private
56
+ def emits_changed_signal=(ecs)
57
+ raise TypeError unless ecs.is_a? EmitsChangedSignal
58
+ # equal?: object identity
59
+ unless @emits_changed_signal.equal?(EmitsChangedSignal::DEFAULT_ECS) ||
60
+ @emits_changed_signal.value == ecs.value
61
+ raise "emits_change_signal was assigned more than once"
62
+ end
63
+
64
+ @emits_changed_signal = ecs
48
65
  end
49
66
 
50
67
  # Validates a service _name_.
@@ -85,6 +102,18 @@ module DBus
85
102
  define(m)
86
103
  end
87
104
  alias declare_method define_method
105
+
106
+ # Return introspection XML string representation of the property.
107
+ # @return [String]
108
+ def to_xml
109
+ xml = " <interface name=\"#{name}\">\n"
110
+ xml += emits_changed_signal.to_xml
111
+ methods.each_value { |m| xml += m.to_xml }
112
+ signals.each_value { |m| xml += m.to_xml }
113
+ properties.each_value { |m| xml += m.to_xml }
114
+ xml += " </interface>\n"
115
+ xml
116
+ end
88
117
  end
89
118
 
90
119
  # = A formal parameter has a name and a type
@@ -190,6 +219,7 @@ module DBus
190
219
  end
191
220
 
192
221
  # Return an XML string representation of the method interface elment.
222
+ # @return [String]
193
223
  def to_xml
194
224
  xml = " <method name=\"#{@name}\">\n"
195
225
  @params.each do |param|
@@ -238,19 +268,20 @@ module DBus
238
268
  # An (exported) property
239
269
  # https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
240
270
  class Property
241
- # @return [String] The name of the property, for example FooBar.
271
+ # @return [Symbol] The name of the property, for example FooBar.
242
272
  attr_reader :name
243
273
  # @return [SingleCompleteType]
244
274
  attr_reader :type
245
275
  # @return [Symbol] :read :write or :readwrite
246
276
  attr_reader :access
247
277
 
248
- # @return [Symbol] What to call at Ruby side.
278
+ # @return [Symbol,nil] What to call at Ruby side.
249
279
  # (Always without the trailing `=`)
280
+ # It is `nil` IFF representing a client-side proxy.
250
281
  attr_reader :ruby_name
251
282
 
252
283
  def initialize(name, type, access, ruby_name:)
253
- @name = name
284
+ @name = name.to_sym
254
285
  @type = type
255
286
  @access = access
256
287
  @ruby_name = ruby_name
@@ -270,5 +301,14 @@ module DBus
270
301
  def to_xml
271
302
  " <property type=\"#{@type}\" name=\"#{@name}\" access=\"#{@access}\"/>\n"
272
303
  end
304
+
305
+ # @param xml_node [AbstractXML::Node]
306
+ # @return [Property]
307
+ def self.from_xml(xml_node)
308
+ name = xml_node["name"].to_sym
309
+ type = xml_node["type"]
310
+ access = xml_node["access"].to_sym
311
+ new(name, type, access, ruby_name: nil)
312
+ end
273
313
  end
274
314
  end
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
@@ -415,8 +415,9 @@ module DBus
415
415
  # @param string_type [SingleCompleteType]
416
416
  # @param value [::Object]
417
417
  # @return [Array(DBus::Type::Type,::Object)]
418
+ # @deprecated Use {Data::Variant#initialize} instead
418
419
  def variant(string_type, value)
419
- [type(string_type), value]
420
+ Data::Variant.new(value, member_type: string_type)
420
421
  end
421
422
  module_function :variant
422
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"
data/spec/data_spec.rb CHANGED
@@ -193,7 +193,7 @@ RSpec.shared_examples "constructor rejects values from this list" do |bad_list|
193
193
  describe "#initialize" do
194
194
  bad_list.each do |(value, exc_class, msg_substr)|
195
195
  it "rejects #{value.inspect} with #{exc_class}: #{msg_substr}" do
196
- msg_re = Regexp.new(Regexp.quote(msg_substr))
196
+ msg_re = Regexp.try_convert(msg_substr) || Regexp.new(Regexp.quote(msg_substr))
197
197
  expect { described_class.new(value) }.to raise_error(exc_class, msg_re)
198
198
  end
199
199
  end
@@ -204,7 +204,7 @@ RSpec.shared_examples "constructor (kwargs) rejects values" do |bad_list|
204
204
  describe "#initialize" do
205
205
  bad_list.each do |(value, kwargs_hash, exc_class, msg_substr)|
206
206
  it "rejects #{value.inspect}, #{kwargs_hash.inspect} with #{exc_class}: #{msg_substr}" do
207
- msg_re = Regexp.new(Regexp.quote(msg_substr))
207
+ msg_re = Regexp.try_convert(msg_substr) || Regexp.new(Regexp.quote(msg_substr))
208
208
  expect { described_class.new(value, **kwargs_hash) }.to raise_error(exc_class, msg_re)
209
209
  end
210
210
  end
@@ -387,8 +387,9 @@ describe DBus::Data do
387
387
 
388
388
  describe "containers" do
389
389
  describe DBus::Data::Array do
390
+ aq = DBus::Data::Array.new([1, 2, 3], type: "aq")
391
+
390
392
  good = [
391
- # [[1, 2, 3], type: nil],
392
393
  [[1, 2, 3], { type: "aq" }],
393
394
  [[1, 2, 3], { type: T::Array[T::UINT16] }],
394
395
  [[1, 2, 3], { type: T::Array["q"] }],
@@ -398,9 +399,14 @@ describe DBus::Data do
398
399
 
399
400
  bad = [
400
401
  # undesirable type guessing
401
- ## [[1, 2, 3], { type: nil }, DBus::InvalidPacketException, "Unknown type code"],
402
- ## [[1, 2, 3], { type: "!" }, DBus::InvalidPacketException, "Unknown type code"]
403
- # TODO: others
402
+ [[1, 2, 3], { type: nil }, ArgumentError, /Expecting DBus::Type.*got nil/],
403
+ [[1, 2, 3], { type: "!" }, DBus::Type::SignatureException, "Unknown type code"],
404
+ [aq, { type: "q" }, ArgumentError, "Expecting \"a\""],
405
+ [aq, { type: "ao" }, ArgumentError,
406
+ "Specified type is ARRAY: [OBJECT_PATH] but value type is ARRAY: [UINT16]"]
407
+ # TODO: how to handle these?
408
+ # [{1 => 2, 3 => 4}, { type: "aq" }, ArgumentError, "?"],
409
+ # [/i am not an array/, { type: "aq" }, ArgumentError, "?"],
404
410
  ]
405
411
 
406
412
  include_examples "#== and #eql? work for container types (inequal)",
@@ -506,7 +512,7 @@ describe DBus::Data do
506
512
  value2 = result1
507
513
  type2 = "(xxx)"
508
514
  expect { described_class.new(value2, type: type2) }
509
- .to raise_error(ArgumentError, /value type is .uuu./)
515
+ .to raise_error(ArgumentError, /value type is STRUCT.*UINT32/)
510
516
  end
511
517
 
512
518
  it "checks that size of type and value match" do
@@ -553,7 +559,7 @@ describe DBus::Data do
553
559
  value2 = result1
554
560
  type2 = T::Hash[T::UINT64, T::UINT64].child
555
561
  expect { described_class.new(value2, type: type2) }
556
- .to raise_error(ArgumentError, /value type is .uu./)
562
+ .to raise_error(ArgumentError, /value type is DICT_ENTRY.*UINT32/)
557
563
  end
558
564
 
559
565
  it "checks that size of type and value match" do
@@ -632,6 +638,36 @@ describe DBus::Data do
632
638
 
633
639
  include_examples "#== and #eql? work for container types (1 value)",
634
640
  "/foo", { member_type: DBus.type(T::STRING) }
641
+
642
+ describe "DBus.variant compatibility" do
643
+ let(:v) { DBus.variant("o", "/foo") }
644
+
645
+ describe "#[]" do
646
+ it "returns the type for 0" do
647
+ expect(v[0]).to eq DBus.type(DBus::Type::OBJECT_PATH)
648
+ end
649
+
650
+ it "returns the value for 1" do
651
+ expect(v[1]).to eq DBus::ObjectPath.new("/foo")
652
+ end
653
+
654
+ it "returns an error for other indices" do
655
+ expect { v[2] }.to raise_error(ArgumentError, /DBus.variant can only be indexed with 0 or 1/)
656
+ end
657
+ end
658
+
659
+ describe "#first" do
660
+ it "returns the type" do
661
+ expect(v.first).to eq DBus.type(DBus::Type::OBJECT_PATH)
662
+ end
663
+ end
664
+
665
+ describe "#last" do
666
+ it "returns the value" do
667
+ expect(v.last).to eq DBus::ObjectPath.new("/foo")
668
+ end
669
+ end
670
+ end
635
671
  end
636
672
  end
637
673
  end
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::EmitsChangedSignal do
8
+ describe "#initialize" do
9
+ it "accepts a simple value" do
10
+ expect(described_class.new(:const).value).to eq :const
11
+ end
12
+
13
+ it "avoids nil by asking the interface" do
14
+ ifc = DBus::Interface.new("org.example.Foo")
15
+ ifc.emits_changed_signal = described_class.new(:invalidates)
16
+
17
+ expect(described_class.new(nil, interface: ifc).value).to eq :invalidates
18
+ end
19
+
20
+ it "fails for unknown value" do
21
+ expect { described_class.new(:huh) }.to raise_error(ArgumentError, /Seen :huh/)
22
+ end
23
+
24
+ it "fails for 2 nils" do
25
+ expect { described_class.new(nil, interface: nil) }.to raise_error(ArgumentError, /Both/)
26
+ end
27
+ end
28
+
29
+ describe "#==" do
30
+ it "is true for two different objects with the same value" do
31
+ const_a = described_class.new(:const)
32
+ const_b = described_class.new(:const)
33
+ expect(const_a == const_b).to be true
34
+ end
35
+ end
36
+
37
+ describe "#to_xml" do
38
+ it "uses a string value" do
39
+ expect(described_class.new(:const).to_xml)
40
+ .to eq " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"const\"/>\n"
41
+ end
42
+ end
43
+
44
+ describe "#to_s" do
45
+ it "uses a string value" do
46
+ expect(described_class.new(:const).to_s).to eq "const"
47
+ end
48
+ end
49
+ end
50
+
51
+ describe DBus::Interface do
52
+ describe ".emits_changed_signal=" do
53
+ it "only allows an EmitsChangedSignal as argument" do
54
+ ifc = described_class.new("org.ruby.Interface")
55
+ expect { ifc.emits_changed_signal = :const }.to raise_error(TypeError)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ class ObjectTest < DBus::Object
8
+ T = DBus::Type unless const_defined? "T"
9
+
10
+ dbus_interface "org.ruby.ServerTest" do
11
+ dbus_attr_writer :write_me, T::Struct[String, String]
12
+
13
+ attr_accessor :read_only_for_dbus
14
+
15
+ dbus_reader :read_only_for_dbus, T::STRING, emits_changed_signal: :invalidates
16
+ end
17
+ end
18
+
19
+ describe DBus::Object do
20
+ describe ".dbus_attr_writer" do
21
+ describe "the declared assignment method" do
22
+ # Slightly advanced RSpec:
23
+ # https://rspec.info/documentation/3.9/rspec-expectations/RSpec/Matchers.html#satisfy-instance_method
24
+ let(:a_struct_in_a_variant) do
25
+ satisfying { |x| x.is_a?(DBus::Data::Variant) && x.member_type.to_s == "(ss)" }
26
+ # ^ This formatting keeps the matcher on a single line
27
+ # which enables RSpec to cite it if it fails, instead of saying "block".
28
+ end
29
+
30
+ it "emits PropertyChanged with correctly typed argument" do
31
+ obj = ObjectTest.new("/test")
32
+ expect(obj).to receive(:PropertiesChanged).with(
33
+ "org.ruby.ServerTest",
34
+ {
35
+ "WriteMe" => a_struct_in_a_variant
36
+ },
37
+ []
38
+ )
39
+ # bug: call PC with simply the assigned value,
40
+ # which will need type guessing
41
+ obj.write_me = ["two", "strings"]
42
+ end
43
+ end
44
+ end
45
+
46
+ describe ".dbus_accessor" do
47
+ it "can only be used within a dbus_interface" do
48
+ expect do
49
+ ObjectTest.instance_exec do
50
+ dbus_accessor :foo, DBus::Type::STRING
51
+ end
52
+ end.to raise_error(DBus::Object::UndefinedInterface)
53
+ end
54
+ end
55
+
56
+ describe ".dbus_reader" do
57
+ it "can only be used within a dbus_interface" do
58
+ expect do
59
+ ObjectTest.instance_exec do
60
+ dbus_reader :foo, DBus::Type::STRING
61
+ end
62
+ end.to raise_error(DBus::Object::UndefinedInterface)
63
+ end
64
+ end
65
+
66
+ describe ".dbus_reader, when paired with attr_accessor" do
67
+ describe "the declared assignment method" do
68
+ it "emits PropertyChanged" do
69
+ obj = ObjectTest.new("/test")
70
+ expect(obj).to receive(:PropertiesChanged).with(
71
+ "org.ruby.ServerTest",
72
+ {},
73
+ ["ReadOnlyForDbus"]
74
+ )
75
+ obj.read_only_for_dbus = "myvalue"
76
+ end
77
+ end
78
+ end
79
+
80
+ describe ".dbus_writer" do
81
+ it "can only be used within a dbus_interface" do
82
+ expect do
83
+ ObjectTest.instance_exec do
84
+ dbus_writer :foo, DBus::Type::STRING
85
+ end
86
+ end.to raise_error(DBus::Object::UndefinedInterface)
87
+ end
88
+ end
89
+
90
+ describe ".dbus_watcher" do
91
+ it "can only be used within a dbus_interface" do
92
+ expect do
93
+ ObjectTest.instance_exec do
94
+ dbus_watcher :foo
95
+ end
96
+ end.to raise_error(DBus::Object::UndefinedInterface)
97
+ end
98
+ end
99
+
100
+ describe ".dbus_method" do
101
+ it "can only be used within a dbus_interface" do
102
+ expect do
103
+ ObjectTest.instance_exec do
104
+ dbus_method :foo do
105
+ end
106
+ end
107
+ end.to raise_error(DBus::Object::UndefinedInterface)
108
+ end
109
+ end
110
+
111
+ describe ".emits_changed_signal" do
112
+ it "raises UndefinedInterface when so" do
113
+ expect { ObjectTest.emits_changed_signal = false }
114
+ .to raise_error DBus::Object::UndefinedInterface
115
+ end
116
+
117
+ it "assigns to the current interface" do
118
+ ObjectTest.instance_exec do
119
+ dbus_interface "org.ruby.Interface" do
120
+ self.emits_changed_signal = false
121
+ end
122
+ end
123
+ ecs = ObjectTest.intfs["org.ruby.Interface"].emits_changed_signal
124
+ expect(ecs).to eq false
125
+ end
126
+
127
+ it "only can be assigned once" do
128
+ expect do
129
+ Class.new(DBus::Object) do
130
+ dbus_interface "org.ruby.Interface" do
131
+ self.emits_changed_signal = false
132
+ self.emits_changed_signal = :invalidates
133
+ end
134
+ end
135
+ end.to raise_error(RuntimeError, /assigned more than once/)
136
+ end
137
+ end
138
+ end
@@ -60,7 +60,7 @@ describe "PropertyTest" do
60
60
 
61
61
  it "tests get all" do
62
62
  all = @iface.all_properties
63
- expect(all.keys.sort).to eq(["MyArray", "MyDict", "MyStruct", "MyVariant", "ReadMe", "ReadOrWriteMe"])
63
+ expect(all.keys.sort).to eq(["MyArray", "MyByte", "MyDict", "MyStruct", "MyVariant", "ReadMe", "ReadOrWriteMe"])
64
64
  end
65
65
 
66
66
  it "tests get all on a V1 object" do
@@ -68,7 +68,7 @@ describe "PropertyTest" do
68
68
  iface = obj["org.ruby.SampleInterface"]
69
69
 
70
70
  all = iface.all_properties
71
- expect(all.keys.sort).to eq(["MyArray", "MyDict", "MyStruct", "MyVariant", "ReadMe", "ReadOrWriteMe"])
71
+ expect(all.keys.sort).to eq(["MyArray", "MyByte", "MyDict", "MyStruct", "MyVariant", "ReadMe", "ReadOrWriteMe"])
72
72
  end
73
73
 
74
74
  it "tests unknown property reading" do
@@ -110,6 +110,7 @@ describe "PropertyTest" do
110
110
  end
111
111
 
112
112
  @iface["ReadOrWriteMe"] = "VALUE"
113
+ @iface.SetTwoProperties("REAMDE", 255)
113
114
 
114
115
  # loop to process the signal. complicated :-( see signal_spec.rb
115
116
  loop = DBus::Main.new
@@ -123,6 +124,8 @@ describe "PropertyTest" do
123
124
  quitter.join
124
125
 
125
126
  expect(received["ReadOrWriteMe"]).to eq("VALUE")
127
+ expect(received["ReadMe"]).to eq("REAMDE")
128
+ expect(received["MyByte"]).to eq(255)
126
129
  end
127
130
 
128
131
  context "a struct-typed property" do
@@ -200,6 +203,36 @@ describe "PropertyTest" do
200
203
  end
201
204
  end
202
205
 
206
+ context "a byte-typed property" do
207
+ # Slightly advanced RSpec:
208
+ # https://rspec.info/documentation/3.9/rspec-expectations/RSpec/Matchers.html#satisfy-instance_method
209
+ let(:a_byte_in_a_variant) do
210
+ satisfying { |x| x.is_a?(DBus::Data::Variant) && x.member_type.to_s == DBus::Type::BYTE }
211
+ # ^ This formatting keeps the matcher on a single line
212
+ # which enables RSpec to cite it if it fails, instead of saying "block".
213
+ end
214
+
215
+ let(:prop_iface) { @obj[DBus::PROPERTY_INTERFACE] }
216
+
217
+ it "gets set with a correct type (#108)" do
218
+ expect(prop_iface).to receive(:Set).with(
219
+ "org.ruby.SampleInterface",
220
+ "MyByte",
221
+ a_byte_in_a_variant
222
+ )
223
+ @iface["MyByte"] = 1
224
+ end
225
+
226
+ it "gets set with a correct type (#108), when using the DBus.variant workaround" do
227
+ expect(prop_iface).to receive(:Set).with(
228
+ "org.ruby.SampleInterface",
229
+ "MyByte",
230
+ a_byte_in_a_variant
231
+ )
232
+ @iface["MyByte"] = DBus.variant("y", 1)
233
+ end
234
+ end
235
+
203
236
  context "marshall.yaml round-trip via a VARIANT property" do
204
237
  marshall_yaml.each do |test|
205
238
  t = OpenStruct.new(test)
@@ -28,6 +28,8 @@ class Test < DBus::Object
28
28
  "three" => [3, 3, 3]
29
29
  }
30
30
  @my_variant = @my_array.dup
31
+ # 201 is a RET instruction for ZX Spectrum which has turned 40 recently
32
+ @my_byte = 201
31
33
  @main_loop = nil
32
34
  end
33
35
 
@@ -111,6 +113,17 @@ class Test < DBus::Object
111
113
  dbus_attr_accessor :my_array, "aq"
112
114
  dbus_attr_accessor :my_dict, "a{sv}"
113
115
  dbus_attr_accessor :my_variant, "v"
116
+
117
+ dbus_attr_accessor :my_byte, "y"
118
+
119
+ # to test dbus_properties_changed
120
+ dbus_method :SetTwoProperties, "in read_me:s, in byte:y" do |read_me, byte|
121
+ @read_me = read_me
122
+ @my_byte = byte
123
+ dbus_properties_changed(INTERFACE,
124
+ { "ReadMe" => read_me, "MyByte" => byte },
125
+ [])
126
+ end
114
127
  end
115
128
 
116
129
  # closing and reopening the same interface
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-dbus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0.beta6
4
+ version: 0.18.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ruby DBus Team
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-25 00:00:00.000000000 Z
11
+ date: 2022-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rexml
@@ -146,6 +146,7 @@ files:
146
146
  - lib/dbus/core_ext/class/attribute.rb
147
147
  - lib/dbus/core_ext/module/redefine_method.rb
148
148
  - lib/dbus/data.rb
149
+ - lib/dbus/emits_changed_signal.rb
149
150
  - lib/dbus/error.rb
150
151
  - lib/dbus/introspect.rb
151
152
  - lib/dbus/logger.rb
@@ -172,12 +173,14 @@ files:
172
173
  - spec/client_robustness_spec.rb
173
174
  - spec/data/marshall.yaml
174
175
  - spec/data_spec.rb
176
+ - spec/emits_changed_signal_spec.rb
175
177
  - spec/err_msg_spec.rb
176
178
  - spec/introspect_xml_parser_spec.rb
177
179
  - spec/introspection_spec.rb
178
180
  - spec/main_loop_spec.rb
179
181
  - spec/node_spec.rb
180
182
  - spec/object_path_spec.rb
183
+ - spec/object_spec.rb
181
184
  - spec/packet_marshaller_spec.rb
182
185
  - spec/packet_unmarshaller_spec.rb
183
186
  - spec/property_spec.rb
@@ -202,7 +205,7 @@ homepage: https://github.com/mvidner/ruby-dbus
202
205
  licenses:
203
206
  - LGPL-2.1
204
207
  metadata: {}
205
- post_install_message:
208
+ post_install_message:
206
209
  rdoc_options: []
207
210
  require_paths:
208
211
  - lib
@@ -213,12 +216,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
213
216
  version: 2.4.0
214
217
  required_rubygems_version: !ruby/object:Gem::Requirement
215
218
  requirements:
216
- - - ">"
219
+ - - ">="
217
220
  - !ruby/object:Gem::Version
218
- version: 1.3.1
221
+ version: '0'
219
222
  requirements: []
220
- rubygems_version: 3.3.0.dev
221
- signing_key:
223
+ rubyforge_project:
224
+ rubygems_version: 2.7.6.3
225
+ signing_key:
222
226
  specification_version: 4
223
227
  summary: Ruby module for interaction with D-Bus
224
228
  test_files: []