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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +288 -3
  3. data/README.md +3 -5
  4. data/Rakefile +27 -12
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +109 -10
  7. data/examples/doc/_extract_examples +7 -0
  8. data/examples/no-bus/pulseaudio.rb +50 -0
  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/complex-property.rb +21 -0
  14. data/examples/service/service_newapi.rb +3 -3
  15. data/examples/simple/call_introspect.rb +1 -0
  16. data/examples/simple/get_id.rb +6 -3
  17. data/examples/simple/properties.rb +2 -0
  18. data/examples/utils/listnames.rb +10 -1
  19. data/examples/utils/notify.rb +1 -0
  20. data/lib/dbus/api_options.rb +9 -0
  21. data/lib/dbus/auth.rb +307 -217
  22. data/lib/dbus/bus.rb +155 -588
  23. data/lib/dbus/bus_name.rb +12 -8
  24. data/lib/dbus/connection.rb +363 -0
  25. data/lib/dbus/core_ext/class/attribute.rb +1 -1
  26. data/lib/dbus/data.rb +821 -0
  27. data/lib/dbus/emits_changed_signal.rb +83 -0
  28. data/lib/dbus/error.rb +4 -2
  29. data/lib/dbus/introspect.rb +133 -31
  30. data/lib/dbus/logger.rb +6 -3
  31. data/lib/dbus/main.rb +66 -0
  32. data/lib/dbus/marshall.rb +247 -296
  33. data/lib/dbus/matchrule.rb +16 -16
  34. data/lib/dbus/message.rb +66 -57
  35. data/lib/dbus/message_queue.rb +40 -25
  36. data/lib/dbus/node_tree.rb +105 -0
  37. data/lib/dbus/object.rb +442 -33
  38. data/lib/dbus/object_manager.rb +61 -0
  39. data/lib/dbus/object_path.rb +11 -6
  40. data/lib/dbus/object_server.rb +149 -0
  41. data/lib/dbus/org.freedesktop.DBus.xml +97 -0
  42. data/lib/dbus/platform.rb +26 -0
  43. data/lib/dbus/proxy_object.rb +30 -11
  44. data/lib/dbus/proxy_object_factory.rb +13 -7
  45. data/lib/dbus/proxy_object_interface.rb +63 -30
  46. data/lib/dbus/proxy_service.rb +107 -0
  47. data/lib/dbus/raw_message.rb +91 -0
  48. data/lib/dbus/type.rb +319 -86
  49. data/lib/dbus/xml.rb +40 -19
  50. data/lib/dbus.rb +28 -17
  51. data/ruby-dbus.gemspec +13 -6
  52. data/spec/async_spec.rb +2 -0
  53. data/spec/auth_spec.rb +225 -0
  54. data/spec/binding_spec.rb +2 -0
  55. data/spec/bus_and_xml_backend_spec.rb +5 -1
  56. data/spec/bus_connection_spec.rb +93 -0
  57. data/spec/bus_name_spec.rb +3 -1
  58. data/spec/bus_spec.rb +2 -0
  59. data/spec/byte_array_spec.rb +2 -0
  60. data/spec/client_robustness_spec.rb +4 -2
  61. data/spec/connection_spec.rb +37 -0
  62. data/spec/coverage_helper.rb +39 -0
  63. data/spec/data/marshall.yaml +1667 -0
  64. data/spec/data_spec.rb +673 -0
  65. data/spec/dbus_spec.rb +22 -0
  66. data/spec/emits_changed_signal_spec.rb +58 -0
  67. data/spec/err_msg_spec.rb +2 -0
  68. data/spec/introspect_xml_parser_spec.rb +2 -0
  69. data/spec/introspection_spec.rb +2 -0
  70. data/spec/main_loop_spec.rb +17 -1
  71. data/spec/message_spec.rb +21 -0
  72. data/spec/mock-service/cockpit-dbustests.rb +29 -0
  73. data/spec/mock-service/com.redhat.Cockpit.DBusTests.xml +180 -0
  74. data/spec/mock-service/org.ruby.service.service +4 -0
  75. data/spec/mock-service/org.rubygems.ruby_dbus.DBusTests.service +4 -0
  76. data/spec/{service_newapi.rb → mock-service/spaghetti-monster.rb} +122 -75
  77. data/spec/node_spec.rb +65 -0
  78. data/spec/object_manager_spec.rb +33 -0
  79. data/spec/object_path_spec.rb +3 -0
  80. data/spec/object_server_spec.rb +138 -0
  81. data/spec/object_spec.rb +194 -0
  82. data/spec/packet_marshaller_spec.rb +41 -0
  83. data/spec/packet_unmarshaller_spec.rb +248 -0
  84. data/spec/platform_spec.rb +14 -0
  85. data/spec/property_spec.rb +214 -12
  86. data/spec/proxy_object_interface_spec.rb +35 -0
  87. data/spec/proxy_object_spec.rb +2 -0
  88. data/spec/{bus_driver_spec.rb → proxy_service_spec.rb} +15 -8
  89. data/spec/raw_message_spec.rb +32 -0
  90. data/spec/server_robustness_spec.rb +20 -6
  91. data/spec/session_bus_spec.rb +36 -1
  92. data/spec/session_bus_spec_manual.rb +2 -0
  93. data/spec/signal_spec.rb +16 -3
  94. data/spec/spec_helper.rb +25 -33
  95. data/spec/thread_safety_spec.rb +55 -12
  96. data/spec/tools/dbus-launch-simple +9 -6
  97. data/spec/tools/dbus-limited-session.conf +32 -0
  98. data/spec/tools/test_env +26 -6
  99. data/spec/type_spec.rb +214 -6
  100. data/spec/value_spec.rb +16 -1
  101. data/spec/variant_spec.rb +4 -2
  102. data/spec/zzz_quit_spec.rb +16 -0
  103. metadata +92 -20
  104. data/examples/gdbus/gdbus +0 -257
  105. data/examples/gdbus/gdbus.glade +0 -98
  106. data/examples/gdbus/launch.sh +0 -4
  107. data/spec/server_spec.rb +0 -53
  108. data/spec/tools/test_server +0 -39
