ruby-dbus 0.17.0 → 0.18.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +38 -0
  3. data/README.md +1 -1
  4. data/Rakefile +3 -11
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +10 -3
  7. data/examples/doc/_extract_examples +2 -0
  8. data/examples/gdbus/gdbus +11 -5
  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 +1 -0
  13. data/examples/service/complex-property.rb +21 -0
  14. data/examples/service/service_newapi.rb +1 -1
  15. data/examples/simple/call_introspect.rb +1 -0
  16. data/examples/simple/get_id.rb +2 -1
  17. data/examples/simple/properties.rb +2 -0
  18. data/examples/utils/listnames.rb +1 -0
  19. data/examples/utils/notify.rb +1 -0
  20. data/lib/dbus/api_options.rb +9 -0
  21. data/lib/dbus/auth.rb +12 -7
  22. data/lib/dbus/bus.rb +114 -70
  23. data/lib/dbus/bus_name.rb +12 -8
  24. data/lib/dbus/data.rb +744 -0
  25. data/lib/dbus/error.rb +4 -2
  26. data/lib/dbus/introspect.rb +30 -18
  27. data/lib/dbus/logger.rb +3 -1
  28. data/lib/dbus/marshall.rb +229 -293
  29. data/lib/dbus/matchrule.rb +16 -16
  30. data/lib/dbus/message.rb +44 -37
  31. data/lib/dbus/message_queue.rb +10 -5
  32. data/lib/dbus/object.rb +36 -15
  33. data/lib/dbus/object_path.rb +11 -6
  34. data/lib/dbus/proxy_object.rb +18 -4
  35. data/lib/dbus/proxy_object_factory.rb +11 -7
  36. data/lib/dbus/proxy_object_interface.rb +26 -22
  37. data/lib/dbus/raw_message.rb +91 -0
  38. data/lib/dbus/type.rb +164 -80
  39. data/lib/dbus/xml.rb +28 -17
  40. data/lib/dbus.rb +13 -7
  41. data/ruby-dbus.gemspec +4 -2
  42. data/spec/async_spec.rb +2 -0
  43. data/spec/binding_spec.rb +2 -0
  44. data/spec/bus_and_xml_backend_spec.rb +2 -0
  45. data/spec/bus_driver_spec.rb +2 -0
  46. data/spec/bus_name_spec.rb +3 -1
  47. data/spec/bus_spec.rb +2 -0
  48. data/spec/byte_array_spec.rb +2 -0
  49. data/spec/client_robustness_spec.rb +4 -2
  50. data/spec/data/marshall.yaml +1639 -0
  51. data/spec/data_spec.rb +353 -0
  52. data/spec/err_msg_spec.rb +2 -0
  53. data/spec/introspect_xml_parser_spec.rb +2 -0
  54. data/spec/introspection_spec.rb +2 -0
  55. data/spec/main_loop_spec.rb +2 -0
  56. data/spec/node_spec.rb +23 -0
  57. data/spec/object_path_spec.rb +3 -0
  58. data/spec/packet_marshaller_spec.rb +34 -0
  59. data/spec/packet_unmarshaller_spec.rb +262 -0
  60. data/spec/property_spec.rb +60 -2
  61. data/spec/proxy_object_spec.rb +2 -0
  62. data/spec/server_robustness_spec.rb +2 -0
  63. data/spec/server_spec.rb +2 -0
  64. data/spec/service_newapi.rb +37 -4
  65. data/spec/session_bus_spec.rb +3 -1
  66. data/spec/session_bus_spec_manual.rb +2 -0
  67. data/spec/signal_spec.rb +2 -0
  68. data/spec/spec_helper.rb +19 -3
  69. data/spec/thread_safety_spec.rb +2 -0
  70. data/spec/type_spec.rb +69 -6
  71. data/spec/value_spec.rb +16 -1
  72. data/spec/variant_spec.rb +4 -2
  73. data/spec/zzz_quit_spec.rb +16 -0
  74. metadata +16 -7
data/lib/dbus/bus.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
@@ -9,7 +11,6 @@
9
11
  # See the file "COPYING" for the exact licensing terms.
10
12
 
11
13
  require "socket"
12
- require "thread"
13
14
  require "singleton"
14
15
 
15
16
  # = D-Bus main module
@@ -48,6 +49,7 @@ module DBus
48
49
  end
49
50
 
50
51
  # Retrieves an object at the given _path_.
52
+ # @param path [ObjectPath]
51
53
  # @return [ProxyObject]
52
54
  def [](path)
