ruby-dbus-openplacos 0.6.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 (51) hide show
  1. data/COPYING +504 -0
  2. data/NEWS +146 -0
  3. data/README +42 -0
  4. data/Rakefile +54 -0
  5. data/VERSION +1 -0
  6. data/doc/tutorial/index.markdown +480 -0
  7. data/examples/gdbus/gdbus +255 -0
  8. data/examples/gdbus/gdbus.glade +184 -0
  9. data/examples/gdbus/launch.sh +4 -0
  10. data/examples/no-introspect/nm-test.rb +21 -0
  11. data/examples/no-introspect/tracker-test.rb +16 -0
  12. data/examples/rhythmbox/playpause.rb +25 -0
  13. data/examples/service/call_service.rb +25 -0
  14. data/examples/service/service_newapi.rb +51 -0
  15. data/examples/simple/call_introspect.rb +34 -0
  16. data/examples/simple/properties.rb +19 -0
  17. data/examples/utils/listnames.rb +11 -0
  18. data/examples/utils/notify.rb +19 -0
  19. data/lib/dbus/auth.rb +258 -0
  20. data/lib/dbus/bus.rb +947 -0
  21. data/lib/dbus/core_ext/class/attribute.rb +91 -0
  22. data/lib/dbus/core_ext/kernel/singleton_class.rb +14 -0
  23. data/lib/dbus/core_ext/module/remove_method.rb +12 -0
  24. data/lib/dbus/error.rb +44 -0
  25. data/lib/dbus/export.rb +124 -0
  26. data/lib/dbus/introspect.rb +570 -0
  27. data/lib/dbus/marshall.rb +443 -0
  28. data/lib/dbus/matchrule.rb +100 -0
  29. data/lib/dbus/message.rb +310 -0
  30. data/lib/dbus/type.rb +222 -0
  31. data/lib/dbus.rb +83 -0
  32. data/ruby-dbus-openplacos.gemspec +17 -0
  33. data/test/binding_test.rb +56 -0
  34. data/test/bus_driver_test.rb +22 -0
  35. data/test/dbus-launch-simple +35 -0
  36. data/test/dbus-limited-session.conf +28 -0
  37. data/test/property_test.rb +55 -0
  38. data/test/server_robustness_test.rb +72 -0
  39. data/test/server_test.rb +53 -0
  40. data/test/service_newapi.rb +197 -0
  41. data/test/session_bus_test_manual.rb +20 -0
  42. data/test/signal_test.rb +64 -0
  43. data/test/t1 +4 -0
  44. data/test/t2.rb +66 -0
  45. data/test/t3-ticket27.rb +18 -0
  46. data/test/t5-report-dbus-interface.rb +58 -0
  47. data/test/t6-loop.rb +82 -0
  48. data/test/test_env +13 -0
  49. data/test/test_server +39 -0
  50. data/test/variant_test.rb +66 -0
  51. metadata +118 -0
