ruby-dbus 0.15.0 → 0.18.0.beta1

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.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/NEWS.md +41 -1
  3. data/README.md +3 -5
  4. data/Rakefile +18 -8
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +93 -3
  7. data/examples/doc/_extract_examples +7 -0
  8. data/examples/gdbus/gdbus +31 -24
  9. data/examples/no-introspect/nm-test.rb +2 -0
  10. data/examples/no-introspect/tracker-test.rb +3 -1
  11. data/examples/rhythmbox/playpause.rb +2 -1
  12. data/examples/service/call_service.rb +2 -1
  13. data/examples/service/service_newapi.rb +1 -1
  14. data/examples/simple/call_introspect.rb +1 -0
  15. data/examples/simple/get_id.rb +2 -1
  16. data/examples/simple/properties.rb +2 -0
  17. data/examples/utils/listnames.rb +1 -0
  18. data/examples/utils/notify.rb +1 -0
  19. data/lib/dbus/api_options.rb +9 -0
  20. data/lib/dbus/auth.rb +20 -15
  21. data/lib/dbus/bus.rb +129 -74
  22. data/lib/dbus/bus_name.rb +31 -0
  23. data/lib/dbus/core_ext/class/attribute.rb +1 -1
  24. data/lib/dbus/error.rb +4 -2
  25. data/lib/dbus/introspect.rb +90 -34
  26. data/lib/dbus/logger.rb +3 -1
  27. data/lib/dbus/marshall.rb +119 -87
  28. data/lib/dbus/matchrule.rb +16 -16
  29. data/lib/dbus/message.rb +40 -27
  30. data/lib/dbus/message_queue.rb +26 -18
  31. data/lib/dbus/object.rb +401 -0
  32. data/lib/dbus/object_path.rb +28 -0
  33. data/lib/dbus/proxy_object.rb +23 -2
  34. data/lib/dbus/proxy_object_factory.rb +11 -7
  35. data/lib/dbus/proxy_object_interface.rb +26 -21
  36. data/lib/dbus/type.rb +59 -34
  37. data/lib/dbus/xml.rb +28 -17
  38. data/lib/dbus.rb +10 -8
  39. data/ruby-dbus.gemspec +8 -4
  40. data/spec/async_spec.rb +2 -0
  41. data/spec/binding_spec.rb +2 -0
  42. data/spec/bus_and_xml_backend_spec.rb +2 -0
  43. data/spec/bus_driver_spec.rb +2 -0
  44. data/spec/bus_name_spec.rb +27 -0
  45. data/spec/bus_spec.rb +2 -0
  46. data/spec/byte_array_spec.rb +2 -0
  47. data/spec/client_robustness_spec.rb +27 -0
  48. data/spec/err_msg_spec.rb +2 -0
  49. data/spec/introspect_xml_parser_spec.rb +2 -0
  50. data/spec/introspection_spec.rb +2 -0
  51. data/spec/main_loop_spec.rb +3 -1
  52. data/spec/node_spec.rb +23 -0
  53. data/spec/object_path_spec.rb +25 -0
  54. data/spec/property_spec.rb +64 -5
  55. data/spec/proxy_object_spec.rb +2 -0
  56. data/spec/server_robustness_spec.rb +2 -0
  57. data/spec/server_spec.rb +2 -0
  58. data/spec/service_newapi.rb +39 -70
  59. data/spec/session_bus_spec.rb +3 -1
  60. data/spec/session_bus_spec_manual.rb +2 -0
  61. data/spec/signal_spec.rb +5 -3
  62. data/spec/spec_helper.rb +23 -9
  63. data/spec/thread_safety_spec.rb +2 -0
  64. data/spec/tools/dbus-limited-session.conf +4 -0
  65. data/spec/type_spec.rb +2 -0
  66. data/spec/value_spec.rb +16 -1
  67. data/spec/variant_spec.rb +4 -2
  68. metadata +32 -12
  69. data/lib/dbus/export.rb +0 -131
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # dbus/introspection.rb - module containing a low-level D-Bus introspection implementation
2
4
  #
3
5
  # This file is part of the ruby-dbus project
@@ -10,13 +12,9 @@
10
12
 
11
13
  module DBus
12
14
  # Regular expressions that should match all method names.