@@ -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
data/lib/dbus/error.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # error.rb
2
4
  #
3
5
  # This file is part of the ruby-dbus project
@@ -32,7 +34,7 @@ module DBus
32
34
  end
33
35
  # TODO: validate error name
34
36
  end
35
- end # class Error
37
+ end
36
38
 
37
39
  # @example raise a generic error
38
40
  # raise DBus.error, "message"
@@ -43,4 +45,4 @@ module DBus
43
45
  DBus::Error.new(nil, name)
44
46
  end
45
47
  module_function :error
46
- end # module DBus
48
+ end
@@ -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,9 +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_]*$/
17
+ INTERFACE_ELEMENT_RE = /^[A-Za-z][A-Za-z0-9_]*$/.freeze
16
18
 
17
19
  # Exception raised when an invalid class definition is encountered.
18
20
  class InvalidClassDefinition < Exception
@@ -24,21 +26,42 @@ module DBus
24
26
  # method call instantiates and configures this class for us.
25
27
  #
26
28
  # It also is the local definition of interface exported by the program.
27
- # At the client side, see ProxyObjectInterface
29
+ # At the client side, see {ProxyObjectInterface}.
28
30
  class Interface
29
- # The name of the interface. String
31
+ # @return [String] The name of the interface.
30
32
  attr_reader :name
31
- # 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.
32
34
  attr_reader :methods
33
- # The signals that are part of the interface. Hash: Symbol => Signal
35
+ # @return [Hash{Symbol => Signal}] The signals that are part of the interface.
34
36
  attr_reader :signals
35
37
 
38
+ # @return [Hash{Symbol => Property}]
39
+ attr_reader :properties
40
+
41
+ # @return [EmitsChangedSignal]
42
+ attr_reader :emits_changed_signal
43
+
36
44
  # Creates a new interface with a given _name_.
37
45
  def initialize(name)
38
46
  validate_name(name)
39
47
  @name = name
40
48
  @methods = {}
41
49
  @signals = {}
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
42
65
  end
43
66
 
44
67
  # Validates a service _name_.
@@ -47,33 +70,57 @@ module DBus
47
70
  raise InvalidIntrospectionData if name =~ /^\./ || name =~ /\.$/
48
71
  raise InvalidIntrospectionData if name =~ /\.\./
49
72
  raise InvalidIntrospectionData if name !~ /\./
73
+
50
74
  name.split(".").each do |element|
51
75
  raise InvalidIntrospectionData if element !~ INTERFACE_ELEMENT_RE
52
76
  end
53
77
  end
54
78
 
55
- # Helper method for defining a method _m_.
56
- def define(m)
57
- if m.is_a?(Method)
58
- @methods[m.name.to_sym] = m
59
- elsif m.is_a?(Signal)
60
- @signals[m.name.to_sym] = m
61
- end
79
+ # Add _ifc_el_ as a known {Method}, {Signal} or {Property}
80
+ # @param ifc_el [InterfaceElement]
81
+ def define(ifc_el)
82
+ name = ifc_el.name.to_sym
83
+ category = case ifc_el
84
+ when Method
85
+ @methods
86
+ when Signal
87
+ @signals
88
+ when Property
89
+ @properties
90
+ end
91
+ category[name] = ifc_el
62
92
  end