data/lib/dbus/bus.rb ADDED
@@ -0,0 +1,947 @@
1
+ # dbus.rb - Module containing the low-level D-Bus implementation
2
+ #
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
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
+ require 'socket'
12
+ require 'thread'
13
+ require 'singleton'
14
+ require 'fcntl'
15
+
16
+ # = D-Bus main module
17
+ #
18
+ # Module containing all the D-Bus modules and classes.
19
+ module DBus
20
+
21
+ # This represents a remote service. It should not be instantiated directly
22
+ # Use Bus::service()
23
+ class Service
24
+ # The service name.
25
+ attr_reader :name
26
+ # The bus the service is running on.
27
+ attr_reader :bus
28
+ # The service root (FIXME).
29
+ attr_reader :root
30
+
31
+ # Create a new service with a given _name_ on a given _bus_.
32
+ def initialize(name, bus)
33
+ @name, @bus = name, bus
34
+ @root = Node.new("/")
35
+ end
36
+
37
+ # Determine whether the service name already exists.
38
+ def exists?
39
+ bus.proxy.ListNames[0].member?(@name)
40
+ end
41
+
42
+ # Perform an introspection on all the objects on the service
43
+ # (starting recursively from the root).
44
+ def introspect
45
+ if block_given?
46
+ raise NotImplementedError
47
+ else
48
+ rec_introspect(@root, "/")
49
+ end
50
+ self
51
+ end
52
+
53
+ # Retrieves an object (ProxyObject) at the given _path_.
54
+ def object(path)
55
+ node = get_node(path, true)
56
+ if node.object.nil?
57
+ node.object = ProxyObject.new(@bus, @name, path)
58
+ end
59
+ node.object
60
+ end
61
+
62
+ # Export an object _obj_ (an DBus::Object subclass instance).
63
+ def export(obj)
64
+ obj.service = self
65
+ get_node(obj.path, true).object = obj
66
+ end
67
+
68
+ # Undo exporting an object _obj_.
69
+ # Raises ArgumentError if it is not a DBus::Object.
70
+ # Returns the object, or false if _obj_ was not exported.
71
+ def unexport(obj)
72
+ raise ArgumentError.new("DBus::Service#unexport() expects a DBus::Object argument") unless obj.kind_of?(DBus::Object)
73
+ return false unless obj.path
74
+ pathSep = obj.path.rindex("/") #last path seperator
75
+ parent_path = obj.path[1..pathSep-1]
76
+ node_name = obj.path[pathSep+1..-1]
77
+
78
+ parent_node = get_node(parent_path, false)
79
+ return false unless parent_node
80
+ obj.service = nil
81
+ parent_node.delete(node_name)
82
+ end
83
+
84
+ # Get the object node corresponding to the given _path_. if _create_ is
85
+ # true, the the nodes in the path are created if they do not already exist.
86
+ def get_node(path, create = false)
87
+ n = @root
88
+ path.sub(/^\//, "").split("/").each do |elem|
89
+ if not n[elem]
90
+ if not create
91
+ return nil
92
+ else
93
+ n[elem] = Node.new(elem)
94
+ end
95
+ end
96
+ n = n[elem]
97
+ end
98
+ if n.nil?
99
+ puts "Warning, unknown object #{path}" if $DEBUG
100
+ end
101
+ n
102
+ end
103
+
104
+ #########
105
+ private
106
+ #########
107
+
108
+ # Perform a recursive retrospection on the given current _node_
109
+ # on the given _path_.
110
+ def rec_introspect(node, path)
111
+ xml = bus.introspect_data(@name, path)
112
+ intfs, subnodes = IntrospectXMLParser.new(xml).parse
113
+ subnodes.each do |nodename|
114
+ subnode = node[nodename] = Node.new(nodename)
115
+ if path == "/"
116
+ subpath = "/" + nodename
117
+ else
118
+ subpath = path + "/" + nodename
119
+ end
120
+ rec_introspect(subnode, subpath)
121
+ end
122
+ if intfs.size > 0
123
+ node.object = ProxyObjectFactory.new(xml, @bus, @name, path).build
124
+ end
125
+ end
126
+ end
127
+
128
+ # = Object path node class
129
+ #
130
+ # Class representing a node on an object path.
131
+ class Node < Hash
132
+ # The D-Bus object contained by the node.
133
+ attr_accessor :object
134
+ # The name of the node.
135
+ attr_reader :name
136
+
137
+ # Create a new node with a given _name_.
138
+ def initialize(name)
139
+ @name = name
140
+ @object = nil
141
+ end
142
+
143
+ # Return an XML string representation of the node.
144
+ def to_xml
145
+ xml = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
146
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
147
+ <node>
148
+ '
149
+ self.each_pair do |k, v|
150
+ xml += "<node name=\"#{k}\" />"
151
+ end
152
+ if @object
153
+ @object.intfs.each_pair do |k, v|
154
+ xml += %{<interface name="#{v.name}">\n}
155
+ v.methods.each_value { |m| xml += m.to_xml }
156
+ v.signals.each_value { |m| xml += m.to_xml }
157
+ xml +="</interface>\n"
158
+ end
159
+ end
160
+ xml += '</node>'
161
+ xml
162
+ end
163
+
164
+ # Return inspect information of the node.
165
+ def inspect
166
+ # Need something here
167
+ "<DBus::Node #{sub_inspect}>"
168
+ end
169
+
170
+ # Return instance inspect information, used by Node#inspect.
171
+ def sub_inspect
172
+ s = ""
173
+ if not @object.nil?
174
+ s += "%x " % @object.object_id
175
+ end
176
+ s + "{" + keys.collect { |k| "#{k} => #{self[k].sub_inspect}" }.join(",") + "}"
177
+ end
178
+ end # class Inspect
179
+
180
+ # FIXME: rename Connection to Bus?
181
+
182
+ # D-Bus main connection class
183
+ #
184
+ # Main class that maintains a connection to a bus and can handle incoming
185
+ # and outgoing messages.
186
+ class Connection
187
+ # The unique name (by specification) of the message.
188
+ attr_reader :unique_name
189
+ # The socket that is used to connect with the bus.
190
+ attr_reader :socket
191
+ attr_accessor :main_message_queue, :main_thread, :queue_used_by_thread, :thread_waiting_for_message, :rescuemethod
192
+
193
+ # Create a new connection to the bus for a given connect _path_. _path_
194
+ # format is described in the D-Bus specification:
195
+ # http://dbus.freedesktop.org/doc/dbus-specification.html#addresses
196
+ # and is something like:
197
+ # "transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2"
198
+ # e.g. "unix:path=/tmp/dbus-test" or "tcp:host=localhost,port=2687"
199
+ def initialize(path, threaded_access)
200
+ @path = path
201
+ @unique_name = nil
202
+ @buffer = ""
203
+ @rescuemethod = nil
204
+ @method_call_replies = Hash.new
205
+ @method_call_msgs = Hash.new
206
+ @signal_matchrules = Hash.new
207
+ @proxy = nil
208
+ @object_root = Node.new("/")
209
+ @is_tcp = false
210
+ @queue_used_by_thread = Hash.new
211
+ @thread_waiting_for_message = Hash.new
212
+ @main_message_queue = Queue.new
213
+ @main_thread = nil
214
+ @threaded = threaded_access
215
+ end
216
+
217
+ def start_read_thread
218
+ @thread = Thread.new{
219
+ puts "start the reading thread on socket #{@socket}" if $DEBUG
220
+ loop do #loop to read
221
+
222
+ if @socket.nil?
223
+ puts "ERROR: Can't wait for messages, @socket is nil."
224
+ return
225
+ end
226
+ ret = pop_message
227
+ while ret == nil
228
+ r, d, d = IO.select([@socket])
229
+ if r and r[0] == @socket
230
+ update_buffer
231
+ ret = pop_message
232
+ end
233
+ end
234
+ case ret.message_type
235
+ when Message::ERROR, Message::METHOD_RETURN
236
+ if ( @thread_waiting_for_message[ret.reply_serial].nil?)
237
+ process(ret) # there is no thread, process the message
238
+ else
239
+ thread_in_wait = @thread_waiting_for_message[ret.reply_serial]
240
+ @queue_used_by_thread[thread_in_wait] << ret # puts the message in the queue
241
+ end
242
+ else
243
+ if main_thread
244
+ @main_message_queue << ret
245
+ else
246
+ process(ret) # there is no main.run thread, process the message
247
+ end
248
+ end
249
+ end
250
+ }
251
+ end
252
+
253
+ # Connect to the bus and initialize the connection.
254
+ def connect
255
+ addresses = @path.split ";"
256
+ # connect to first one that succeeds
257
+ worked = addresses.find do |a|
258
+ transport, keyvaluestring = a.split ":"
259
+ kv_list = keyvaluestring.split ","
260
+ kv_hash = Hash.new
261
+ kv_list.each do |kv|
262
+ key, escaped_value = kv.split "="
263
+ value = escaped_value.gsub(/%(..)/) {|m| [$1].pack "H2" }
264
+ kv_hash[key] = value
265
+ end
266
+ case transport
267
+ when "unix"
268
+ connect_to_unix kv_hash
269
+ when "tcp"
270
+ connect_to_tcp kv_hash
271
+ else
272
+ # ignore, report?
273
+ end
274
+ end
275
+ if (@threaded)
276
+ start_read_thread
277
+ end
278
+ worked
279
+ # returns the address that worked or nil.
280
+ # how to report failure?
281
+ end
282
+
283
+ # Connect to a bus over tcp and initialize the connection.
284
+ def connect_to_tcp(params)
285
+ #check if the path is sufficient
286
+ if params.key?("host") and params.key?("port")
287
+ begin
288
+ #initialize the tcp socket
289
+ @socket = TCPSocket.new(params["host"],params["port"].to_i)
290
+ @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
291
+ init_connection
292
+ @is_tcp = true
293
+ rescue
294
+ puts "Error: Could not establish connection to: #{@path}, will now exit."
295
+ exit(0) #a little harsh
296
+ end
297
+ else
298
+ #Danger, Will Robinson: the specified "path" is not usable
299
+ puts "Error: supplied path: #{@path}, unusable! sorry."
300
+ end
301
+ end
302
+
303
+ # Connect to an abstract unix bus and initialize the connection.
304
+ def connect_to_unix(params)
305
+ @socket = Socket.new(Socket::Constants::PF_UNIX,Socket::Constants::SOCK_STREAM, 0)
306
+ @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
307
+ if ! params['abstract'].nil?
308
+ if HOST_END == LIL_END
309
+ sockaddr = "\1\0\0#{params['abstract']}"
310
+ else
311
+ sockaddr = "\0\1\0#{params['abstract']}"
312
+ end
313
+ elsif ! params['path'].nil?
314
+ sockaddr = Socket.pack_sockaddr_un(params['path'])
315
+ end
316
+ @socket.connect(sockaddr)
317
+ init_connection
318
+ end
319
+
320
+ # Send the buffer _buf_ to the bus using Connection#writel.
321
+ def send(buf)
322
+ @socket.write(buf) unless @socket.nil?
323
+ end
324
+
325
+ # Tell a bus to register itself on the glib main loop
326
+ def glibize
327
+ require 'glib2'
328
+ # Circumvent a ruby-glib bug
329
+ @channels ||= Array.new
330
+
331
+ gio = GLib::IOChannel.new(@socket.fileno)
332
+ @channels << gio
333
+ gio.add_watch(GLib::IOChannel::IN) do |c, ch|
334
+ update_buffer
335
+ messages.each do |msg|
336
+ process(msg)
337
+ end
338
+ true
339
+ end
340
+ end
341
+
342
+ # FIXME: describe the following names, flags and constants.
343
+ # See DBus spec for definition
344
+ NAME_FLAG_ALLOW_REPLACEMENT = 0x1
345
+ NAME_FLAG_REPLACE_EXISTING = 0x2
346
+ NAME_FLAG_DO_NOT_QUEUE = 0x4
347
+
348
+ REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1
349
+ REQUEST_NAME_REPLY_IN_QUEUE = 0x2
350
+ REQUEST_NAME_REPLY_EXISTS = 0x3
351
+ REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4
352
+
353
+ DBUSXMLINTRO = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
354
+ "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
355
+ <node>
356
+ <interface name="org.freedesktop.DBus.Introspectable">
357
+ <method name="Introspect">
358
+ <arg name="data" direction="out" type="s"/>
359
+ </method>
360
+ </interface>
361
+ <interface name="org.freedesktop.DBus">
362
+ <method name="RequestName">
363
+ <arg direction="in" type="s"/>
364
+ <arg direction="in" type="u"/>
365
+ <arg direction="out" type="u"/>
366
+ </method>
367
+ <method name="ReleaseName">
368
+ <arg direction="in" type="s"/>
369
+ <arg direction="out" type="u"/>
370
+ </method>
371
+ <method name="StartServiceByName">
372
+ <arg direction="in" type="s"/>
373
+ <arg direction="in" type="u"/>
374
+ <arg direction="out" type="u"/>
375
+ </method>
376
+ <method name="Hello">
377
+ <arg direction="out" type="s"/>
378
+ </method>
379
+ <method name="NameHasOwner">
380
+ <arg direction="in" type="s"/>
381
+ <arg direction="out" type="b"/>
382
+ </method>
383
+ <method name="ListNames">
384
+ <arg direction="out" type="as"/>
385
+ </method>
386
+ <method name="ListActivatableNames">
387
+ <arg direction="out" type="as"/>
388
+ </method>
389
+ <method name="AddMatch">
390
+ <arg direction="in" type="s"/>
391
+ </method>
392
+ <method name="RemoveMatch">
393
+ <arg direction="in" type="s"/>
394
+ </method>
395
+ <method name="GetNameOwner">
396
+ <arg direction="in" type="s"/>
397
+ <arg direction="out" type="s"/>
398
+ </method>
399
+ <method name="ListQueuedOwners">
400
+ <arg direction="in" type="s"/>
401
+ <arg direction="out" type="as"/>
402
+ </method>
403
+ <method name="GetConnectionUnixUser">
404
+ <arg direction="in" type="s"/>
405
+ <arg direction="out" type="u"/>
406
+ </method>
407
+ <method name="GetConnectionUnixProcessID">
408
+ <arg direction="in" type="s"/>
409
+ <arg direction="out" type="u"/>
410
+ </method>
411
+ <method name="GetConnectionSELinuxSecurityContext">
412
+ <arg direction="in" type="s"/>
413
+ <arg direction="out" type="ay"/>
414
+ </method>
415
+ <method name="ReloadConfig">
416
+ </method>
417
+ <signal name="NameOwnerChanged">
418
+ <arg type="s"/>
419
+ <arg type="s"/>
420
+ <arg type="s"/>
421
+ </signal>
422
+ <signal name="NameLost">
423
+ <arg type="s"/>
424
+ </signal>
425
+ <signal name="NameAcquired">
426
+ <arg type="s"/>
427
+ </signal>
428
+ </interface>
429
+ </node>
430
+ '
431
+ # This apostroph is for syntax highlighting editors confused by above xml: "
432
+
433
+ def introspect_data(dest, path)
434
+ m = DBus::Message.new(DBus::Message::METHOD_CALL)
435
+ m.path = path
436
+ m.interface = "org.freedesktop.DBus.Introspectable"
437
+ m.destination = dest
438
+ m.member = "Introspect"
439
+ m.sender = unique_name
440
+ if not block_given?
441
+ # introspect in synchronous !
442
+ send_sync(m) do |rmsg|
443
+ if rmsg.is_a?(Error)
444
+ raise rmsg
445
+ else
446
+ return rmsg.params[0] # return value of introspect_data
447
+ end
448
+ end
449
+ else
450
+ send(m.marshall)
451
+ on_return(m) do |rmsg|
452
+ if rmsg.is_a?(Error)
453
+ yield rmsg
454
+ else
455
+ yield rmsg.params[0]
456
+ end
457
+ end
458
+ end
459
+ nil
460
+ end
461
+
462
+ # Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method
463
+ # _dest_ is the service and _path_ the object path you want to introspect
464
+ # If a code block is given, the introspect call in asynchronous. If not
465
+ # data is returned
466
+ #
467
+ # FIXME: link to ProxyObject data definition
468
+ # The returned object is a ProxyObject that has methods you can call to
469
+ # issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN
470
+ def introspect(dest, path)
471
+ if not block_given?
472
+ # introspect in synchronous !
473
+ data = introspect_data(dest, path)
474
+ pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
475
+ return pof.build
476
+ else
477
+ introspect_data(dest, path) do |async_data|
478
+ yield(DBus::ProxyObjectFactory.new(async_data, self, dest, path).build)
479
+ end
480
+ end
481
+ end
482
+
483
+ # Exception raised when a service name is requested that is not available.
484
+ class NameRequestError < Exception
485
+ end
486
+
487
+ # Attempt to request a service _name_.
488
+ #
489
+ # FIXME, NameRequestError cannot really be rescued as it will be raised
490
+ # when dispatching a later call. Rework the API to better match the spec.
491
+ def request_service(name)
492
+ # Use RequestName, but asynchronously!
493
+ # A synchronous call would not work with service activation, where
494
+ # method calls to be serviced arrive before the reply for RequestName
495
+ # (Ticket#29).
496
+ proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING) do |rmsg, r|
497
+ if rmsg.is_a?(Error) # check and report errors first
498
+ raise rmsg
499
+ elsif r != REQUEST_NAME_REPLY_PRIMARY_OWNER
500
+ raise NameRequestError
501
+ end
502
+ end
503
+ @service = Service.new(name, self)
504
+ @service
505
+ end
506
+
507
+ # Set up a ProxyObject for the bus itself, since the bus is introspectable.
508
+ # Returns the object.
509
+ def proxy
510
+ if @proxy == nil
511
+ path = "/org/freedesktop/DBus"
512
+ dest = "org.freedesktop.DBus"
513
+ pof = DBus::ProxyObjectFactory.new(DBUSXMLINTRO, self, dest, path)
514
+ @proxy = pof.build["org.freedesktop.DBus"]
515
+ end
516
+ @proxy
517
+ end
518
+
519
+ # Fill (append) the buffer from data that might be available on the
520
+ # socket.
521
+ def update_buffer
522
+ @buffer += @socket.read_nonblock(MSG_BUF_SIZE)
523
+ rescue EOFError
524
+ if (@threaded)
525
+ @rescuemethod.call
526
+ end
527
+ raise # the caller expects it
528
+ rescue Exception => e
529
+ puts "Oops:", e
530
+ raise if @is_tcp # why?
531
+ puts "WARNING: read_nonblock failed, falling back to .recv"
532
+ @buffer += @socket.recv(MSG_BUF_SIZE)
533
+ end
534
+
535
+ # Get one message from the bus and remove it from the buffer.
536
+ # Return the message.
537
+ def pop_message
538
+ return nil if @buffer.empty?
539
+ ret = nil
540
+ begin
541
+ ret, size = Message.new.unmarshall_buffer(@buffer)
542
+ @buffer.slice!(0, size)
543
+ rescue IncompleteBufferException => e
544
+ # fall through, let ret be null
545
+ end
546
+ ret
547
+ end
548
+
549
+ # Retrieve all the messages that are currently in the buffer.
550
+ def messages
551
+ ret = Array.new
552
+ while msg = pop_message
553
+ ret << msg
554
+ end
555
+ ret
556
+ end
557
+
558
+ # The buffer size for messages.
559
+ MSG_BUF_SIZE = 4096
560
+
561
+ # Update the buffer and retrieve all messages using Connection#messages.
562
+ # Return the messages.
563
+ def poll_messages
564
+ ret = nil
565
+ r, d, d = IO.select([@socket], nil, nil, 0)
566
+ if r and r.size > 0
567
+ update_buffer
568
+ end
569
+ messages
570
+ end
571
+
572
+ # Wait for a message to arrive. Return it once it is available.
573
+ def wait_for_message
574
+ if(@threaded)
575
+ return @queue_used_by_thread[Thread.current].pop
576
+ else
577
+ if @socket.nil?
578
+ puts "ERROR: Can't wait for messages, @socket is nil."
579
+ return
580
+ end
581
+ ret = pop_message
582
+ while ret == nil
583
+ r, d, d = IO.select([@socket])
584
+ if r and r[0] == @socket
585
+ update_buffer
586
+ ret = pop_message
587
+ end
588
+ end
589
+ ret
590
+ end
591
+ end
592
+
593
+ # Send a message _m_ on to the bus. This is done synchronously, thus
594
+ # the call will block until a reply message arrives.
595
+ def send_sync(m, &retc) # :yields: reply/return message
596
+ return if m.nil? #check if somethings wrong
597
+ if (@threaded)
598
+ @queue_used_by_thread[Thread.current] = Queue.new # Creating Queue message for return
599
+ @thread_waiting_for_message[m.serial] = Thread.current
600
+ end
601
+ send(m.marshall)
602
+ @method_call_msgs[m.serial] = m
603
+ @method_call_replies[m.serial] = retc
604
+
605
+ retm = wait_for_message
606
+ return if retm.nil? #check if somethings wrong
607
+
608
+ process(retm)
609
+ if (@threaded)
610
+ @queue_used_by_thread.delete(Thread.current)
611
+ else
612
+ until [DBus::Message::ERROR,
613
+ DBus::Message::METHOD_RETURN].include?(retm.message_type) and
614
+ retm.reply_serial == m.serial
615
+ retm = wait_for_message
616
+ process(retm)
617
+ end
618
+ end
619
+ end
620
+
621
+ # Specify a code block that has to be executed when a reply for
622
+ # message _m_ is received.
623
+ def on_return(m, &retc)
624
+ # Have a better exception here
625
+ if m.message_type != Message::METHOD_CALL
626
+ raise "on_return should only get method_calls"
627
+ end
628
+ @method_call_msgs[m.serial] = m
629
+ @method_call_replies[m.serial] = retc
630
+ end
631
+
632
+ # Asks bus to send us messages matching mr, and execute slot when
633
+ # received
634
+ def add_match(mr, &slot)
635
+ # check this is a signal.
636
+ mrs = mr.to_s
637
+ puts "#{@signal_matchrules.size} rules, adding #{mrs.inspect}" if $DEBUG
638
+ # don't ask for the same match if we override it
639
+ unless @signal_matchrules.key?(mrs)
640
+ puts "Asked for a new match" if $DEBUG
641
+ proxy.AddMatch(mrs)
642
+ end
643
+ @signal_matchrules[mrs] = slot
644
+ end
645
+
646
+ def remove_match(mr)
647
+ mrs = mr.to_s
648
+ unless @signal_matchrules.delete(mrs).nil?
649
+ # don't remove nonexisting matches.
650
+ # FIXME if we do try, the Error.MatchRuleNotFound is *not* raised
651
+ # and instead is reported as "no return code for nil"
652
+ proxy.RemoveMatch(mrs)
653
+ end
654
+ end
655
+
656
+ # Process a message _m_ based on its type.
657
+ def process(m)
658
+ return if m.nil? #check if somethings wrong
659
+ case m.message_type
660
+ when Message::ERROR, Message::METHOD_RETURN
661
+ raise InvalidPacketException if m.reply_serial == nil
662
+ mcs = @method_call_replies[m.reply_serial]
663
+ if not mcs
664
+ puts "DEBUG: no return code for mcs: #{mcs.inspect} m: #{m.inspect}" if $DEBUG
665
+ else
666
+ if m.message_type == Message::ERROR
667
+ mcs.call(Error.new(m))
668
+ else
669
+ mcs.call(m)
670
+ end
671
+ @method_call_replies.delete(m.reply_serial)
672
+ @method_call_msgs.delete(m.reply_serial)
673
+ end
674
+ when DBus::Message::METHOD_CALL
675
+ if m.path == "/org/freedesktop/DBus"
676
+ puts "DEBUG: Got method call on /org/freedesktop/DBus" if $DEBUG
677
+ end
678
+ node = @service.get_node(m.path)
679
+ if not node
680
+ reply = Message.error(m, "org.freedesktop.DBus.Error.UnknownObject",
681
+ "Object #{m.path} doesn't exist")
682
+ send(reply.marshall)
683
+ # handle introspectable as an exception:
684
+ elsif m.interface == "org.freedesktop.DBus.Introspectable" and
685
+ m.member == "Introspect"
686
+ reply = Message.new(Message::METHOD_RETURN).reply_to(m)
687
+ reply.sender = @unique_name
688
+ reply.add_param(Type::STRING, node.to_xml)
689
+ send(reply.marshall)
690
+ else
691
+ obj = node.object
692
+ return if obj.nil? # FIXME, sends no reply
693
+ obj.dispatch(m) if obj
694
+ end
695
+ when DBus::Message::SIGNAL
696
+ # the signal can match multiple different rules
697
+ @signal_matchrules.each do |mrs, slot|
698
+ if DBus::MatchRule.new.from_s(mrs).match(m)
699
+ slot.call(m)
700
+ end
701
+ end
702
+ else
703
+ puts "DEBUG: Unknown message type: #{m.message_type}" if $DEBUG
704
+ end
705
+ end
706
+
707
+ # Retrieves the Service with the given _name_.
708
+ def service(name)
709
+ # The service might not exist at this time so we cannot really check
710
+ # anything
711
+ Service.new(name, self)
712
+ end
713
+ alias :[] :service
714
+
715
+ # Emit a signal event for the given _service_, object _obj_, interface
716
+ # _intf_ and signal _sig_ with arguments _args_.
717
+ def emit(service, obj, intf, sig, *args)
718
+ m = Message.new(DBus::Message::SIGNAL)
719
+ m.path = obj.path
720
+ m.interface = intf.name
721
+ m.member = sig.name
722
+ m.sender = service.name
723
+ i = 0
724
+ sig.params.each do |par|
725
+ m.add_param(par.type, args[i])
726
+ i += 1
727
+ end
728
+ send(m.marshall)
729
+ end
730
+
731
+ ###########################################################################
732
+ private
733
+
734
+ # Send a hello messages to the bus to let it know we are here.
735
+ def send_hello
736
+ m = Message.new(DBus::Message::METHOD_CALL)
737
+ m.path = "/org/freedesktop/DBus"
738
+ m.destination = "org.freedesktop.DBus"
739
+ m.interface = "org.freedesktop.DBus"
740
+ m.member = "Hello"
741
+ send_sync(m) do |rmsg|
742
+ @unique_name = rmsg.destination
743
+ puts "Got hello reply. Our unique_name is #{@unique_name}" if $DEBUG
744
+ end
745
+ @service = Service.new(@unique_name, self)
746
+ end
747
+
748
+ # Initialize the connection to the bus.
749
+ def init_connection
750
+ @client = Client.new(@socket)
751
+ @client.authenticate
752
+ end
753
+ end # class Connection
754
+
755
+
756
+ # = D-Bus session bus class
757
+ #
758
+ # The session bus is a session specific bus (mostly for desktop use).
759
+ #
760
+ # Use SessionBus, the non-singleton ASessionBus is
761
+ # for the test suite.
762
+ class ASessionBus < Connection
763
+ # Get the the default session bus.
764
+ def initialize
765
+ super(ENV["DBUS_SESSION_BUS_ADDRESS"] || address_from_file, ENV["DBUS_THREADED_ACCESS"] || false)
766
+ connect
767
+ send_hello
768
+ end
769
+
770
+ def address_from_file
771
+ f = File.new("/var/lib/dbus/machine-id")
772
+ machine_id = f.readline.chomp
773
+ f.close
774
+ display = ENV["DISPLAY"].gsub(/.*:([0-9]*).*/, '\1')
775
+ File.open(ENV["HOME"] + "/.dbus/session-bus/#{machine_id}-#{display}").each do |line|
776
+ if line =~ /^DBUS_SESSION_BUS_ADDRESS=(.*)/
777
+ return $1
778
+ end
779
+ end
780
+ end
781
+ end
782
+
783
+ # See ASessionBus
784
+ class SessionBus < ASessionBus
785
+ include Singleton
786
+ end
787
+
788
+
789
+ # = D-Bus system bus class
790
+ #
791
+ # The system bus is a system-wide bus mostly used for global or
792
+ # system usages.
793
+ #
794
+ # Use SystemBus, the non-singleton ASystemBus is
795
+ # for the test suite.
796
+ class ASystemBus < Connection
797
+ # Get the default system bus.
798
+ def initialize
799
+ super(SystemSocketName, ENV["DBUS_THREADED_ACCESS"] || false)
800
+ connect
801
+ send_hello
802
+ end
803
+ end
804
+
805
+ # = D-Bus remote (TCP) bus class
806
+ #
807
+ # This class may be used when connecting to remote (listening on a TCP socket)
808
+ # busses. You can also use it to connect to other non-standard path busses.
809
+ #
810
+ # The specified socket_name should look like this:
811
+ # (for TCP) tcp:host=127.0.0.1,port=2687
812
+ # (for Unix-socket) unix:path=/tmp/my_funky_bus_socket
813
+ #
814
+ # you'll need to take care about authentification then, more info here:
815
+ # http://github.com/pangdudu/ruby-dbus/blob/master/README.rdoc
816
+ class RemoteBus < Connection
817
+
818
+ # Get the remote bus.
819
+ def initialize socket_name
820
+ super(socket_name)
821
+ connect
822
+ send_hello
823
+ end
824
+ end
825
+
826
+ # See ASystemBus
827
+ class SystemBus < ASystemBus
828
+ include Singleton
829
+ end
830
+
831
+ # Shortcut for the SystemBus instance
832
+ def DBus.system_bus
833
+ SystemBus.instance
834
+ end
835
+
836
+ # Shortcut for the SessionBus instance
837
+ def DBus.session_bus
838
+ SessionBus.instance
839
+ end
840
+
841
+ # = Main event loop class.
842
+ #
843
+ # Class that takes care of handling message and signal events
844
+ # asynchronously. *Note:* This is a native implement and therefore does
845
+ # not integrate with a graphical widget set main loop.
846
+ class Main
847
+ # Create a new main event loop.
848
+ def initialize
849
+ @buses = Hash.new
850
+ @quit_queue = Queue.new
851
+ @quitting = false
852
+ $mainclass = self
853
+ end
854
+
855
+ # the standar quit method didn't quit imediately and wait for a last
856
+ # message. This methodes allow to quit imediately
857
+ def quit_imediately
858
+ @buses_thread.each do |th|
859
+ @buses_thread_id.delete(th.object_id)
860
+ th.exit
861
+ end
862
+ @quit_queue << "quit"
863
+ @buses.each_value do |b|
864
+ b.thread.exit
865
+ end
866
+
867
+ end
868
+
869
+ # Add a _bus_ to the list of buses to watch for events.
870
+ def <<(bus)
871
+ @buses[bus.socket] = bus
872
+ end
873
+
874
+ # Quit a running main loop, to be used eg. from a signal handler
875
+ def quit
876
+ if(ENV["DBUS_THREADED_ACCESS"] || false)
877
+ @quitting = true
878
+ quit_imediately
879
+ else
880
+ @quitting = true
881
+ end
882
+ end
883
+
884
+ # Run the main loop. This is a blocking call!
885
+ def run
886
+ # before blocking, empty the buffers
887
+ # https://bugzilla.novell.com/show_bug.cgi?id=537401
888
+ @buses_thread_id = Array.new
889
+ @buses_thread = Array.new
890
+ @thread_as_quit = Queue.new
891
+
892
+ if(ENV["DBUS_THREADED_ACCESS"] || false)
893
+ @buses.each_value do |b|
894
+
895
+ b.rescuemethod = self.method(:quit_imediately)
896
+ th= Thread.new{
897
+ b.main_thread = true
898
+ while m = b.pop_message
899
+ b.process(m)
900
+ end
901
+
902
+ while not b.nil? and not @quitting
903
+ m = b.main_message_queue.pop
904
+ b.process(m)
905
+ end
906
+
907
+ @thread_as_quit << Thread.current.object_id
908
+ }
909
+ @buses_thread_id.push th.object_id
910
+ @buses_thread.push th
911
+ end
912
+
913
+ @quit_queue.pop
914
+
915
+ while not @buses_thread_id.empty?
916
+ id = @thread_as_quit.pop
917
+ @buses_thread_id.delete(id)
918
+ end
919
+
920
+ else
921
+ @buses.each_value do |b|
922
+ while m = b.pop_message
923
+ b.process(m)
924
+ end
925
+ end
926
+ while not @quitting and not @buses.empty?
927
+ ready, dum, dum = IO.select(@buses.keys)
928
+ ready.each do |socket|
929
+ b = @buses[socket]
930
+ begin
931
+ b.update_buffer
932
+ rescue EOFError, SystemCallError
933
+ @buses.delete socket # this bus died
934
+ next
935
+ end
936
+ while m = b.pop_message
937
+ b.process(m)
938
+ end
939
+ end
940
+ end
941
+
942
+ end # if($threaded)
943
+ end # run
944
+
945
+ end # class Main
946
+
947
+ end # module DBus