ruby-dbus 0.5.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 (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