53
55
  object(path, api: ApiOptions::A1)
@@ -55,9 +57,11 @@ module DBus
55
57
 
56
58
  # Retrieves an object at the given _path_
57
59
  # whose methods always return an array.
60
+ # @param path [ObjectPath]
61
+ # @param api [ApiOptions]
58
62
  # @return [ProxyObject]
59
63
  def object(path, api: ApiOptions::A0)
60
- node = get_node(path, _create = true)
64
+ node = get_node(path, create: true)
61
65
  if node.object.nil? || node.object.api != api
62
66
  node.object = ProxyObject.new(
63
67
  @bus, @name, path,
@@ -67,35 +71,43 @@ module DBus
67
71
  node.object
68
72
  end
69
73
 
70
- # Export an object _obj_ (an DBus::Object subclass instance).
74
+ # Export an object
75
+ # @param obj [DBus::Object]
71
76
  def export(obj)
72
77
  obj.service = self
73
- get_node(obj.path, true).object = obj
78
+ get_node(obj.path, create: true).object = obj
74
79
  end
75
80
 
76
81
  # Undo exporting an object _obj_.
77
82
  # Raises ArgumentError if it is not a DBus::Object.
78
83
  # Returns the object, or false if _obj_ was not exported.
84
+ # @param obj [DBus::Object]
79
85
  def unexport(obj)
80
86
  raise ArgumentError, "DBus::Service#unexport() expects a DBus::Object argument" unless obj.is_a?(DBus::Object)
81
87
  return false unless obj.path
88
+
82
89
  last_path_separator_idx = obj.path.rindex("/")
83
90
  parent_path = obj.path[1..last_path_separator_idx - 1]
84
91
  node_name = obj.path[last_path_separator_idx + 1..-1]
85
92
 
86
- parent_node = get_node(parent_path, false)
93
+ parent_node = get_node(parent_path, create: false)
87
94
  return false unless parent_node
95
+
88
96
  obj.service = nil
89
97
  parent_node.delete(node_name).object
90
98
  end
91
99
 
92
- # Get the object node corresponding to the given _path_. if _create_ is
93
- # true, the the nodes in the path are created if they do not already exist.
94
- def get_node(path, create = false)
100
+ # Get the object node corresponding to the given *path*.
101
+ # @param path [ObjectPath]
102
+ # @param create [Boolean] if true, the the {Node}s in the path are created
103
+ # if they do not already exist.
104
+ # @return [Node,nil]
105
+ def get_node(path, create: false)
95
106
  n = @root
96
107
  path.sub(%r{^/}, "").split("/").each do |elem|
97
108
  if !(n[elem])
98
109
  return nil if !create
110
+
99
111
  n[elem] = Node.new(elem)
100
112
  end
101
113
  n = n[elem]
@@ -120,13 +132,14 @@ module DBus
120
132
  subnodes.each do |nodename|
121
133
  subnode = node[nodename] = Node.new(nodename)
122
134
  subpath = if path == "/"
123
- "/" + nodename
135
+ "/#{nodename}"
124
136
  else
125
- path + "/" + nodename
137
+ "#{path}/#{nodename}"
126
138
  end
127
139
  rec_introspect(subnode, subpath)
128
140
  end
129
141
  return if intfs.empty?
142
+
130
143
  node.object = ProxyObjectFactory.new(xml, @bus, @name, path).build
131
144
  end
132
145
  end
@@ -135,13 +148,17 @@ module DBus
135
148
  #
136
149
  # Class representing a node on an object path.
137
150
  class Node < Hash
138
- # The D-Bus object contained by the node.
151
+ # @return [DBus::Object,DBus::ProxyObject,nil]
152
+ # The D-Bus object contained by the node.
139
153
  attr_accessor :object
154
+
140
155
  # The name of the node.
156
+ # @return [String] the last component of its object path, or "/"
141
157
  attr_reader :name
142
158
 
143
159
  # Create a new node with a given _name_.
144
160
  def initialize(name)
161
+ super()
145
162
  @name = name
146
163
  @object = nil
147
164
  end
@@ -157,14 +174,12 @@ module DBus
157
174
  each_pair do |k, _v|
158
175
  xml += " <node name=\"#{k}\" />\n"
159
176
  end
160
- if @object
161
- @object.intfs.each_pair do |_k, v|
162
- xml += " <interface name=\"#{v.name}\">\n"
163
- v.methods.each_value { |m| xml += m.to_xml }
164
- v.signals.each_value { |m| xml += m.to_xml }
165
- v.properties.each_value { |m| xml += m.to_xml }
166
- xml += " </interface>\n"
167
- 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"
168
183
  end
169
184
  xml += "</node>"
170
185
  xml
@@ -182,9 +197,12 @@ module DBus
182
197
  if !@object.nil?
183
198
  s += format("%x ", @object.object_id)
184
199
  end
185
- s + "{" + keys.collect { |k| "#{k} => #{self[k].sub_inspect}" }.join(",") + "}"
200
+ contents_sub_inspect = keys
201
+ .map { |k| "#{k} => #{self[k].sub_inspect}" }
202
+ .join(",")
203
+ "#{s}{#{contents_sub_inspect}}"
186
204
  end
187
- end # class Inspect
205
+ end
188
206
 
189
207
  # FIXME: rename Connection to Bus?
190
208
 
@@ -207,7 +225,15 @@ module DBus
207
225
  def initialize(path)
208
226
  @message_queue = MessageQueue.new(path)
209
227
  @unique_name = nil
228
+
229
+ # @return [Hash{Integer => Proc}]
230
+ # key: message serial
231
+ # value: block to be run when the reply to that message is received
210
232
  @method_call_replies = {}
233
+
234
+ # @return [Hash{Integer => Message}]
235
+ # for debugging only: messages for which a reply was not received yet;
236
+ # key == value.serial
211
237
  @method_call_msgs = {}
212
238
  @signal_matchrules = {}
213
239
  @proxy = nil
@@ -218,7 +244,7 @@ module DBus
218
244
  # but do not block on the queue.
219
245
  # Called by a main loop when something is available in the queue
220
246
  def dispatch_message_queue
221
- while (msg = @message_queue.pop(:non_block)) # FIXME: EOFError
247
+ while (msg = @message_queue.pop(blocking: false)) # FIXME: EOFError
222
248
  process(msg)
223
249
  end
224
250
  end
@@ -325,7 +351,7 @@ module DBus
325
351
  </signal>
326
352
  </interface>
327
353
  </node>
328
- '.freeze
354
+ '
329
355
  # This apostroph is for syntax highlighting editors confused by above xml: "
330
356
 
331
357
  # @api private
@@ -340,6 +366,7 @@ module DBus
340
366
  if reply_handler.nil?
341
367
  send_sync(message) do |rmsg|
342
368
  raise rmsg if rmsg.is_a?(Error)
369
+
343
370
  ret = rmsg.params
344
371
  end
345
372
  else
@@ -444,44 +471,52 @@ module DBus
444
471
  end
445
472
 
446
473
  # @api private
447
- # Send a message _m_ on to the bus. This is done synchronously, thus
474
+ # Send a message _msg_ on to the bus. This is done synchronously, thus
448
475
  # the call will block until a reply message arrives.
449
- def send_sync(m, &retc) # :yields: reply/return message
450
- return if m.nil? # check if somethings wrong
451
- @message_queue.push(m)
452
- @method_call_msgs[m.serial] = m
453
- @method_call_replies[m.serial] = retc
476
+ # @param msg [Message]
477
+ # @param retc [Proc] the reply handler
478
+ # @yieldparam rmsg [MethodReturnMessage] the reply
479
+ # @yieldreturn [Array<Object>] the reply (out) parameters
480
+ def send_sync(msg, &retc) # :yields: reply/return message
481
+ return if msg.nil? # check if somethings wrong
482
+
483
+ @message_queue.push(msg)
484
+ @method_call_msgs[msg.serial] = msg
485
+ @method_call_replies[msg.serial] = retc
454
486
 
455
487
  retm = wait_for_message
456
488
  return if retm.nil? # check if somethings wrong
457
489
 
458
490
  process(retm)
459
- while @method_call_replies.key? m.serial
491
+ while @method_call_replies.key? msg.serial
460
492
  retm = wait_for_message
461
493
  process(retm)
462
494
  end
463
495
  rescue EOFError
464
- new_err = DBus::Error.new("Connection dropped after we sent #{m.inspect}")
496
+ new_err = DBus::Error.new("Connection dropped after we sent #{msg.inspect}")
465
497
  raise new_err
466
498
  end
467
499
 
468
500
  # @api private
469
501
  # Specify a code block that has to be executed when a reply for
470
- # message _m_ is received.
471
- def on_return(m, &retc)
502
+ # message _msg_ is received.
503
+ # @param msg [Message]
504
+ def on_return(msg, &retc)
472
505
  # Have a better exception here
473
- if m.message_type != Message::METHOD_CALL
506
+ if msg.message_type != Message::METHOD_CALL
474
507
  raise "on_return should only get method_calls"
475
508
  end
476
- @method_call_msgs[m.serial] = m
477
- @method_call_replies[m.serial] = retc
509
+
510
+ @method_call_msgs[msg.serial] = msg
511
+ @method_call_replies[msg.serial] = retc
478
512
  end
479
513
 
480
514
  # Asks bus to send us messages matching mr, and execute slot when
481
515
  # received
482
- def add_match(mr, &slot)
516
+ # @param match_rule [MatchRule,#to_s]
517
+ def add_match(match_rule, &slot)
483
518
  # check this is a signal.
484
- mrs = mr.to_s
519
+ mrs = match_rule.to_s
485
520
  DBus.logger.debug "#{@signal_matchrules.size} rules, adding #{mrs.inspect}"
486
521
  # don't ask for the same match if we override it
487
522
  unless @signal_matchrules.key?(mrs)
@@ -491,70 +526,77 @@ module DBus
491
526
  @signal_matchrules[mrs] = slot
492
527
  end
493
528
 
494
- def remove_match(mr)
495
- mrs = mr.to_s
529
+ # @param match_rule [MatchRule,#to_s]
530
+ def remove_match(match_rule)
531
+ mrs = match_rule.to_s
496
532
  rule_existed = @signal_matchrules.delete(mrs).nil?
497
533
  # don't remove nonexisting matches.
498
534
  return if rule_existed
535
+
499
536
  # FIXME: if we do try, the Error.MatchRuleNotFound is *not* raised
500
537
  # and instead is reported as "no return code for nil"
501
538
  proxy.RemoveMatch(mrs)
502
539
  end
503
540
 
504
541
  # @api private
505
- # Process a message _m_ based on its type.
506
- def process(m)
507
- return if m.nil? # check if somethings wrong
508
- case m.message_type
542
+ # Process a message _msg_ based on its type.
543
+ # @param msg [Message]
544
+ def process(msg)
545
+ return if msg.nil? # check if somethings wrong
546
+
547
+ case msg.message_type
509
548
  when Message::ERROR, Message::METHOD_RETURN
510
- raise InvalidPacketException if m.reply_serial.nil?
511
- mcs = @method_call_replies[m.reply_serial]
549
+ raise InvalidPacketException if msg.reply_serial.nil?
550
+
551
+ mcs = @method_call_replies[msg.reply_serial]
512
552
  if !mcs
513
- DBus.logger.debug "no return code for mcs: #{mcs.inspect} m: #{m.inspect}"
553
+ DBus.logger.debug "no return code for mcs: #{mcs.inspect} msg: #{msg.inspect}"
514
554
  else
515
- if m.message_type == Message::ERROR
516
- mcs.call(Error.new(m))
555
+ if msg.message_type == Message::ERROR
556
+ mcs.call(Error.new(msg))
517
557
  else
518
- mcs.call(m)
558
+ mcs.call(msg)
519
559
  end
520
- @method_call_replies.delete(m.reply_serial)
521
- @method_call_msgs.delete(m.reply_serial)
560
+ @method_call_replies.delete(msg.reply_serial)
561
+ @method_call_msgs.delete(msg.reply_serial)
522
562
  end
523
563
  when DBus::Message::METHOD_CALL
524
- if m.path == "/org/freedesktop/DBus"
564
+ if msg.path == "/org/freedesktop/DBus"
525
565
  DBus.logger.debug "Got method call on /org/freedesktop/DBus"
526
566
  end
527
- node = @service.get_node(m.path)
567
+ node = @service.get_node(msg.path, create: false)
528
568
  if !node
529
- reply = Message.error(m, "org.freedesktop.DBus.Error.UnknownObject",
530
- "Object #{m.path} doesn't exist")
569
+ reply = Message.error(msg, "org.freedesktop.DBus.Error.UnknownObject",
570
+ "Object #{msg.path} doesn't exist")
531
571
  @message_queue.push(reply)
532
572
  # handle introspectable as an exception:
533
- elsif m.interface == "org.freedesktop.DBus.Introspectable" &&
534
- m.member == "Introspect"
535
- reply = Message.new(Message::METHOD_RETURN).reply_to(m)
573
+ elsif msg.interface == "org.freedesktop.DBus.Introspectable" &&
574
+ msg.member == "Introspect"
575
+ reply = Message.new(Message::METHOD_RETURN).reply_to(msg)
536
576
  reply.sender = @unique_name
537
- xml = node.to_xml(m.path)
577
+ xml = node.to_xml(msg.path)
538
578
  reply.add_param(Type::STRING, xml)
539
579
  @message_queue.push(reply)
540
580
  else
541
581
  obj = node.object
542
582
  return if obj.nil? # FIXME, pushes no reply
543
- obj.dispatch(m) if obj
583
+
584
+ obj&.dispatch(msg)
544
585
  end
545
586
  when DBus::Message::SIGNAL
546
587
  # the signal can match multiple different rules
547
588
  # clone to allow new signale handlers to be registered
548
589
  @signal_matchrules.dup.each do |mrs, slot|
549
- if DBus::MatchRule.new.from_s(mrs).match(m)
550
- slot.call(m)
590
+ if DBus::MatchRule.new.from_s(mrs).match(msg)
591
+ slot.call(msg)
551
592
  end
552
593
  end
553
594
  else
554
- DBus.logger.debug "Unknown message type: #{m.message_type}"
595
+ # spec(Message Format): Unknown types must be ignored.
596
+ DBus.logger.debug "Unknown message type: #{msg.message_type}"
555
597
  end
556
- rescue Exception => ex
557
- raise m.annotate_exception(ex)
598
+ rescue Exception => e
599
+ raise msg.annotate_exception(e)
558
600
  end
559
601
 
560
602
  # Retrieves the Service with the given _name_.
@@ -604,7 +646,7 @@ module DBus
604
646
  end
605
647
  @service = Service.new(@unique_name, self)
606
648
  end
607
- end # class Connection
649
+ end
608
650
 
609
651
  # = D-Bus session bus class
610
652
  #
@@ -630,6 +672,7 @@ module DBus
630
672
  # traditional dbus uses /var/lib/dbus/machine-id
631
673
  machine_id_path = Dir["{/etc,/var/lib/dbus,/var/db/dbus}/machine-id"].first
632
674
  return nil unless machine_id_path
675
+
633
676
  machine_id = File.read(machine_id_path).chomp
634
677
 
635
678
  display = ENV["DISPLAY"][/:(\d+)\.?/, 1]
@@ -661,7 +704,7 @@ module DBus
661
704
  class ASystemBus < Connection
662
705
  # Get the default system bus.
663
706
  def initialize
664
- super(SystemSocketName)
707
+ super(SYSTEM_BUS_ADDRESS)
665
708
  send_hello
666
709
  end
667
710
  end
@@ -736,6 +779,7 @@ module DBus
736
779
  while !@quitting && !@buses.empty?
737
780
  ready = IO.select(@buses.keys, [], [], 5) # timeout 5 seconds
738
781
  next unless ready # timeout exceeds so continue unless quitting
782
+
739
783
  ready.first.each do |socket|
740
784
  b = @buses[socket]
741
785
  begin
@@ -750,5 +794,5 @@ module DBus
750
794
  end
751
795
  end
752
796
  end
753
- end # class Main
754
- end # module DBus
797
+ end
798
+ end
data/lib/dbus/bus_name.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is part of the ruby-dbus project
2
4
  # Copyright (C) 2019 Martin Vidner
3
5
  #
@@ -7,21 +9,23 @@
7
9
  # See the file "COPYING" for the exact licensing terms.
8
10
 
9
11
  module DBus
10
- # A {::String} that validates at initialization time
12
+ # D-Bus: a name for a connection, like ":1.3" or "org.example.ManagerManager".
13
+ # Implemented as a {::String} that validates at initialization time.
11
14
  # @see https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-bus
12
15
  class BusName < String
13
16
  # @raise Error if not a valid bus name
14
- def initialize(s)
15
- unless self.class.valid?(s)
16
- raise DBus::Error, "Invalid bus name #{s.inspect}"
17
+ def initialize(name)
18
+ unless self.class.valid?(name)
19
+ raise DBus::Error, "Invalid bus name #{name.inspect}"
17
20
  end
21
+
18
22
  super
19
23
  end
20
24
 
21
- def self.valid?(s)
22
- s.size <= 255 &&
23
- (s =~ /\A:[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)+\z/ ||
24
- s =~ /\A[A-Za-z_-][A-Za-z0-9_-]*(\.[A-Za-z_-][A-Za-z0-9_-]*)+\z/)
25
+ def self.valid?(name)
26
+ name.size <= 255 &&
27
+ (name =~ /\A:[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)+\z/ ||
28
+ name =~ /\A[A-Za-z_-][A-Za-z0-9_-]*(\.[A-Za-z_-][A-Za-z0-9_-]*)+\z/)
25
29
  end
26
30
  end
27
31
  end