ruby-dbus-openplacos 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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