ruby-dbus 0.5.0

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