13
- METHOD_SIGNAL_RE = /^[A-Za-z][A-Za-z0-9_]*$/
15
+ METHOD_SIGNAL_RE = /^[A-Za-z][A-Za-z0-9_]*$/.freeze
14
16
  # Regular expressions that should match all interface names.
15
- INTERFACE_ELEMENT_RE = /^[A-Za-z][A-Za-z0-9_]*$/
16
-
17
- # Exception raised when an unknown signal is used.
18
- class UnknownSignal < Exception
19
- end
17
+ INTERFACE_ELEMENT_RE = /^[A-Za-z][A-Za-z0-9_]*$/.freeze
20
18
 
21
19
  # Exception raised when an invalid class definition is encountered.
22
20
  class InvalidClassDefinition < Exception
@@ -30,19 +28,23 @@ module DBus
30
28
  # It also is the local definition of interface exported by the program.
31
29
  # At the client side, see ProxyObjectInterface
32
30
  class Interface
33
- # The name of the interface. String
31
+ # @return [String] The name of the interface.
34
32
  attr_reader :name
35
- # The methods that are part of the interface. Hash: Symbol => DBus::Method
33
+ # @return [Hash{Symbol => DBus::Method}] The methods that are part of the interface.
36
34
  attr_reader :methods
37
- # The signals that are part of the interface. Hash: Symbol => Signal
35
+ # @return [Hash{Symbol => Signal}] The signals that are part of the interface.
38
36
  attr_reader :signals
39
37
 
38
+ # @return [Hash{Symbol => Property}]
39
+ attr_reader :properties
40
+
40
41
  # Creates a new interface with a given _name_.
41
42
  def initialize(name)
42
43
  validate_name(name)
43
44
  @name = name
44
45
  @methods = {}
45
46
  @signals = {}
47
+ @properties = {}
46
48
  end
47
49
 
48
50
  # Validates a service _name_.
@@ -51,33 +53,45 @@ module DBus
51
53
  raise InvalidIntrospectionData if name =~ /^\./ || name =~ /\.$/
52
54
  raise InvalidIntrospectionData if name =~ /\.\./
53
55
  raise InvalidIntrospectionData if name !~ /\./
56
+
54
57
  name.split(".").each do |element|
55
58
  raise InvalidIntrospectionData if element !~ INTERFACE_ELEMENT_RE
56
59
  end
57
60
  end
58
61
 
59
- # Helper method for defining a method _m_.
60
- def define(m)
61
- if m.is_a?(Method)
62
- @methods[m.name.to_sym] = m
63
- elsif m.is_a?(Signal)
64
- @signals[m.name.to_sym] = m
65
- end
62
+ # Add _ifc_el_ as a known {Method}, {Signal} or {Property}
63
+ # @param ifc_el [InterfaceElement]
64
+ def define(ifc_el)
65
+ name = ifc_el.name.to_sym
66
+ category = case ifc_el
67
+ when Method
68
+ @methods
69
+ when Signal
70
+ @signals
71
+ when Property
72
+ @properties
73
+ end
74
+ category[name] = ifc_el
66
75
  end
76
+ alias declare define
67
77
  alias << define
68
78
 
69
79
  # Defines a method with name _id_ and a given _prototype_ in the
70
80
  # interface.
81
+ # Better name: declare_method
71
82
  def define_method(id, prototype)
72
83
  m = Method.new(id)
73
84
  m.from_prototype(prototype)
74
85
  define(m)
75
86
  end
76
- end # class Interface
87
+ alias declare_method define_method
88
+ end
77
89
 
78
90
  # = A formal parameter has a name and a type
79
91
  class FormalParameter
92
+ # @return [#to_s]
80
93
  attr_reader :name
94
+ # @return [SingleCompleteType]
81
95
  attr_reader :type
82
96
 
83
97
  def initialize(name, type)
@@ -99,14 +113,16 @@ module DBus
99
113
  # This is a generic class for entities that are part of the interface
100
114
  # such as methods and signals.
101
115
  class InterfaceElement
102
- # The name of the interface element. Symbol
116
+ # @return [Symbol] The name of the interface element
103
117
  attr_reader :name
104
- # The parameters of the interface element. Array: FormalParameter
118
+
119
+ # @return [Array<FormalParameter>] The parameters of the interface element
105
120
  attr_reader :params
