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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8b0c43d3f27f0877e52c0adfb8014374829f97bcd38be6b9fabda1d5da8413c
4
- data.tar.gz: 000eadf163b0fab2ef4ab39de81a79714b34392cd81b3be68e12587ddfa93372
3
+ metadata.gz: 6d6ace164222b10bbf03ab5ac9e2ec3799c8847c4a8f94e1d7f509944bf3e806
4
+ data.tar.gz: 9e8a61b9fa885b36ef21d04f4eb03d4d5a5ab8ec78d5ae258e2433a9e4e5cabe
5
5
  SHA512:
6
- metadata.gz: c2206dcd935fed4e3711b88b2c0c265e5a335700f9137d2c3972f419791d9c25198d0acf9ba7aaecdbc6a25e540005c64321e5165c6c644838fb3557355861ac
7
- data.tar.gz: 212cb8bdcd75e3f35622843f262cdb80eaa07c1919a7d17ffdf601e05c627556343b7855a014f60a220c8a757c201431d66799a6025a98d084aef6cc12f7d8bb
6
+ metadata.gz: a6f4e56b35c43975202769d8085cd690c95f345b09e5c78cd2e89f373105ee0e7edc0d9954ac3c647678a41c18ffb06dbaa806f9bed269b832ad6052a9449afe
7
+ data.tar.gz: 986c96e6a2f74668cd70e111c9fd57c4dd1c30a2e1b2ed66252406bc8fc67a6ec56bc663bae111b0e331e0f8544419d7fb4facabec1a14e1407ca903695d43c9
data/NEWS.md CHANGED
@@ -2,6 +2,49 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## Ruby D-Bus 0.18.0.beta8 - 2022-06-21
6
+
7
+ Bug fixes:
8
+ * Introduced Object#dbus_properties_changed to send correctly typed property
9
+ values ([#115][]). Avoid calling PropertiesChanged directly as it will
10
+ guess the types.
11
+ * Fix Object.dbus_reader to work with attr_accessor and automatically produce
12
+ dbus_properties_changed for properties that are read-write at
13
+ implementation side and read-only at D-Bus side ([#96][])
14
+
15
+ [#96]: https://github.com/mvidner/ruby-dbus/issues/96
16
+
17
+ API:
18
+ * Service side `emits_changed_signal` to control emission of
19
+ PropertiesChanged: can be assigned within `dbus_interface` or as an option
20
+ when declaring properties ([#117][]).
21
+
22
+ [#115]: https://github.com/mvidner/ruby-dbus/issues/115
23
+ [#117]: https://github.com/mvidner/ruby-dbus/pulls/117
24
+
25
+ ## Ruby D-Bus 0.18.0.beta7 - 2022-05-29
26
+
27
+ API:
28
+ * DBus.variant(type, value) is deprecated in favor of
29
+ Data::Variant.new(value, member_type:)
30
+
31
+ Bug fixes:
32
+ * Client-side properties: When calling Properties.Set in
33
+ ProxyObjectInterface#[]=, use the correct type ([#108][]).
34
+
35
+ [#108]: https://github.com/mvidner/ruby-dbus/issues/108
36
+
37
+ ## Ruby D-Bus 0.18.0.beta6 - 2022-05-25
38
+
39
+ API:
40
+ * Data::Base#value returns plain Ruby types;
41
+ Data::Container#exact_value contains Data::Base ([#114][]).
42
+ * Data::Base#initialize and .from_typed allow plain or exact values, validate
43
+ argument types.
44
+ * Implement #== (converting) and #eql? (strict) for Data::Base and DBus::Type.
45
+
46
+ [#114]: https://github.com/mvidner/ruby-dbus/pull/114
47
+
5
48
  ## Ruby D-Bus 0.18.0.beta5 - 2022-04-27
6
49
 
7
50
  API:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.18.0.beta5
1
+ 0.18.0.beta8
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
@@ -35,7 +35,7 @@ module DBus
35
35
  # construct an appropriate {Data::Base} instance.
36
36
  #
37
37
  # @param type [SingleCompleteType,Type]
38
- # @param value [::Object]
38
+ # @param value [::Object,Data::Base] a plain value; exact values also allowed
39
39
  # @return [Data::Base]
40
40
  # @raise TypeError
41
41
  def make_typed(type, value)
@@ -58,9 +58,13 @@ module DBus
58
58
  # @!method self.fixed?
59
59
  # @return [Boolean]
60
60
 
61
- # @return appropriately-typed, valid value
61
+ # @return [::Object] a valid value, plain-Ruby typed.
62
+ # @see Data::Container#exact_value
62
63
  attr_reader :value
63
64
 
65
+ # @!method self.type_code
66
+ # @return [String] a single-character string, for example "a" for arrays
67
+
64
68
  # @!method type
65
69
  # @abstract
66
70
  # Note that for Variants type=="v",
@@ -91,11 +95,18 @@ module DBus
91
95
 
92
96
  # Hash key equality
93
97
  # See https://ruby-doc.org/core-3.0.0/Object.html#method-i-eql-3F
94
- alias eql? ==
98
+ # Stricter than #== (RSpec: eq), 1==1.0 but 1.eql(1.0)->false
99
+ def eql?(other)
100
+ return false unless other.class == self.class
101
+
102
+ other.value.eql?(@value)
103
+ # TODO: this should work, now check derived classes, exact_value
104
+ end
95
105
 
96
106
  # @param type [Type]
97
107
  def self.assert_type_matches_class(type)
98
- raise ArgumentError unless type.sigtype == type_code
108
+ raise ArgumentError, "Expecting #{type_code.inspect} for class #{self}, got #{type.sigtype.inspect}" \
109
+ unless type.sigtype == type_code
99
110
  end
100
111
  end
101
112
 
@@ -480,7 +491,7 @@ module DBus
480
491
  Byte
481
492
  end
482
493
 
483
- # @return [Array<Type>]
494
+ # @return [::Array<Type>]
484
495
  def self.validate_raw!(value)
485
496
  DBus.types(value)
486
497
  rescue Type::SignatureException => e
@@ -510,6 +521,29 @@ module DBus
510
521
  # For containers, the type varies among instances
511
522
  # @see Base#type
512
523
  attr_reader :type
524
+
525
+ # @return something that is, or contains, {Data::Base}.
526
+ # Er, this docs kinda sucks.
527
+ def exact_value
528
+ @value
529
+ end
530
+
531
+ def value
532
+ @value.map(&:value)
533
+ end
534
+
535
+ # Hash key equality
536
+ # See https://ruby-doc.org/core-3.0.0/Object.html#method-i-eql-3F
537
+ # Stricter than #== (RSpec: eq), 1==1.0 but 1.eql(1.0)->false
538
+ def eql?(other)
539
+ return false unless other.class == self.class
540
+
541
+ other.exact_value.eql?(exact_value)
542
+ end
543
+
544
+ # def ==(other)
545
+ # eql?(other) || super
546
+ # end
513
547
  end
514
548
 
515
549
  # An Array, or a Dictionary (Hash).
@@ -538,16 +572,17 @@ module DBus
538
572
  # @param type [Type]
539
573
  # @return [Data::Array]
540
574
  def self.from_typed(value, type:)
541
- assert_type_matches_class(type)
542
- # TODO: validation
543
- member_type = type.child
575
+ new(value, type: type) # initialize(::Array<Data::Base>)
576
+ end
544
577
 
545
- # TODO: Dict??
546
- items = value.map do |i|
547
- Data.make_typed(member_type, i)
578
+ def value
579
+ v = super
580
+ if type.child.sigtype == Type::DICT_ENTRY
581
+ # BTW this makes a copy so mutating it is pointless
582
+ v.to_h
583
+ else
584
+ v
548
585
  end
549
-
550
- new(items, type: type) # initialize(::Array<Data::Base>)
551
586
  end
552
587
 
553
588
  # FIXME: should Data::Array be mutable?
@@ -556,13 +591,29 @@ module DBus
556
591
  # TODO: specify type or guess type?
557
592
  # Data is the exact type, so its constructor should be exact
558
593
  # and guesswork should be clearly labeled
594
+
595
+ # @param value [Data::Array,Enumerable]
559
596
  # @param type [SingleCompleteType,Type]
560
597
  def initialize(value, type:)
561
- type = DBus.type(type) unless type.is_a?(Type)
598
+ type = Type::Factory.make_type(type)
562
599
  self.class.assert_type_matches_class(type)
563
600
  @type = type
564
- # TODO: copy from another Data::Array
565
- super(value)
601
+
602
+ typed_value = case value
603
+ when Data::Array
604
+ unless value.type == type
605
+ raise ArgumentError,
606
+ "Specified type is #{type.inspect} but value type is #{value.type.inspect}"
607
+ end
608
+
609
+ value.exact_value
610
+ else
611
+ # TODO: Dict??
612
+ value.map do |i|
613
+ Data.make_typed(type.child, i)
614
+ end
615
+ end
616
+ super(typed_value)
566
617
  end
567
618
  end
568
619
 
@@ -590,25 +641,74 @@ module DBus
590
641
  # @param type [Type]
591
642
  # @return [Struct]
592
643
  def self.from_typed(value, type:)
593
- # TODO: validation
594
- member_types = type.members
595
- raise unless value.size == member_types.size
596
-
597
- items = member_types.zip(value).map do |item_type, item|
598
- Data.make_typed(item_type, item)
599
- end
600
-
601
- new(items, type: type) # initialize(::Array<Data::Base>)
644
+ new(value, type: type)
602
645
  end
603
646
 
647
+ # @param value [Data::Struct,Enumerable]
648
+ # @param type [SingleCompleteType,Type]
604
649
  def initialize(value, type:)
650
+ type = Type::Factory.make_type(type)
605
651
  self.class.assert_type_matches_class(type)
606
652
  @type = type
607
- super(value)
653
+
654
+ typed_value = case value
655
+ when self.class
656
+ unless value.type == type
657
+ raise ArgumentError,
658
+ "Specified type is #{type.inspect} but value type is #{value.type.inspect}"
659
+ end
660
+
661
+ value.exact_value
662
+ else
663
+ member_types = type.members
664
+ unless value.size == member_types.size
665
+ raise ArgumentError, "Specified type has #{member_types.size} members " \
666
+ "but value has #{value.size} members"
667
+ end
668
+
669
+ member_types.zip(value).map do |item_type, item|
670
+ Data.make_typed(item_type, item)
671
+ end
672
+ end
673
+ super(typed_value)
674
+ end
675
+
676
+ def ==(other)
677
+ case other
678
+ when ::Struct
679
+ @value.size == other.size &&
680
+ @value.zip(other.to_a).all? { |i, other_i| i == other_i }
681
+ else
682
+ super
683
+ end
684
+ end
685
+ end
686
+
687
+ # Dictionary/Hash entry.
688
+ # TODO: shouldn't instantiate?
689
+ class DictEntry < Struct
690
+ def self.type_code
691
+ "e"
692
+ end
693
+
694
+ # @param value [::Array]
695
+ def self.from_items(value, mode:, type:) # rubocop:disable Lint/UnusedMethodArgument
696
+ value.freeze
697
+ # DictEntry ignores the :exact mode
698
+ value
699
+ end
700
+
701
+ # @param value [::Object] (#size, #each)
702
+ # @param type [Type]
703
+ # @return [DictEntry]
704
+ def self.from_typed(value, type:)
705
+ new(value, type: type)
608
706
  end
609
707
  end
610
708
 
611
- # A generic type
709
+ # A generic type.
710
+ #
711
+ # Implementation note: @value is a {Data::Base}.
612
712
  class Variant < Container
613
713
  def self.type_code
614
714
  "v"
@@ -618,6 +718,10 @@ module DBus
618
718
  1
619
719
  end
620
720
 
721
+ def value
722
+ @value.value
723
+ end
724
+
621
725
  # @param member_type [Type]
622
726
  def self.from_items(value, mode:, member_type:)
623
727
  return value if mode == :plain
@@ -631,7 +735,7 @@ module DBus
631
735
  def self.from_typed(value, type:)
632
736
  assert_type_matches_class(type)
633
737
 
634
- # decide on type of value
738
+ # nil: decide on type of value
635
739
  new(value, member_type: nil)
636
740
  end
637
741
 
@@ -651,66 +755,59 @@ module DBus
651
755
  # @return [Type]
652
756
  attr_reader :member_type
653
757
 
758
+ # Determine the type of *value*
759
+ # @param value [::Object]
760
+ # @return [Type]
761
+ # @api private
762
+ # See also {PacketMarshaller.make_variant}
654
763
  def self.guess_type(value)
655
764
  sct, = PacketMarshaller.make_variant(value)
656
765
  DBus.type(sct)
657
766
  end
658
767
 
659
- # @param member_type [Type,nil]
768
+ # @param member_type [SingleCompleteType,Type,nil]
660
769
  def initialize(value, member_type:)
770
+ member_type = Type::Factory.make_type(member_type) if member_type
661
771
  # TODO: validate that the given *member_type* matches *value*
662
- if value.is_a?(self.class)
772
+ case value
773
+ when Data::Variant
663
774
  # Copy the contained value instead of boxing it more
664
775
  # TODO: except perhaps for round-tripping in exact mode?
665
776
  @member_type = value.member_type
666
- value = value.value
777
+ value = value.exact_value
778
+ when Data::Base
779
+ @member_type = member_type || value.type
780
+ raise ArgumentError, "Variant type #{@member_type} does not match value type #{value.type}" \
781
+ unless @member_type == value.type
667
782
  else
668
783
  @member_type = member_type || self.class.guess_type(value)
784
+ value = Data.make_typed(@member_type, value)
669
785
  end
670
786
  super(value)
671
787
  end
672
- end
673
-
674
- # Dictionary/Hash entry.
675
- # TODO: shouldn't instantiate?
676
- class DictEntry < Container
677
- def self.type_code
678
- "e"
679
- end
680
-
681
- def self.alignment
682
- 8
683
- end
684
-
685
- # @param value [::Array]
686
- def self.from_items(value, mode:, type:) # rubocop:disable Lint/UnusedMethodArgument
687
- value.freeze
688
- # DictEntry ignores the :exact mode
689
- value
690
- end
691
788
 
692
- # @param value [::Object] (#size, #each)
693
- # @param type [Type]
694
- # @return [DictEntry]
695
- def self.from_typed(value, type:)
696
- assert_type_matches_class(type)
697
- member_types = type.members
698
- # assert member_types.size == 2
699
- # TODO: duplicated from Struct. Inherit/delegate?
700
- # TODO: validation
701
- raise unless value.size == member_types.size
702
-
703
- items = member_types.zip(value).map do |item_type, item|
704
- Data.make_typed(item_type, item)
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}"
705
800
  end
801
+ end
706
802
 
707
- new(items, type: type) # initialize(::Array<Data::Base>)
803
+ # @see #[]
804
+ def first
805
+ self[0]
708
806
  end
709
807
 
710
- def initialize(value, type:)
711
- self.class.assert_type_matches_class(type)
712
- @type = type
713
- super(value)
808
+ # @see #[]
809
+ def last
810
+ self[1]
714
811
  end
715
812
  end
716
813
 
@@ -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/marshall.rb CHANGED
@@ -250,10 +250,10 @@ module DBus
250
250
  when Type::VARIANT
251
251
  append_variant(val)
252
252
  when Type::ARRAY
253
- val = val.value if val.is_a?(Data::Array)
253
+ val = val.exact_value if val.is_a?(Data::Array)
254
254
  append_array(type.child, val)
255
255
  when Type::STRUCT, Type::DICT_ENTRY
256
- val = val.value if val.is_a?(Data::Struct) || val.is_a?(Data::DictEntry)
256
+ val = val.exact_value if val.is_a?(Data::Struct) || val.is_a?(Data::DictEntry)
257
257
  unless val.is_a?(Array) || val.is_a?(Struct)
258
258
  type_name = Type::TYPE_MAPPING[type.sigtype].first
259
259
  raise TypeException, "#{type_name} expects an Array or Struct, seen #{val.class}"
@@ -284,7 +284,10 @@ module DBus
284
284
  vartype = nil
285
285
  if val.is_a?(DBus::Data::Variant)
286
286
  vartype = val.member_type
287
- vardata = val.value
287
+ vardata = val.exact_value
288
+ elsif val.is_a?(DBus::Data::Container)
289
+ vartype = val.type
290
+ vardata = val.exact_value
288
291
  elsif val.is_a?(DBus::Data::Base)
289
292
  vartype = val.type
290
293
  vardata = val.value