93
+ alias declare define
63
94
  alias << define
64
95
 
65
96
  # Defines a method with name _id_ and a given _prototype_ in the
66
97
  # interface.
98
+ # Better name: declare_method
67
99
  def define_method(id, prototype)
68
100
  m = Method.new(id)
69
101
  m.from_prototype(prototype)
70
102
  define(m)
71
103
  end
72
- end # class Interface
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
117
+ end
73
118
 
74
119
  # = A formal parameter has a name and a type
75
120
  class FormalParameter
121
+ # @return [#to_s]
76
122
  attr_reader :name
123
+ # @return [SingleCompleteType]
77
124
  attr_reader :type
78
125
 
79
126
  def initialize(name, type)
@@ -95,14 +142,16 @@ module DBus
95
142
  # This is a generic class for entities that are part of the interface
96
143
  # such as methods and signals.
97
144
  class InterfaceElement
98
- # The name of the interface element. Symbol
145
+ # @return [Symbol] The name of the interface element
99
146
  attr_reader :name
100
- # The parameters of the interface element. Array: FormalParameter
147
+
148
+ # @return [Array<FormalParameter>] The parameters of the interface element
101
149
  attr_reader :params
102
150
 
103
151
  # Validates element _name_.
104
152
  def validate_name(name)
105
153
  return if (name =~ METHOD_SIGNAL_RE) && (name.bytesize <= 255)
154
+
106
155
  raise InvalidMethodName, name
107
156
  end
108
157
 
@@ -123,13 +172,13 @@ module DBus
123
172
  def add_param(name_signature_pair)
124
173
  add_fparam(*name_signature_pair)
125
174
  end
126
- end # class InterfaceElement
175
+ end
127
176
 
128
177
  # = D-Bus interface method class
129
178
  #
130
179
  # This is a class representing methods that are part of an interface.
131
180
  class Method < InterfaceElement
132
- # The list of return values for the method. Array: FormalParameter
181
+ # @return [Array<FormalParameter>] The list of return values for the method
133
182
  attr_reader :rets
134
183
 
135
184
  # Creates a new method interface element with the given _name_.
@@ -139,15 +188,19 @@ module DBus
139
188
  end
140
189
 
141
190
  # Add a return value _name_ and _signature_.
191
+ # @param name [#to_s]
192
+ # @param signature [SingleCompleteType]
142
193
  def add_return(name, signature)
143
194
  @rets << FormalParameter.new(name, signature)
144
195
  end
145
196
 
146
197
  # Add parameter types by parsing the given _prototype_.
198
+ # @param prototype [Prototype]
147
199
  def from_prototype(prototype)
148
200
  prototype.split(/, */).each do |arg|
149
201
  arg = arg.split(" ")
150
202
  raise InvalidClassDefinition if arg.size != 2
203
+
151
204
  dir, arg = arg
152
205
  if arg =~ /:/
153
206
  arg = arg.split(":")
@@ -166,20 +219,21 @@ module DBus
166
219
  end
167
220
 
168
221
  # Return an XML string representation of the method interface elment.
222
+ # @return [String]
169
223
  def to_xml
170
- xml = %(<method name="#{@name}">\n)
224
+ xml = " <method name=\"#{@name}\">\n"
171
225
  @params.each do |param|