106
121
 
107
122
  # Validates element _name_.
108
123
  def validate_name(name)
109
124
  return if (name =~ METHOD_SIGNAL_RE) && (name.bytesize <= 255)
125
+
110
126
  raise InvalidMethodName, name
111
127
  end
112
128
 
@@ -127,13 +143,13 @@ module DBus
127
143
  def add_param(name_signature_pair)
128
144
  add_fparam(*name_signature_pair)
129
145
  end
130
- end # class InterfaceElement
146
+ end
131
147
 
132
148
  # = D-Bus interface method class
133
149
  #
134
150
  # This is a class representing methods that are part of an interface.
135
151
  class Method < InterfaceElement
136
- # The list of return values for the method. Array: FormalParameter
152
+ # @return [Array<FormalParameter>] The list of return values for the method
137
153
  attr_reader :rets
138
154
 
139
155
  # Creates a new method interface element with the given _name_.
@@ -143,15 +159,19 @@ module DBus
143
159
  end
144
160
 
145
161
  # Add a return value _name_ and _signature_.
162
+ # @param name [#to_s]
163
+ # @param signature [SingleCompleteType]
146
164
  def add_return(name, signature)
147
165
  @rets << FormalParameter.new(name, signature)
148
166
  end
149
167
 
150
168
  # Add parameter types by parsing the given _prototype_.
169
+ # @param prototype [Prototype]
151
170
  def from_prototype(prototype)
152
171
  prototype.split(/, */).each do |arg|
153
172
  arg = arg.split(" ")
154
173
  raise InvalidClassDefinition if arg.size != 2
174
+
155
175
  dir, arg = arg
156
176
  if arg =~ /:/
157
177
  arg = arg.split(":")
@@ -171,19 +191,19 @@ module DBus
171
191
 
172
192
  # Return an XML string representation of the method interface elment.
173
193
  def to_xml
174
- xml = %(<method name="#{@name}">\n)
194
+ xml = " <method name=\"#{@name}\">\n"
175
195
  @params.each do |param|
