ruby-dbus 0.16.0 → 0.25.0
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 +4 -4
- data/NEWS.md +288 -3
- data/README.md +3 -5
- data/Rakefile +27 -12
- data/VERSION +1 -1
- data/doc/Reference.md +109 -10
- data/examples/doc/_extract_examples +7 -0
- data/examples/no-bus/pulseaudio.rb +50 -0
- data/examples/no-introspect/nm-test.rb +2 -0
- data/examples/no-introspect/tracker-test.rb +3 -1
- data/examples/rhythmbox/playpause.rb +2 -1
- data/examples/service/call_service.rb +2 -1
- data/examples/service/complex-property.rb +21 -0
- data/examples/service/service_newapi.rb +3 -3
- data/examples/simple/call_introspect.rb +1 -0
- data/examples/simple/get_id.rb +6 -3
- data/examples/simple/properties.rb +2 -0
- data/examples/utils/listnames.rb +10 -1
- data/examples/utils/notify.rb +1 -0
- data/lib/dbus/api_options.rb +9 -0
- data/lib/dbus/auth.rb +307 -217
- data/lib/dbus/bus.rb +155 -588
- data/lib/dbus/bus_name.rb +12 -8
- data/lib/dbus/connection.rb +363 -0
- data/lib/dbus/core_ext/class/attribute.rb +1 -1
- data/lib/dbus/data.rb +821 -0
- data/lib/dbus/emits_changed_signal.rb +83 -0
- data/lib/dbus/error.rb +4 -2
- data/lib/dbus/introspect.rb +133 -31
- data/lib/dbus/logger.rb +6 -3
- data/lib/dbus/main.rb +66 -0
- data/lib/dbus/marshall.rb +247 -296
- data/lib/dbus/matchrule.rb +16 -16
- data/lib/dbus/message.rb +66 -57
- data/lib/dbus/message_queue.rb +40 -25
- data/lib/dbus/node_tree.rb +105 -0
- data/lib/dbus/object.rb +442 -33
- data/lib/dbus/object_manager.rb +61 -0
- data/lib/dbus/object_path.rb +11 -6
- data/lib/dbus/object_server.rb +149 -0
- data/lib/dbus/org.freedesktop.DBus.xml +97 -0
- data/lib/dbus/platform.rb +26 -0
- data/lib/dbus/proxy_object.rb +30 -11
- data/lib/dbus/proxy_object_factory.rb +13 -7
- data/lib/dbus/proxy_object_interface.rb +63 -30
- data/lib/dbus/proxy_service.rb +107 -0
- data/lib/dbus/raw_message.rb +91 -0
- data/lib/dbus/type.rb +319 -86
- data/lib/dbus/xml.rb +40 -19
- data/lib/dbus.rb +28 -17
- data/ruby-dbus.gemspec +13 -6
- data/spec/async_spec.rb +2 -0
- data/spec/auth_spec.rb +225 -0
- data/spec/binding_spec.rb +2 -0
- data/spec/bus_and_xml_backend_spec.rb +5 -1
- data/spec/bus_connection_spec.rb +93 -0
- data/spec/bus_name_spec.rb +3 -1
- data/spec/bus_spec.rb +2 -0
- data/spec/byte_array_spec.rb +2 -0
- data/spec/client_robustness_spec.rb +4 -2
- data/spec/connection_spec.rb +37 -0
- data/spec/coverage_helper.rb +39 -0
- data/spec/data/marshall.yaml +1667 -0
- data/spec/data_spec.rb +673 -0
- data/spec/dbus_spec.rb +22 -0
- data/spec/emits_changed_signal_spec.rb +58 -0
- data/spec/err_msg_spec.rb +2 -0
- data/spec/introspect_xml_parser_spec.rb +2 -0
- data/spec/introspection_spec.rb +2 -0
- data/spec/main_loop_spec.rb +17 -1
- data/spec/message_spec.rb +21 -0
- data/spec/mock-service/cockpit-dbustests.rb +29 -0
- data/spec/mock-service/com.redhat.Cockpit.DBusTests.xml +180 -0
- data/spec/mock-service/org.ruby.service.service +4 -0
- data/spec/mock-service/org.rubygems.ruby_dbus.DBusTests.service +4 -0
- data/spec/{service_newapi.rb → mock-service/spaghetti-monster.rb} +122 -75
- data/spec/node_spec.rb +65 -0
- data/spec/object_manager_spec.rb +33 -0
- data/spec/object_path_spec.rb +3 -0
- data/spec/object_server_spec.rb +138 -0
- data/spec/object_spec.rb +194 -0
- data/spec/packet_marshaller_spec.rb +41 -0
- data/spec/packet_unmarshaller_spec.rb +248 -0
- data/spec/platform_spec.rb +14 -0
- data/spec/property_spec.rb +214 -12
- data/spec/proxy_object_interface_spec.rb +35 -0
- data/spec/proxy_object_spec.rb +2 -0
- data/spec/{bus_driver_spec.rb → proxy_service_spec.rb} +15 -8
- data/spec/raw_message_spec.rb +32 -0
- data/spec/server_robustness_spec.rb +20 -6
- data/spec/session_bus_spec.rb +36 -1
- data/spec/session_bus_spec_manual.rb +2 -0
- data/spec/signal_spec.rb +16 -3
- data/spec/spec_helper.rb +25 -33
- data/spec/thread_safety_spec.rb +55 -12
- data/spec/tools/dbus-launch-simple +9 -6
- data/spec/tools/dbus-limited-session.conf +32 -0
- data/spec/tools/test_env +26 -6
- data/spec/type_spec.rb +214 -6
- data/spec/value_spec.rb +16 -1
- data/spec/variant_spec.rb +4 -2
- data/spec/zzz_quit_spec.rb +16 -0
- metadata +92 -20
- data/examples/gdbus/gdbus +0 -257
- data/examples/gdbus/gdbus.glade +0 -98
- data/examples/gdbus/launch.sh +0 -4
- data/spec/server_spec.rb +0 -53
- data/spec/tools/test_server +0 -39
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This file is part of the ruby-dbus project
|
|
4
|
+
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
|
|
5
|
+
# Copyright (C) 2023 Martin Vidner
|
|
6
|
+
#
|
|
7
|
+
# This library is free software; you can redistribute it and/or
|
|
8
|
+
# modify it under the terms of the GNU Lesser General Public
|
|
9
|
+
# License, version 2.1 as published by the Free Software Foundation.
|
|
10
|
+
# See the file "COPYING" for the exact licensing terms.
|
|
11
|
+
|
|
12
|
+
require_relative "node_tree"
|
|
13
|
+
|
|
14
|
+
module DBus
|
|
15
|
+
# Used by clients to represent a named service on the other side of the bus.
|
|
16
|
+
#
|
|
17
|
+
# Formerly this class was intermixed with {ObjectServer} as Service.
|
|
18
|
+
#
|
|
19
|
+
# @example Usage
|
|
20
|
+
# svc = DBus.system_bus["org.freedesktop.machine1"]
|
|
21
|
+
# manager = svc["/org/freedesktop/machine1"]
|
|
22
|
+
# p manager.ListImages
|
|
23
|
+
class ProxyService < NodeTree
|
|
24
|
+
# @return [BusName,nil] The service name.
|
|
25
|
+
# Will be nil for a {PeerConnection}
|
|
26
|
+
attr_reader :name
|
|
27
|
+
# @return [Connection] The connection we're using.
|
|
28
|
+
attr_reader :connection
|
|
29
|
+
|
|
30
|
+
# @param connection [Connection] The connection we're using.
|
|
31
|
+
def initialize(name, connection)
|
|
32
|
+
@name = BusName.new(name)
|
|
33
|
+
@connection = connection
|
|
34
|
+
super()
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Determine whether the service name already exists.
|
|
38
|
+
def exists?
|
|
39
|
+
bus = connection # TODO: raise a better error if this is a peer connection
|
|
40
|
+
bus.proxy.ListNames[0].member?(@name)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Perform an introspection on all the objects on the service
|
|
44
|
+
# (starting recursively from the root).
|
|
45
|
+
def introspect
|
|
46
|
+
raise NotImplementedError if block_given?
|
|
47
|
+
|
|
48
|
+
rec_introspect(@root, "/")
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Retrieves an object at the given _path_.
|
|
53
|
+
# @param path [ObjectPath]
|
|
54
|
+
# @return [ProxyObject]
|
|
55
|
+
def [](path)
|
|
56
|
+
object(path, api: ApiOptions::A1)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Retrieves an object at the given _path_
|
|
60
|
+
# whose methods always return an array.
|
|
61
|
+
# @param path [ObjectPath]
|
|
62
|
+
# @param api [ApiOptions]
|
|
63
|
+
# @return [ProxyObject]
|
|
64
|
+
def object(path, api: ApiOptions::A0)
|
|
65
|
+
node = get_node(path, create: true)
|
|
66
|
+
if node.object.nil? || node.object.api != api
|
|
67
|
+
node.object = ProxyObject.new(
|
|
68
|
+
@connection, @name, path,
|
|
69
|
+
api: api
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
node.object
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# Perform a recursive retrospection on the given current _node_
|
|
78
|
+
# on the given _path_.
|
|
79
|
+
def rec_introspect(node, path)
|
|
80
|
+
xml = connection.introspect_data(@name, path)
|
|
81
|
+
intfs, subnodes = IntrospectXMLParser.new(xml).parse
|
|
82
|
+
subnodes.each do |nodename|
|
|
83
|
+
subnode = node[nodename] = Node.new(nodename)
|
|
84
|
+
subpath = if path == "/"
|
|
85
|
+
"/#{nodename}"
|
|
86
|
+
else
|
|
87
|
+
"#{path}/#{nodename}"
|
|
88
|
+
end
|
|
89
|
+
rec_introspect(subnode, subpath)
|
|
90
|
+
end
|
|
91
|
+
return if intfs.empty?
|
|
92
|
+
|
|
93
|
+
node.object = ProxyObjectFactory.new(xml, @connection, @name, path).build
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# A hack for pretending that a {PeerConnection} has a single unnamed {ProxyService}
|
|
98
|
+
# so that we can get {ProxyObject}s from it.
|
|
99
|
+
class ProxyPeerService < ProxyService
|
|
100
|
+
# @param connection [Connection] The peer connection we're using.
|
|
101
|
+
def initialize(connection)
|
|
102
|
+
# this way we disallow ProxyService taking a nil name by accident
|
|
103
|
+
super(":0.0", connection)
|
|
104
|
+
@name = nil
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
# A message while it is being parsed: a binary string,
|
|
13
|
+
# with a position cursor (*pos*), and an *endianness* tag.
|
|
14
|
+
class RawMessage
|
|
15
|
+
# @return [String]
|
|
16
|
+
# attr_reader :bytes
|
|
17
|
+
|
|
18
|
+
# @return [Integer] position in the byte buffer
|
|
19
|
+
attr_reader :pos
|
|
20
|
+
|
|
21
|
+
# @return [:little,:big]
|
|
22
|
+
attr_reader :endianness
|
|
23
|
+
|
|
24
|
+
# @param bytes [String]
|
|
25
|
+
# @param endianness [:little,:big,nil]
|
|
26
|
+
# if not given, read the 1st byte of *bytes*
|
|
27
|
+
def initialize(bytes, endianness = nil)
|
|
28
|
+
@bytes = bytes
|
|
29
|
+
@pos = 0
|
|
30
|
+
@endianness = endianness || self.class.endianness(@bytes[0])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Get the endiannes switch as a Symbol,
|
|
34
|
+
# which will make using it slightly more efficient
|
|
35
|
+
# @param tag_char [String]
|
|
36
|
+
# @return [:little,:big]
|
|
37
|
+
def self.endianness(tag_char)
|
|
38
|
+
case tag_char
|
|
39
|
+
when LIL_END
|
|
40
|
+
:little
|
|
41
|
+
when BIG_END
|
|
42
|
+
:big
|
|
43
|
+
else
|
|
44
|
+
raise InvalidPacketException, "Incorrect endianness #{tag_char.inspect}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [void]
|
|
49
|
+
# @raise IncompleteBufferException if there are not enough bytes remaining
|
|
50
|
+
def want!(size)
|
|
51
|
+
raise IncompleteBufferException if @pos + size > @bytes.bytesize
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [String]
|
|
55
|
+
# @raise IncompleteBufferException if there are not enough bytes remaining
|
|
56
|
+
# TODO: stress test this with encodings. always binary?
|
|
57
|
+
def read(size)
|
|
58
|
+
want!(size)
|
|
59
|
+
ret = @bytes.slice(@pos, size)
|
|
60
|
+
@pos += size
|
|
61
|
+
ret
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [String]
|
|
65
|
+
# @api private
|
|
66
|
+
def remaining_bytes
|
|
67
|
+
# This returns "" if pos is just past the end of the string,
|
|
68
|
+
# and nil if it is further.
|
|
69
|
+
@bytes[@pos..-1]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Align the *pos* index on a multiple of *alignment*
|
|
73
|
+
# @param alignment [Integer] must be 1, 2, 4 or 8
|
|
74
|
+
# @return [void]
|
|
75
|
+
def align(alignment)
|
|
76
|
+
case alignment
|
|
77
|
+
when 1
|
|
78
|
+
nil
|
|
79
|
+
when 2, 4, 8
|
|
80
|
+
bits = alignment - 1
|
|
81
|
+
pad_size = ((@pos + bits) & ~bits) - @pos
|
|
82
|
+
pad = read(pad_size)
|
|
83
|
+
unless pad.bytes.all?(&:zero?)
|
|
84
|
+
raise InvalidPacketException, "Alignment bytes are not NUL"
|
|
85
|
+
end
|
|
86
|
+
else
|
|
87
|
+
raise ArgumentError, "Unsupported alignment #{alignment}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/dbus/type.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# dbus/type.rb - module containing low-level D-Bus data type information
|
|
2
4
|
#
|
|
3
5
|
# This file is part of the ruby-dbus project
|
|
@@ -9,13 +11,33 @@
|
|
|
9
11
|
# See the file "COPYING" for the exact licensing terms.
|
|
10
12
|
|
|
11
13
|
module DBus
|
|
12
|
-
#
|
|
14
|
+
# Like a {Signature} but containing only a single complete type.
|
|
15
|
+
#
|
|
16
|
+
# For documentation purposes only.
|
|
17
|
+
class SingleCompleteType < String; end
|
|
18
|
+
|
|
19
|
+
# Zero or more {SingleCompleteType}s; its own type code is "g".
|
|
20
|
+
# For example "ssv" for a method taking two Strings and a Variant/
|
|
21
|
+
#
|
|
22
|
+
# For documentation purposes only.
|
|
23
|
+
class Signature < String; end
|
|
24
|
+
|
|
25
|
+
# Similar to {Signature} but for {DBus::Object.define_method},
|
|
26
|
+
# contains names and direction of the parameters.
|
|
27
|
+
# For example "in query:s, in case_sensitive:b, out results:ao".
|
|
13
28
|
#
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
# For documentation purposes only.
|
|
30
|
+
class Prototype < String; end
|
|
31
|
+
|
|
32
|
+
# Represents the D-Bus types.
|
|
33
|
+
#
|
|
34
|
+
# Corresponds to {SingleCompleteType}.
|
|
35
|
+
# Instances are immutable/frozen once fully constructed.
|
|
36
|
+
#
|
|
37
|
+
# See also {DBus::Data::Signature} which is "type on the wire".
|
|
38
|
+
class Type
|
|
17
39
|
# Mapping from type number to name and alignment.
|
|
18
|
-
|
|
40
|
+
TYPE_MAPPING = {
|
|
19
41
|
0 => ["INVALID", nil],
|
|
20
42
|
"y" => ["BYTE", 1],
|
|
21
43
|
"b" => ["BOOLEAN", 4],
|
|
@@ -36,7 +58,7 @@ module DBus
|
|
|
36
58
|
"h" => ["UNIX_FD", 4]
|
|
37
59
|
}.freeze
|
|
38
60
|
# Defines the set of constants
|
|
39
|
-
|
|
61
|
+
TYPE_MAPPING.each_pair do |key, value|
|
|
40
62
|
Type.const_set(value.first, key)
|
|
41
63
|
end
|
|
42
64
|
|
|
@@ -44,87 +66,141 @@ module DBus
|
|
|
44
66
|
class SignatureException < Exception
|
|
45
67
|
end
|
|
46
68
|
|
|
47
|
-
#
|
|
69
|
+
# Formerly this was a Module and there was a DBus::Type::Type class
|
|
70
|
+
# but the class got too prominent to keep its double double name.
|
|
71
|
+
# This is for backward compatibility.
|
|
72
|
+
Type = self # rubocop:disable Naming/ConstantName
|
|
73
|
+
|
|
74
|
+
# @return [String] the signature type character, eg "s" or "e".
|
|
75
|
+
attr_reader :sigtype
|
|
76
|
+
# @return [Array<Type>] contained member types.
|
|
77
|
+
attr_reader :members
|
|
78
|
+
|
|
79
|
+
# Use {DBus.type} instead, because this allows constructing
|
|
80
|
+
# incomplete or invalid types, for backward compatibility.
|
|
48
81
|
#
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
raise SignatureException, "Unknown
|
|
82
|
+
# @param abstract [Boolean] allow abstract types "r" and "e"
|
|
83
|
+
# (Enabled for internal usage by {Parser}.)
|
|
84
|
+
def initialize(sigtype, abstract: false)
|
|
85
|
+
if !TYPE_MAPPING.keys.member?(sigtype)
|
|
86
|
+
case sigtype
|
|
87
|
+
when ")"
|
|
88
|
+
raise SignatureException, "STRUCT unexpectedly closed: )"
|
|
89
|
+
when "}"
|
|
90
|
+
raise SignatureException, "DICT_ENTRY unexpectedly closed: }"
|
|
91
|
+
else
|
|
92
|
+
raise SignatureException, "Unknown type code #{sigtype.inspect}"
|
|
60
93
|
end
|
|
61
|
-
@sigtype = sigtype
|
|
62
|
-
@members = []
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Return the required alignment for the type.
|
|
66
|
-
def alignment
|
|
67
|
-
TypeMapping[@sigtype].last
|
|
68
94
|
end
|
|
69
95
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def to_s
|
|
73
|
-
case @sigtype
|
|
96
|
+
unless abstract
|
|
97
|
+
case sigtype
|
|
74
98
|
when STRUCT
|
|
75
|
-
"
|
|
76
|
-
when ARRAY
|
|
77
|
-
"a" + child.to_s
|
|
99
|
+
raise SignatureException, "Abstract STRUCT, use \"(...)\" instead of \"#{STRUCT}\""
|
|
78
100
|
when DICT_ENTRY
|
|
79
|
-
"{"
|
|
80
|
-
else
|
|
81
|
-
if !TypeMapping.keys.member?(@sigtype)
|
|
82
|
-
raise NotImplementedError
|
|
83
|
-
end
|
|
84
|
-
@sigtype.chr
|
|
101
|
+
raise SignatureException, "Abstract DICT_ENTRY, use \"{..}\" instead of \"#{DICT_ENTRY}\""
|
|
85
102
|
end
|
|
86
103
|
end
|
|
87
104
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
@members << a
|
|
105
|
+
@sigtype = sigtype.freeze
|
|
106
|
+
@members = [] # not frozen yet, Parser#parse_one or Factory will do it
|
|
107
|
+
freeze
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# A Type is equal to
|
|
111
|
+
# - another Type with the same string representation
|
|
112
|
+
# - a String ({SingleCompleteType}) describing the type
|
|
113
|
+
def ==(other)
|
|
114
|
+
case other
|
|
115
|
+
when ::String
|
|
116
|
+
to_s == other
|
|
117
|
+
else
|
|
118
|
+
eql?(other)
|
|
105
119
|
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# A Type is eql? to
|
|
123
|
+
# - another Type with the same string representation
|
|
124
|
+
#
|
|
125
|
+
# Hash key equality
|
|
126
|
+
# See https://ruby-doc.org/core-3.0.0/Object.html#method-i-eql-3F
|
|
127
|
+
def eql?(other)
|
|
128
|
+
return false unless other.is_a?(Type)
|
|
129
|
+
|
|
130
|
+
@sigtype == other.sigtype && @members == other.members
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Return the required alignment for the type.
|
|
134
|
+
def alignment
|
|
135
|
+
TYPE_MAPPING[@sigtype].last
|
|
136
|
+
end
|
|
106
137
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
138
|
+
# Return a string representation of the type according to the
|
|
139
|
+
# D-Bus specification.
|
|
140
|
+
def to_s
|
|
141
|
+
case @sigtype
|
|
142
|
+
when STRUCT
|
|
143
|
+
"(#{@members.collect(&:to_s).join})"
|
|
144
|
+
when ARRAY
|
|
145
|
+
"a#{child}"
|
|
146
|
+
when DICT_ENTRY
|
|
147
|
+
"{#{@members.collect(&:to_s).join}}"
|
|
148
|
+
else
|
|
149
|
+
@sigtype.chr
|
|
110
150
|
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Add a new member type _item_.
|
|
154
|
+
# @param item [Type]
|
|
155
|
+
def <<(item)
|
|
156
|
+
raise ArgumentError unless item.is_a?(Type)
|
|
111
157
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
158
|
+
if ![STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype)
|
|
159
|
+
raise SignatureException
|
|
160
|
+
end
|
|
161
|
+
raise SignatureException if @sigtype == ARRAY && !@members.empty?
|
|
162
|
+
|
|
163
|
+
if @sigtype == DICT_ENTRY
|
|
164
|
+
case @members.size
|
|
165
|
+
when 2
|
|
166
|
+
raise SignatureException,
|
|
167
|
+
"DICT_ENTRY must have 2 subtypes, found 3 or more: #{@members.inspect} << #{item.inspect}"
|
|
168
|
+
when 0
|
|
169
|
+
if [STRUCT, ARRAY, DICT_ENTRY, VARIANT].member?(item.sigtype)
|
|
170
|
+
raise SignatureException, "DICT_ENTRY key must be basic (non-container)"
|
|
171
|
+
end
|
|
116
172
|
end
|
|
117
|
-
s
|
|
118
173
|
end
|
|
119
|
-
|
|
174
|
+
@members << item
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Return the first contained member type.
|
|
178
|
+
def child
|
|
179
|
+
@members[0]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def inspect
|
|
183
|
+
s = TYPE_MAPPING[@sigtype].first
|
|
184
|
+
if [STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype)
|
|
185
|
+
s += ": #{@members.inspect}"
|
|
186
|
+
end
|
|
187
|
+
s
|
|
188
|
+
end
|
|
120
189
|
|
|
121
190
|
# = D-Bus type parser class
|
|
122
191
|
#
|
|
123
192
|
# Helper class to parse a type signature in the protocol.
|
|
193
|
+
# @api private
|
|
124
194
|
class Parser
|
|
125
195
|
# Create a new parser for the given _signature_.
|
|
196
|
+
# @param signature [Signature]
|
|
126
197
|
def initialize(signature)
|
|
127
198
|
@signature = signature
|
|
199
|
+
if signature.size > 255
|
|
200
|
+
msg = "Potential signature is longer than 255 characters (#{@signature.size}): #{@signature}"
|
|
201
|
+
raise SignatureException, msg
|
|
202
|
+
end
|
|
203
|
+
|
|
128
204
|
@idx = 0
|
|
129
205
|
end
|
|
130
206
|
|
|
@@ -135,57 +211,214 @@ module DBus
|
|
|
135
211
|
c
|
|
136
212
|
end
|
|
137
213
|
|
|
138
|
-
# Parse one character
|
|
139
|
-
|
|
214
|
+
# Parse one character _char_ of the signature.
|
|
215
|
+
# @param for_array [Boolean] are we parsing an immediate child of an ARRAY
|
|
216
|
+
# @return [Type]
|
|
217
|
+
def parse_one(char, for_array: false)
|
|
140
218
|
res = nil
|
|
141
|
-
case
|
|
219
|
+
case char
|
|
142
220
|
when "a"
|
|
143
221
|
res = Type.new(ARRAY)
|
|
144
|
-
|
|
145
|
-
raise SignatureException, "
|
|
146
|
-
|
|
222
|
+
char = nextchar
|
|
223
|
+
raise SignatureException, "Empty ARRAY in #{@signature}" if char.nil?
|
|
224
|
+
|
|
225
|
+
child = parse_one(char, for_array: true)
|
|
147
226
|
res << child
|
|
148
227
|
when "("
|
|
149
|
-
res = Type.new(STRUCT)
|
|
150
|
-
while (
|
|
151
|
-
res << parse_one(
|
|
228
|
+
res = Type.new(STRUCT, abstract: true)
|
|
229
|
+
while (char = nextchar) && char != ")"
|
|
230
|
+
res << parse_one(char)
|
|
152
231
|
end
|
|
153
|
-
raise SignatureException, "
|
|
232
|
+
raise SignatureException, "STRUCT not closed in #{@signature}" if char.nil?
|
|
233
|
+
raise SignatureException, "Empty STRUCT in #{@signature}" if res.members.empty?
|
|
154
234
|
when "{"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
235
|
+
raise SignatureException, "DICT_ENTRY not an immediate child of an ARRAY" unless for_array
|
|
236
|
+
|
|
237
|
+
res = Type.new(DICT_ENTRY, abstract: true)
|
|
238
|
+
|
|
239
|
+
# key type, value type
|
|
240
|
+
2.times do |i|
|
|
241
|
+
char = nextchar
|
|
242
|
+
raise SignatureException, "DICT_ENTRY not closed in #{@signature}" if char.nil?
|
|
243
|
+
|
|
244
|
+
raise SignatureException, "DICT_ENTRY must have 2 subtypes, found #{i} in #{@signature}" if char == "}"
|
|
245
|
+
|
|
246
|
+
res << parse_one(char)
|
|
158
247
|
end
|
|
159
|
-
|
|
248
|
+
|
|
249
|
+
# closing "}"
|
|
250
|
+
char = nextchar
|
|
251
|
+
raise SignatureException, "DICT_ENTRY not closed in #{@signature}" if char.nil?
|
|
252
|
+
|
|
253
|
+
raise SignatureException, "DICT_ENTRY must have 2 subtypes, found 3 or more in #{@signature}" if char != "}"
|
|
160
254
|
else
|
|
161
|
-
res = Type.new(
|
|
255
|
+
res = Type.new(char)
|
|
162
256
|
end
|
|
257
|
+
res.members.freeze
|
|
163
258
|
res
|
|
164
259
|
end
|
|
165
260
|
|
|
166
261
|
# Parse the entire signature, return a DBus::Type object.
|
|
262
|
+
# @return [Array<Type>]
|
|
167
263
|
def parse
|
|
168
264
|
@idx = 0
|
|
169
265
|
ret = []
|
|
170
266
|
while (c = nextchar)
|
|
171
267
|
ret << parse_one(c)
|
|
172
268
|
end
|
|
173
|
-
ret
|
|
269
|
+
ret.freeze
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Parse one {SingleCompleteType}
|
|
273
|
+
# @return [Type]
|
|
274
|
+
def parse1
|
|
275
|
+
c = nextchar
|
|
276
|
+
raise SignatureException, "Empty signature, expecting a Single Complete Type" if c.nil?
|
|
277
|
+
|
|
278
|
+
t = parse_one(c)
|
|
279
|
+
raise SignatureException, "Has more than a Single Complete Type: #{@signature}" unless nextchar.nil?
|
|
280
|
+
|
|
281
|
+
t.freeze
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
class Factory
|
|
286
|
+
# @param type [Type,SingleCompleteType,Class]
|
|
287
|
+
# @see from_plain_class
|
|
288
|
+
# @return [Type] (frozen)
|
|
289
|
+
def self.make_type(type)
|
|
290
|
+
case type
|
|
291
|
+
when Type
|
|
292
|
+
type
|
|
293
|
+
when String
|
|
294
|
+
DBus.type(type)
|
|
295
|
+
when Class
|
|
296
|
+
from_plain_class(type)
|
|
297
|
+
else
|
|
298
|
+
msg = "Expecting DBus::Type, DBus::SingleCompleteType(aka ::String), or Class, got #{type.inspect}"
|
|
299
|
+
raise ArgumentError, msg
|
|
300
|
+
end
|
|
174
301
|
end
|
|
175
|
-
|
|
176
|
-
|
|
302
|
+
|
|
303
|
+
# Make a {Type} corresponding to some plain classes:
|
|
304
|
+
# - String
|
|
305
|
+
# - Float
|
|
306
|
+
# - DBus::ObjectPath
|
|
307
|
+
# - DBus::Signature, DBus::SingleCompleteType
|
|
308
|
+
# @param klass [Class]
|
|
309
|
+
# @return [Type] (frozen)
|
|
310
|
+
def self.from_plain_class(klass)
|
|
311
|
+
@signature_type ||= DBus.type(SIGNATURE)
|
|
312
|
+
@class_to_type ||= {
|
|
313
|
+
DBus::ObjectPath => DBus.type(OBJECT_PATH),
|
|
314
|
+
DBus::Signature => @signature_type,
|
|
315
|
+
DBus::SingleCompleteType => @signature_type,
|
|
316
|
+
String => DBus.type(STRING),
|
|
317
|
+
Float => DBus.type(DOUBLE)
|
|
318
|
+
}
|
|
319
|
+
t = @class_to_type[klass]
|
|
320
|
+
raise ArgumentError, "Cannot convert plain class #{klass} to a D-Bus type" if t.nil?
|
|
321
|
+
|
|
322
|
+
t
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Syntactic helper for constructing an array Type.
|
|
327
|
+
# You may be looking for {Data::Array} instead.
|
|
328
|
+
# @example
|
|
329
|
+
# t = Type::Array[Type::INT16]
|
|
330
|
+
class ArrayFactory < Factory
|
|
331
|
+
# @param member_type [Type,SingleCompleteType]
|
|
332
|
+
# @return [Type] (frozen)
|
|
333
|
+
def self.[](member_type)
|
|
334
|
+
t = Type.new(ARRAY)
|
|
335
|
+
t << make_type(member_type)
|
|
336
|
+
t.members.freeze
|
|
337
|
+
t
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# @example
|
|
342
|
+
# t = Type::Array[Type::INT16]
|
|
343
|
+
Array = ArrayFactory
|
|
344
|
+
|
|
345
|
+
# Syntactic helper for constructing a hash Type.
|
|
346
|
+
# You may be looking for {Data::Array} and {Data::DictEntry} instead.
|
|
347
|
+
# @example
|
|
348
|
+
# t = Type::Hash[Type::STRING, Type::VARIANT]
|
|
349
|
+
class HashFactory < Factory
|
|
350
|
+
# @param key_type [Type,SingleCompleteType]
|
|
351
|
+
# @param value_type [Type,SingleCompleteType]
|
|
352
|
+
# @return [Type] (frozen)
|
|
353
|
+
def self.[](key_type, value_type)
|
|
354
|
+
t = Type.new(ARRAY)
|
|
355
|
+
de = Type.new(DICT_ENTRY, abstract: true)
|
|
356
|
+
de << make_type(key_type)
|
|
357
|
+
de << make_type(value_type)
|
|
358
|
+
de.members.freeze
|
|
359
|
+
t << de
|
|
360
|
+
t.members.freeze
|
|
361
|
+
t
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# @example
|
|
366
|
+
# t = Type::Hash[Type::INT16]
|
|
367
|
+
Hash = HashFactory
|
|
368
|
+
|
|
369
|
+
# Syntactic helper for constructing a struct Type.
|
|
370
|
+
# You may be looking for {Data::Struct} instead.
|
|
371
|
+
# @example
|
|
372
|
+
# t = Type::Struct[Type::INT16, Type::STRING]
|
|
373
|
+
class StructFactory < Factory
|
|
374
|
+
# @param member_types [::Array<Type,SingleCompleteType>]
|
|
375
|
+
# @return [Type] (frozen)
|
|
376
|
+
def self.[](*member_types)
|
|
377
|
+
raise ArgumentError if member_types.empty?
|
|
378
|
+
|
|
379
|
+
t = Type.new(STRUCT, abstract: true)
|
|
380
|
+
member_types.each do |mt|
|
|
381
|
+
t << make_type(mt)
|
|
382
|
+
end
|
|
383
|
+
t.members.freeze
|
|
384
|
+
t
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# @example
|
|
389
|
+
# t = Type::Struct[Type::INT16, Type::STRING]
|
|
390
|
+
Struct = StructFactory
|
|
391
|
+
end
|
|
177
392
|
|
|
178
393
|
# shortcuts
|
|
179
394
|
|
|
180
|
-
# Parse a String to a DBus::Type
|
|
395
|
+
# Parse a String to a valid {DBus::Type}.
|
|
396
|
+
# This is prefered to {Type#initialize} which allows
|
|
397
|
+
# incomplete or invalid types.
|
|
398
|
+
# @param string_type [SingleCompleteType]
|
|
399
|
+
# @return [DBus::Type] (frozen)
|
|
400
|
+
# @raise SignatureException
|
|
181
401
|
def type(string_type)
|
|
182
|
-
Type::Parser.new(string_type).
|
|
402
|
+
Type::Parser.new(string_type).parse1
|
|
183
403
|
end
|
|
184
404
|
module_function :type
|
|
185
405
|
|
|
406
|
+
# Parse a String to zero or more {DBus::Type}s.
|
|
407
|
+
# @param string_type [Signature]
|
|
408
|
+
# @return [Array<DBus::Type>] (frozen)
|
|
409
|
+
# @raise SignatureException
|
|
410
|
+
def types(string_type)
|
|
411
|
+
Type::Parser.new(string_type).parse
|
|
412
|
+
end
|
|
413
|
+
module_function :types
|
|
414
|
+
|
|
186
415
|
# Make an explicit [Type, value] pair
|
|
416
|
+
# @param string_type [SingleCompleteType]
|
|
417
|
+
# @param value [::Object]
|
|
418
|
+
# @return [Array(DBus::Type::Type,::Object)]
|
|
419
|
+
# @deprecated Use {Data::Variant#initialize} instead
|
|
187
420
|
def variant(string_type, value)
|
|
188
|
-
|
|
421
|
+
Data::Variant.new(value, member_type: string_type)
|
|
189
422
|
end
|
|
190
423
|
module_function :variant
|
|
191
|
-
end
|
|
424
|
+
end
|