172
- name = param.name ? %(name="#{param.name}" ) : ""
173
- xml += %(<arg #{name}direction="in" type="#{param.type}"/>\n)
226
+ name = param.name ? "name=\"#{param.name}\" " : ""
227
+ xml += " <arg #{name}direction=\"in\" type=\"#{param.type}\"/>\n"
174
228
  end
175
229
  @rets.each do |param|
176
- name = param.name ? %(name="#{param.name}" ) : ""
177
- xml += %(<arg #{name}direction="out" type="#{param.type}"/>\n)
230
+ name = param.name ? "name=\"#{param.name}\" " : ""
231
+ xml += " <arg #{name}direction=\"out\" type=\"#{param.type}\"/>\n"
178
232
  end
179
- xml += %(</method>\n)
233
+ xml += " </method>\n"
180
234
  xml
181
235
  end
182
- end # class Method
236
+ end
183
237
 
184
238
  # = D-Bus interface signal class
185
239
  #
@@ -201,13 +255,61 @@ module DBus
201
255
 
202
256
  # Return an XML string representation of the signal interface elment.
203
257
  def to_xml
204
- xml = %(<signal name="#{@name}">\n)
258
+ xml = " <signal name=\"#{@name}\">\n"
205
259
  @params.each do |param|
206
- name = param.name ? %(name="#{param.name}" ) : ""
207
- xml += %(<arg #{name}type="#{param.type}"/>\n)
260
+ name = param.name ? "name=\"#{param.name}\" " : ""
261
+ xml += " <arg #{name}type=\"#{param.type}\"/>\n"
208
262
  end
209
- xml += %(</signal>\n)
263
+ xml += " </signal>\n"
210
264
  xml
211
265
  end
212
- end # class Signal
213
- end # module DBus
266
+ end
267
+
268
+ # An (exported) property
269
+ # https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
270
+ class Property
271
+ # @return [Symbol] The name of the property, for example FooBar.
272
+ attr_reader :name
273
+ # @return [Type]
274
+ attr_reader :type
275
+ # @return [Symbol] :read :write or :readwrite
276
+ attr_reader :access
277
+
278
+ # @return [Symbol,nil] What to call at Ruby side.
279
+ # (Always without the trailing `=`)
280
+ # It is `nil` IFF representing a client-side proxy.
281
+ attr_reader :ruby_name
282
+
283
+ def initialize(name, type, access, ruby_name:)
284
+ @name = name.to_sym
285
+ type = DBus.type(type) unless type.is_a?(Type)
286
+ @type = type
287
+ @access = access
288
+ @ruby_name = ruby_name
289
+ end
290
+
291
+ # @return [Boolean]
292
+ def readable?
293
+ access == :read || access == :readwrite
294
+ end
295
+
296
+ # @return [Boolean]
297
+ def writable?
298
+ access == :write || access == :readwrite
299
+ end
300
+
301
+ # Return introspection XML string representation of the property.
302
+ def to_xml
303
+ " <property type=\"#{@type}\" name=\"#{@name}\" access=\"#{@access}\"/>\n"
304
+ end
305
+
306
+ # @param xml_node [AbstractXML::Node]
307
+ # @return [Property]
308
+ def self.from_xml(xml_node)
309
+ name = xml_node["name"].to_sym
310
+ type = xml_node["type"]
311
+ access = xml_node["access"].to_sym
312
+ new(name, type, access, ruby_name: nil)
313
+ end
314
+ end
315
+ 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
@@ -15,9 +17,10 @@ module DBus
15
17
  # The default one logs to STDERR,
16
18
  # with DEBUG if $DEBUG is set, otherwise INFO.
17
19
  def logger
18
- unless defined? @logger
19
- @logger = Logger.new(STDERR)
20
- @logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO
20
+ if !defined?(@logger) || @logger.nil?
21
+ debug = $DEBUG || ENV["RUBY_DBUS_DEBUG"]
22
+ @logger = Logger.new($stderr)
23
+ @logger.level = debug ? Logger::DEBUG : Logger::INFO
21
24
  end
22
25
  @logger
23
26
  end
data/lib/dbus/main.rb ADDED
@@ -0,0 +1,66 @@
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
+ module DBus
13
+ # = Main event loop class.
14
+ #
15
+ # Class that takes care of handling message and signal events
16
+ # asynchronously. *Note:* This is a native implement and therefore does
17
+ # not integrate with a graphical widget set main loop.
18
+ class Main
19
+ # Create a new main event loop.
20
+ def initialize
21
+ @buses = {}
22
+ @quitting = false
23
+ end
24
+
25
+ # Add a _bus_ to the list of buses to watch for events.
26
+ def <<(bus)
27
+ @buses[bus.message_queue.socket] = bus
28
+ end
29
+
30
+ # Quit a running main loop, to be used eg. from a signal handler
31
+ def quit
32
+ @quitting = true
33
+ end
34
+
35
+ # Run the main loop. This is a blocking call!
36
+ def run
37
+ # before blocking, empty the buffers
38
+ # https://bugzilla.novell.com/show_bug.cgi?id=537401
39
+ @buses.each_value do |b|
40
+ while (m = b.message_queue.message_from_buffer_nonblock)
41
+ b.process(m)
42
+ end
43
+ end
44
+ while !@quitting && !@buses.empty?
45
+ ready = IO.select(@buses.keys, [], [], 5) # timeout 5 seconds
46
+ next unless ready # timeout exceeds so continue unless quitting
47
+
48
+ ready.first.each do |socket|
49
+ b = @buses[socket]
50
+ begin
51
+ b.message_queue.buffer_from_socket_nonblock
52
+ rescue EOFError, SystemCallError => e
53
+ DBus.logger.debug "Got #{e.inspect} from #{socket.inspect}"
54
+ @buses.delete socket # this bus died
55
+ next
56
+ end
57
+ while (m = b.message_queue.message_from_buffer_nonblock)
58
+ b.process(m)
59
+ end
60
+ end
61
+ end
62
+ DBus.logger.debug "Main loop quit" if @quitting
63
+ DBus.logger.debug "Main loop quit, no connections left" if @buses.empty?
64
+ end
65
+ end
66
+ end