176
- name = param.name ? %(name="#{param.name}" ) : ""
177
- xml += %(<arg #{name}direction="in" type="#{param.type}"/>\n)
196
+ name = param.name ? "name=\"#{param.name}\" " : ""
197
+ xml += " <arg #{name}direction=\"in\" type=\"#{param.type}\"/>\n"
178
198
  end
179
199
  @rets.each do |param|
180
- name = param.name ? %(name="#{param.name}" ) : ""
181
- xml += %(<arg #{name}direction="out" type="#{param.type}"/>\n)
200
+ name = param.name ? "name=\"#{param.name}\" " : ""
201
+ xml += " <arg #{name}direction=\"out\" type=\"#{param.type}\"/>\n"
182
202
  end
183
- xml += %(</method>\n)
203
+ xml += " </method>\n"
184
204
  xml
185
205
  end
186
- end # class Method
206
+ end
187
207
 
188
208
  # = D-Bus interface signal class
189
209
  #
@@ -205,13 +225,49 @@ module DBus
205
225
 
206
226
  # Return an XML string representation of the signal interface elment.
207
227
  def to_xml
208
- xml = %(<signal name="#{@name}">\n)
228
+ xml = " <signal name=\"#{@name}\">\n"
209
229
  @params.each do |param|
210
- name = param.name ? %(name="#{param.name}" ) : ""
211
- xml += %(<arg #{name}type="#{param.type}"/>\n)
230
+ name = param.name ? "name=\"#{param.name}\" " : ""
231
+ xml += " <arg #{name}type=\"#{param.type}\"/>\n"
212
232
  end
213
- xml += %(</signal>\n)
233
+ xml += " </signal>\n"
214
234
  xml
215
235
  end
216
- end # class Signal
217
- end # module DBus
236
+ end
237
+
238
+ # An (exported) property
239
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
240
+ class Property
241
+ # @return [String] The name of the property, for example FooBar.
242
+ attr_reader :name
243
+ attr_reader :type
244
+ # @return [Symbol] :read :write or :readwrite
245
+ attr_reader :access
246
+
247
+ # @return [Symbol] What to call at Ruby side.
248
+ # (Always without the trailing `=`)
249
+ attr_reader :ruby_name
250
+
251
+ def initialize(name, type, access, ruby_name:)
252
+ @name = name
253
+ @type = type
254
+ @access = access
255
+ @ruby_name = ruby_name
256
+ end
257
+
258
+ # @return [Boolean]
259
+ def readable?
260
+ access == :read || access == :readwrite
261
+ end
262
+
263
+ # @return [Boolean]
264
+ def writable?
265
+ access == :write || access == :readwrite
266
+ end
267
+
268
+ # Return introspection XML string representation of the property.
269
+ def to_xml
270
+ " <property type=\"#{@type}\" name=\"#{@name}\" access=\"#{@access}\"/>\n"
271
+ end
272
+ end
273
+ end
data/lib/dbus/logger.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # dbus/logger.rb - debug logging
2
4
  #
3
5
  # This file is part of the ruby-dbus project
@@ -16,7 +18,7 @@ module DBus
16
18
  # with DEBUG if $DEBUG is set, otherwise INFO.
17
19
  def logger
18
20
  unless defined? @logger
19
- @logger = Logger.new(STDERR)
21
+ @logger = Logger.new($stderr)
20
22
  @logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO
21
23
  end
22
24
  @logger
data/lib/dbus/marshall.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # dbus.rb - Module containing the low-level D-Bus implementation
2
4
  #
3
5
  # This file is part of the ruby-dbus project
@@ -34,11 +36,12 @@ module DBus
34
36
  def initialize(buffer, endianness)
35
37
  @buffy = buffer.dup
36
38
  @endianness = endianness
37
- if @endianness == BIG_END
39
+ case @endianness
40
+ when BIG_END
38
41
  @uint32 = "N"
39
42
  @uint16 = "n"
40
43
  @double = "G"
41
- elsif @endianness == LIL_END
44
+ when LIL_END
42
45
  @uint32 = "V"
43
46
  @uint16 = "v"
44
47
  @double = "E"
@@ -50,12 +53,14 @@ module DBus
50
53
 
51
54
  # Unmarshall the buffer for a given _signature_ and length _len_.
52
55
  # Return an array of unmarshalled objects
56
+ # @param signature [Signature]
57
+ # @param len [Integer,nil] if given, and there is not enough data
58
+ # in the buffer, raise {IncompleteBufferException}
59
+ # @return [Array<::Object>]
60
+ # @raise IncompleteBufferException
53
61
  def unmarshall(signature, len = nil)
54
- if !len.nil?
55
- if @buffy.bytesize < @idx + len
56
- raise IncompleteBufferException
57
- end
58
- end
62
+ raise IncompleteBufferException if len && @buffy.bytesize < @idx + len
63
+
59
64
  sigtree = Type::Parser.new(signature).parse
60
65
  ret = []
61
66
  sigtree.each do |elem|
@@ -64,18 +69,18 @@ module DBus
64
69
  ret
65
70
  end
66
71
 
67
- # Align the pointer index on a byte index of _a_, where a
72
+ # Align the pointer index on a byte index of _alignment_, which
68
73
  # must be 1, 2, 4 or 8.
69
- def align(a)
70
- case a
74
+ def align(alignment)
75
+ case alignment
71
76
  when 1
72
77
  nil
73
78
  when 2, 4, 8
74
- bits = a - 1
79
+ bits = alignment - 1
75
80
  @idx = @idx + bits & ~bits
76
81
  raise IncompleteBufferException if @idx > @buffy.bytesize
77
82
  else
78
- raise "Unsupported alignment #{a}"
83
+ raise ArgumentError, "Unsupported alignment #{alignment}"
79
84
  end
80
85
  end
81
86
 
@@ -87,6 +92,7 @@ module DBus
87
92
  # Retrieve the next _nbytes_ number of bytes from the buffer.
88
93
  def read(nbytes)
89
94
  raise IncompleteBufferException if @idx + nbytes > @buffy.bytesize
95
+
90
96
  ret = @buffy.slice(@idx, nbytes)
91
97
  @idx += nbytes
92
98
  ret
@@ -96,13 +102,15 @@ module DBus
96
102
  # Return the string.
97
103
  def read_string
98
104
  align(4)
99
- str_sz = read(4).unpack(@uint32)[0]
105
+ str_sz = read(4).unpack1(@uint32)
100
106
  ret = @buffy.slice(@idx, str_sz)
101
107
  raise IncompleteBufferException if @idx + str_sz + 1 > @buffy.bytesize
108
+
102
109
  @idx += str_sz
103
110
  if @buffy[@idx].ord != 0
104
111
  raise InvalidPacketException, "String is not nul-terminated"
105
112
  end
113
+
106
114
  @idx += 1
107
115
  # no exception, see check above
108
116
  ret
@@ -111,13 +119,15 @@ module DBus
111
119
  # Read the signature length and signature itself from the buffer.
112
120
  # Return the signature.
113
121
  def read_signature
114
- str_sz = read(1).unpack("C")[0]
122
+ str_sz = read(1).unpack1("C")
115
123
  ret = @buffy.slice(@idx, str_sz)
116
124
  raise IncompleteBufferException if @idx + str_sz + 1 >= @buffy.bytesize
125
+
117
126
  @idx += str_sz
118
127
  if @buffy[@idx].ord != 0
119
128
  raise InvalidPacketException, "Type is not nul-terminated"
120
129
  end
130
+
121
131
  @idx += 1
122
132
  # no exception, see check above
123
133
  ret
@@ -129,29 +139,29 @@ module DBus
129
139
  packet = nil
130
140
  case signature.sigtype
131
141
  when Type::BYTE
132
- packet = read(1).unpack("C")[0]
142
+ packet = read(1).unpack1("C")
133
143
  when Type::UINT16
134
144
  align(2)
135
- packet = read(2).unpack(@uint16)[0]
145
+ packet = read(2).unpack1(@uint16)
136
146
  when Type::INT16
137
147
  align(2)
138
- packet = read(2).unpack(@uint16)[0]
148
+ packet = read(2).unpack1(@uint16)
139
149
  if (packet & 0x8000) != 0
140
150
  packet -= 0x10000
141
151
  end
142
152
  when Type::UINT32, Type::UNIX_FD
143
153
  align(4)
144
- packet = read(4).unpack(@uint32)[0]
154
+ packet = read(4).unpack1(@uint32)
145
155
  when Type::INT32
146
156
  align(4)
147
- packet = read(4).unpack(@uint32)[0]
157
+ packet = read(4).unpack1(@uint32)
148
158
  if (packet & 0x80000000) != 0
149
159
  packet -= 0x100000000
150
160
  end
151
161
  when Type::UINT64
152
162
  align(8)
153
- packet_l = read(4).unpack(@uint32)[0]
154
- packet_h = read(4).unpack(@uint32)[0]
163
+ packet_l = read(4).unpack1(@uint32)
164
+ packet_h = read(4).unpack1(@uint32)
155
165
  packet = if @endianness == LIL_END
156
166
  packet_l + packet_h * 2**32
157
167
  else
@@ -159,8 +169,8 @@ module DBus
159
169
  end
160
170
  when Type::INT64
161
171
  align(8)
162
- packet_l = read(4).unpack(@uint32)[0]
163
- packet_h = read(4).unpack(@uint32)[0]
172
+ packet_l = read(4).unpack1(@uint32)
173
+ packet_h = read(4).unpack1(@uint32)
164
174
  packet = if @endianness == LIL_END
165
175
  packet_l + packet_h * 2**32
166
176
  else
@@ -171,16 +181,17 @@ module DBus
171
181
  end
172
182
  when Type::DOUBLE
173
183
  align(8)
174
- packet = read(8).unpack(@double)[0]
184
+ packet = read(8).unpack1(@double)
175
185
  when Type::BOOLEAN
176
186
  align(4)
177
- v = read(4).unpack(@uint32)[0]
187
+ v = read(4).unpack1(@uint32)
178
188
  raise InvalidPacketException if ![0, 1].member?(v)
189
+
179
190
  packet = (v == 1)
180
191
  when Type::ARRAY
181
192
  align(4)
182
193
  # checks please
183
- array_sz = read(4).unpack(@uint32)[0]
194
+ array_sz = read(4).unpack1(@uint32)
184
195
  raise InvalidPacketException if array_sz > 67_108_864
185
196
 
186
197
  align(signature.child.alignment)
@@ -201,6 +212,7 @@ module DBus
201
212
  signature.members.each do |elem|
202
213
  packet << do_parse(elem)
203
214
  end
215
+ packet.freeze
204
216
  when Type::VARIANT
205
217
  string = read_signature
206
218
  # error checking please
@@ -224,8 +236,8 @@ module DBus
224
236
  "sigtype: #{signature.sigtype} (#{signature.sigtype.chr})"
225
237
  end
226
238
  packet
227
- end # def do_parse
228
- end # class PacketUnmarshaller
239
+ end
240
+ end
229
241
 
230
242
  # D-Bus packet marshaller class
231
243
  #
@@ -243,20 +255,21 @@ module DBus
243
255
  @offset = offset # for correct alignment of nested marshallers
244
256
  end
245
257
 
246
- # Round _n_ up to the specified power of two, _a_
247
- def num_align(n, a)
248
- case a
258
+ # Round _num_ up to the specified power of two, _alignment_
259
+ def num_align(num, alignment)
260
+ case alignment
249
261
  when 1, 2, 4, 8
250
- bits = a - 1
251
- n + bits & ~bits
262
+ bits = alignment - 1
263
+ num + bits & ~bits
252
264
  else
253
- raise "Unsupported alignment"
265
+ raise ArgumentError, "Unsupported alignment #{alignment}"
254
266
  end
255
267
  end
256
268
 
257
- # Align the buffer with NULL (\0) bytes on a byte length of _a_.
258
- def align(a)
259
- @packet = @packet.ljust(num_align(@offset + @packet.bytesize, a) - @offset, 0.chr)
269
+ # Align the buffer with NULL (\0) bytes on a byte length of _alignment_.
270
+ def align(alignment)
271
+ pad_count = num_align(@offset + @packet.bytesize, alignment) - @offset
272
+ @packet = @packet.ljust(pad_count, 0.chr)
260
273
  end
261
274
 
262
275
  # Append the the string _str_ itself to the packet.
@@ -267,7 +280,7 @@ module DBus
267
280
 
268
281
  # Append the the signature _signature_ itself to the packet.
269
282
  def append_signature(str)
270
- @packet += str.bytesize.chr + str + "\0"
283
+ @packet += "#{str.bytesize.chr}#{str}\u0000"
271
284
  end
272
285
 
273
286
  # Append the array type _type_ to the packet and allow for appending
@@ -282,6 +295,7 @@ module DBus
282
295
  yield
283
296
  sz = @packet.bytesize - contentidx
284
297
  raise InvalidPacketException if sz > 67_108_864
298
+
285
299
  @packet[sizeidx...sizeidx + 4] = [sz].pack("L")
286
300
  end
287
301
 
@@ -337,57 +351,24 @@ module DBus
337
351
  when Type::SIGNATURE
338
352
  append_signature(val)
339
353
  when Type::VARIANT
340
- vartype = nil
341
- if val.is_a?(Array) && val.size == 2
342
- if val[0].is_a?(DBus::Type::Type)
343
- vartype, vardata = val
344
- elsif val[0].is_a?(String)
345
- begin
346
- parsed = Type::Parser.new(val[0]).parse
347
- vartype = parsed[0] if parsed.size == 1
348
- vardata = val[1]
349
- rescue Type::SignatureException
350
- # no assignment
351
- end
352
- end
353
- end
354
- if vartype.nil?
355
- vartype, vardata = PacketMarshaller.make_variant(val)
356
- vartype = Type::Parser.new(vartype).parse[0]
357
- end
358
-
359
- append_signature(vartype.to_s)
360
- align(vartype.alignment)
361
- sub = PacketMarshaller.new(@offset + @packet.bytesize)
362
- sub.append(vartype, vardata)
363
- @packet += sub.packet
354
+ append_variant(val)
364
355
  when Type::ARRAY
365
- if val.is_a?(Hash)
366
- raise TypeException, "Expected an Array but got a Hash" if type.child.sigtype != Type::DICT_ENTRY
367
- # Damn ruby rocks here
368
- val = val.to_a
369
- end
370
- # If string is recieved and ay is expected, explode the string
371
- if val.is_a?(String) && type.child.sigtype == Type::BYTE
372
- val = val.bytes
373
- end
374
- if !val.is_a?(Enumerable)
375
- raise TypeException, "Expected an Enumerable of #{type.child.inspect} but got a #{val.class}"
376
- end
377
- array(type.child) do
378
- val.each do |elem|
379
- append(type.child, elem)
380
- end
381
- end
356
+ append_array(type.child, val)
382
357
  when Type::STRUCT, Type::DICT_ENTRY
383
- # TODO: use duck typing, val.respond_to?
384
- raise TypeException, "Struct/DE expects an Array" if !val.is_a?(Array)
358
+ unless val.is_a?(Array) || val.is_a?(Struct)
359
+ type_name = Type::TYPE_MAPPING[type.sigtype].first
360
+ raise TypeException, "#{type_name} expects an Array or Struct"
361
+ end
362
+
385
363
  if type.sigtype == Type::DICT_ENTRY && val.size != 2
386
- raise TypeException, "Dict entry expects a pair"
364
+ raise TypeException, "DICT_ENTRY expects a pair"
387
365
  end
366
+
388
367
  if type.members.size != val.size
389
- raise TypeException, "Struct/DE has #{val.size} elements but type info for #{type.members.size}"
368
+ type_name = Type::TYPE_MAPPING[type.sigtype].first
369
+ raise TypeException, "#{type_name} has #{val.size} elements but type info for #{type.members.size}"
390
370
  end
371
+
391
372
  struct do
392
373
  type.members.zip(val).each do |t, v|
393
374
  append(t, v)
@@ -397,7 +378,58 @@ module DBus
397
378
  raise NotImplementedError,
398
379
  "sigtype: #{type.sigtype} (#{type.sigtype.chr})"
399
380
  end
400
- end # def append
381
+ end
382
+
383
+ def append_variant(val)
384
+ vartype = nil
385
+ if val.is_a?(Array) && val.size == 2
386
+ case val[0]
387
+ when DBus::Type::Type
388
+ vartype, vardata = val
389
+ when String
390
+ begin
391
+ parsed = Type::Parser.new(val[0]).parse
392
+ vartype = parsed[0] if parsed.size == 1
393
+ vardata = val[1]
394
+ rescue Type::SignatureException
395
+ # no assignment
396
+ end
397
+ end
398
+ end
399
+ if vartype.nil?
400
+ vartype, vardata = PacketMarshaller.make_variant(val)
401
+ vartype = Type::Parser.new(vartype).parse[0]
402
+ end
403
+
404
+ append_signature(vartype.to_s)
405
+ align(vartype.alignment)
406
+ sub = PacketMarshaller.new(@offset + @packet.bytesize)
407
+ sub.append(vartype, vardata)
408
+ @packet += sub.packet
409
+ end
410
+
411
+ # @param child_type [DBus::Type::Type]
412
+ def append_array(child_type, val)
413
+ if val.is_a?(Hash)
414
+ raise TypeException, "Expected an Array but got a Hash" if child_type.sigtype != Type::DICT_ENTRY
415
+
416
+ # Damn ruby rocks here
417
+ val = val.to_a
418
+ end
419
+ # If string is recieved and ay is expected, explode the string
420
+ if val.is_a?(String) && child_type.sigtype == Type::BYTE
421
+ val = val.bytes
422
+ end
423
+ if !val.is_a?(Enumerable)
424
+ raise TypeException, "Expected an Enumerable of #{child_type.inspect} but got a #{val.class}"
425
+ end
426
+
427
+ array(child_type) do
428
+ val.each do |elem|
429
+ append(child_type, elem)
430
+ end
431
+ end
432
+ end
401
433
 
402
434
  # Make a [signature, value] pair for a variant
403
435
  def self.make_variant(value)
@@ -422,12 +454,12 @@ module DBus
422
454
  ["s", value.to_str]
423
455
  elsif value.respond_to? :to_int
424
456
  i = value.to_int
425
- if -2_147_483_648 <= i && i < 2_147_483_648
457
+ if (-2_147_483_648...2_147_483_648).cover?(i)
426
458
  ["i", i]
427
459
  else
428
460
  ["x", i]
429
461
  end
430
462
  end
431
463
  end
432
- end # class PacketMarshaller
433
- end # module DBus
464
+ end
465
+ end