pangdudu-ruby-dbus 0.1.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.
- data/COPYING +504 -0
- data/README.rdoc +111 -0
- data/config/remote.session.dbus.conf +39 -0
- data/config/start_dbus_session.sh +2 -0
- data/lib/dbus/auth.rb +238 -0
- data/lib/dbus/bus.rb +719 -0
- data/lib/dbus/export.rb +123 -0
- data/lib/dbus/introspect.rb +509 -0
- data/lib/dbus/marshall.rb +396 -0
- data/lib/dbus/matchrule.rb +98 -0
- data/lib/dbus/message.rb +281 -0
- data/lib/dbus/type.rb +207 -0
- data/lib/dbus.rb +88 -0
- data/test/simple_socket_test.rb +27 -0
- metadata +78 -0
data/README.rdoc
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
= Ruby D-Bus -> ffwd
|
2
|
+
|
3
|
+
Ruby D-Bus provides an implementation of the D-Bus protocol such that the
|
4
|
+
D-Bus system can be used in the Ruby programming language.
|
5
|
+
|
6
|
+
|
7
|
+
This is my personal dev-fork of mvidners upstream.
|
8
|
+
|
9
|
+
If you'd like to contrib to this fork, let me know.
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
Most of my work here is actually for another project: http://github.com/pangdudu/robots/tree/master
|
14
|
+
|
15
|
+
I might also start writing a less ugly version of: http://github.com/pangdudu/ruby-dbus-daemon/tree/master
|
16
|
+
|
17
|
+
If you know of a nice custom-dbus-daemon let me know. Still thinking about writing
|
18
|
+
a dbus spec compliant server/daemon implementation in (pure) Ruby, so that I can
|
19
|
+
play around with the network and authentification more easily.
|
20
|
+
|
21
|
+
Oki, will clean up a little now. :)
|
22
|
+
|
23
|
+
Peace.
|
24
|
+
|
25
|
+
== Requirements
|
26
|
+
|
27
|
+
* Ruby 1.8 (>= 1.8.6?)
|
28
|
+
|
29
|
+
== Installation
|
30
|
+
|
31
|
+
sudo gem install pangdudu-ruby-dbus --source=http://gems.github.com
|
32
|
+
|
33
|
+
== Features
|
34
|
+
|
35
|
+
Ruby D-Bus currently supports the following features:
|
36
|
+
|
37
|
+
* Connecting to local buses.
|
38
|
+
* Accessing remote services, objects and interfaces.
|
39
|
+
* Invoking methods on remote objects synchronously and asynchronously.
|
40
|
+
* Catch signals on remote objects and handle them via callbacks.
|
41
|
+
* Remote object introspection.
|
42
|
+
* Walking object trees.
|
43
|
+
* Creating services and registering them on the bus.
|
44
|
+
* Exporting objects with interfaces on a bus for remote use.
|
45
|
+
* Rubyish D-Bus object and interface syntax support that automatically
|
46
|
+
allows for introspection.
|
47
|
+
* Emitting signals on exported objects.
|
48
|
+
|
49
|
+
* Connection to a local or remote bus over TCP/IP
|
50
|
+
* Authentification mechanisms working now: External,CookieSHA1
|
51
|
+
|
52
|
+
== Usage
|
53
|
+
|
54
|
+
=== Basics:
|
55
|
+
|
56
|
+
View a tutorial online on http://trac.luon.net/data/ruby-dbus/tutorial/.
|
57
|
+
|
58
|
+
|
59
|
+
=== TCP/Remote stuff:
|
60
|
+
|
61
|
+
Take a look at 'config/remote.session.dbus.conf' and start 'config/start_dbus_session.sh'.
|
62
|
+
|
63
|
+
You can now try and run one of the tests e.g. 'test/simple_socket_test.rb'.
|
64
|
+
|
65
|
+
|
66
|
+
=== Problems you'll have with 'CookieSHA1' when using it remotely:
|
67
|
+
|
68
|
+
Unless we [including you :)] write a funkier SASL authentification mechanism that
|
69
|
+
makes sense when using it over-wire, we're stuck with the cookie auth.
|
70
|
+
|
71
|
+
Works like this:
|
72
|
+
|
73
|
+
There is a file in '~/.dbus-keyrings' named 'org_freedesktop_general'.
|
74
|
+
It is '-rw-------' so that only you can read it (if you change the permissions,
|
75
|
+
dbus-daemon will most likely ignore the file leaving you with 'External', which is
|
76
|
+
even worse remotely). It's kind of a secret key you use to auth against yourself
|
77
|
+
later on.
|
78
|
+
|
79
|
+
If you take a look at 'dbus/auth.rb line 40+' you'll see the problem:
|
80
|
+
|
81
|
+
# Search cookie file for id
|
82
|
+
path = File.join(ENV['HOME'], '.dbus-keyrings', context)
|
83
|
+
|
84
|
+
So if you're starting 'config/start_dbus_session.sh' on one host and 'config/start_dbus_session.sh'
|
85
|
+
on another one, you'll need to make sure that the content in '~/.dbus-keyrings/org_freedesktop_general'
|
86
|
+
is the same on both machines, in order for the 'CookieSHA1' auth to work.
|
87
|
+
|
88
|
+
Not cool. I can think of hacks with nfs,smb or fuse:sshfs making it less
|
89
|
+
painful to use.
|
90
|
+
|
91
|
+
The file content also get's updated every 5 minutes (when a client fails to auth etc.).
|
92
|
+
Making copy and paste from one shell to another very frolic.
|
93
|
+
|
94
|
+
To sum it up:
|
95
|
+
|
96
|
+
Today it's acceptable to use it like this, but until next week we'll need an easy
|
97
|
+
auth mechanism that works on the wire.
|
98
|
+
|
99
|
+
|
100
|
+
more infos:
|
101
|
+
http://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms
|
102
|
+
http://lists.freedesktop.org/archives/dbus/2007-June/008067.html
|
103
|
+
|
104
|
+
Booyaa.
|
105
|
+
|
106
|
+
== License
|
107
|
+
|
108
|
+
Ruby D-Bus is free software; you can redistribute it and/or modify it
|
109
|
+
under the terms of the GNU Lesser General Public License as published by the
|
110
|
+
Free Software Foundation; either version 2.1 of the License, or (at
|
111
|
+
your option) any later version.
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<!-- This configuration file controls the per-user-login-session message bus.
|
2
|
+
Add a session-local.conf and edit that rather than changing this
|
3
|
+
file directly. -->
|
4
|
+
|
5
|
+
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
|
6
|
+
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
7
|
+
<busconfig>
|
8
|
+
<!-- Our well-known bus type, don't change this -->
|
9
|
+
<type>session</type>
|
10
|
+
|
11
|
+
<!-- If we fork, keep the user's original umask to avoid affecting
|
12
|
+
the behavior of child processes. -->
|
13
|
+
<keep_umask/>
|
14
|
+
|
15
|
+
<!-- <allow_anonymous/> -->
|
16
|
+
|
17
|
+
<!-- Listen to everything on tcp! -->
|
18
|
+
<listen>tcp:host=0.0.0.0,port=2687,family=ipv4</listen>
|
19
|
+
<!-- Listen on socket at this file location -->
|
20
|
+
<listen>unix:path=/tmp/socket_test_session_bus_socket</listen>
|
21
|
+
|
22
|
+
<standard_session_servicedirs />
|
23
|
+
|
24
|
+
<policy context="default">
|
25
|
+
<!-- Allow everything to be sent -->
|
26
|
+
<allow send_destination="*" eavesdrop="true"/>
|
27
|
+
<!-- Allow everything to be received -->
|
28
|
+
<allow eavesdrop="true"/>
|
29
|
+
<!-- Allow anyone to own anything -->
|
30
|
+
<allow own="*"/>
|
31
|
+
</policy>
|
32
|
+
|
33
|
+
<!-- raise the service start timeout to 40 seconds as it can timeout
|
34
|
+
on the live cd on slow machines -->
|
35
|
+
<limit name="service_start_timeout">60000</limit>
|
36
|
+
|
37
|
+
<include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>
|
38
|
+
|
39
|
+
</busconfig>
|
data/lib/dbus/auth.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
# This file is part of the ruby-dbus project
|
2
|
+
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
|
3
|
+
#
|
4
|
+
# This library is free software; you can redistribute it and/or
|
5
|
+
# modify it under the terms of the GNU Lesser General Public
|
6
|
+
# License, version 2.1 as published by the Free Software Foundation.
|
7
|
+
# See the file "COPYING" for the exact licensing terms.
|
8
|
+
|
9
|
+
module DBus
|
10
|
+
# Exception raised when authentication fails somehow.
|
11
|
+
class AuthenticationFailed < Exception
|
12
|
+
end
|
13
|
+
|
14
|
+
# = General class for authentication.
|
15
|
+
class Authenticator
|
16
|
+
# Returns the name of the authenticator.
|
17
|
+
def name
|
18
|
+
self.class.to_s.upcase.sub(/.*::/, "")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# = External authentication class
|
23
|
+
#
|
24
|
+
# Class for 'external' type authentication.
|
25
|
+
class External < Authenticator
|
26
|
+
# Performs the authentication.
|
27
|
+
def authenticate
|
28
|
+
# Take the user id (eg integer 1000) make a string out of it "1000", take
|
29
|
+
# each character and determin hex value "1" => 0x31, "0" => 0x30. You
|
30
|
+
# obtain for "1000" => 31303030 This is what the server is expecting.
|
31
|
+
# Why? I dunno. How did I come to that conclusion? by looking at rbus
|
32
|
+
# code. I have no idea how he found that out.
|
33
|
+
return Process.uid.to_s.split(//).collect { |a| "%x" % a[0] }.join
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# = Authentication class using SHA1 crypto algorithm
|
38
|
+
#
|
39
|
+
# Class for 'CookieSHA1' type authentication.
|
40
|
+
# Implements the AUTH DBUS_COOKIE_SHA1 mechanism.
|
41
|
+
class DBusCookieSHA1 < Authenticator
|
42
|
+
|
43
|
+
#the autenticate method (called in stage one of authentification)
|
44
|
+
def authenticate
|
45
|
+
require 'etc'
|
46
|
+
return "#{hex_encode(Etc.getlogin)}" #server expects it to be binary
|
47
|
+
end
|
48
|
+
|
49
|
+
#returns the modules name
|
50
|
+
def name
|
51
|
+
return 'DBUS_COOKIE_SHA1'
|
52
|
+
end
|
53
|
+
|
54
|
+
#handles the interesting crypto stuff, check the rbus-project for more info: http://rbus.rubyforge.org/
|
55
|
+
def data(hexdata)
|
56
|
+
require 'digest/sha1'
|
57
|
+
data = hex_decode(hexdata)
|
58
|
+
# name of cookie file, id of cookie in file, servers random challenge
|
59
|
+
context, id, s_challenge = data.split(' ')
|
60
|
+
# Random client challenge
|
61
|
+
c_challenge = Array.new(s_challenge.length/2).map{|obj|obj=rand(255).to_s}.join
|
62
|
+
# Search cookie file for id
|
63
|
+
path = File.join(ENV['HOME'], '.dbus-keyrings', context)
|
64
|
+
dlog "path: #{path.inspect}"
|
65
|
+
File.foreach(path) do |line|
|
66
|
+
if line.index(id) == 0
|
67
|
+
# Right line of file, read cookie
|
68
|
+
cookie = line.split(' ')[2].chomp
|
69
|
+
dlog "cookie: #{cookie.inspect}"
|
70
|
+
# Concatenate and encrypt
|
71
|
+
to_encrypt = [s_challenge, c_challenge, cookie].join(':')
|
72
|
+
sha = Digest::SHA1.hexdigest(to_encrypt)
|
73
|
+
#the almighty tcp server wants everything hex encoded
|
74
|
+
hex_response = hex_encode("#{c_challenge} #{sha}")
|
75
|
+
# Return response
|
76
|
+
response = [:AuthOk, hex_response]
|
77
|
+
return response
|
78
|
+
end
|
79
|
+
end
|
80
|
+
elog "Unable to locate cookie"
|
81
|
+
end
|
82
|
+
|
83
|
+
# encode plain to hex
|
84
|
+
def hex_encode(plain)
|
85
|
+
return nil if plain.nil?
|
86
|
+
plain.to_s.unpack('H*')[0]
|
87
|
+
end
|
88
|
+
|
89
|
+
# decode hex to plain
|
90
|
+
def hex_decode(encoded)
|
91
|
+
encoded.scan(/[[:xdigit:]]{2}/).map{|h|h.hex.chr}.join
|
92
|
+
end
|
93
|
+
end #DBusCookieSHA1 class ends here
|
94
|
+
|
95
|
+
# = Authentication client class.
|
96
|
+
#
|
97
|
+
# Class tha performs the actional authentication.
|
98
|
+
class Client
|
99
|
+
# Create a new authentication client.
|
100
|
+
def initialize(socket)
|
101
|
+
@socket = socket
|
102
|
+
@state = nil
|
103
|
+
@auth_list = [External,DBusCookieSHA1]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Start the authentication process.
|
107
|
+
def authenticate
|
108
|
+
@socket.write(0.chr)
|
109
|
+
next_authenticator
|
110
|
+
@state = :Starting
|
111
|
+
while @state != :Authenticated
|
112
|
+
r = next_state
|
113
|
+
return r if not r
|
114
|
+
end
|
115
|
+
true
|
116
|
+
end
|
117
|
+
|
118
|
+
##########
|
119
|
+
private
|
120
|
+
##########
|
121
|
+
|
122
|
+
# Send an authentication method _meth_ with arguments _args_ to the
|
123
|
+
# server.
|
124
|
+
def send(meth, *args)
|
125
|
+
o = ([meth] + args).join(" ")
|
126
|
+
@socket.write(o + "\r\n")
|
127
|
+
end
|
128
|
+
|
129
|
+
# Try authentication using the next authenticator.
|
130
|
+
def next_authenticator
|
131
|
+
begin
|
132
|
+
raise AuthException if @auth_list.size == 0
|
133
|
+
@authenticator = @auth_list.shift.new
|
134
|
+
auth_msg = ["AUTH", @authenticator.name, @authenticator.authenticate]
|
135
|
+
dlog "auth_msg: #{auth_msg.inspect}"
|
136
|
+
send(auth_msg)
|
137
|
+
rescue AuthException
|
138
|
+
@socket.close
|
139
|
+
raise
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# Read data (a buffer) from the bus until CR LF is encountered.
|
145
|
+
# Return the buffer without the CR LF characters.
|
146
|
+
def next_msg
|
147
|
+
data,crlf = "","\r\n"
|
148
|
+
left = 1024 #1024 byte, no idea if it's ever getting bigger
|
149
|
+
while left > 0
|
150
|
+
buf = @socket.read( left > 1 ? 1 : left )
|
151
|
+
break if buf.nil?
|
152
|
+
left -= buf.size
|
153
|
+
data += buf
|
154
|
+
break if data.include? crlf #crlf means line finished, the TCP socket keeps on listening, so we break
|
155
|
+
end
|
156
|
+
readline = data.chomp.split(" ")
|
157
|
+
dlog "readline: #{readline.inspect}"
|
158
|
+
return readline
|
159
|
+
end
|
160
|
+
|
161
|
+
# Try to reach the next state based on the current state.
|
162
|
+
def next_state
|
163
|
+
msg = next_msg
|
164
|
+
if @state == :Starting
|
165
|
+
dlog ":Starting msg: #{msg[0].inspect}"
|
166
|
+
case msg[0]
|
167
|
+
when "OK"
|
168
|
+
@state = :WaitingForOk
|
169
|
+
when "CONTINUE"
|
170
|
+
@state = :WaitingForData
|
171
|
+
when "REJECTED" #needed by tcp, unix-path/abstract doesn't get here
|
172
|
+
@state = :WaitingForData
|
173
|
+
end
|
174
|
+
end
|
175
|
+
dlog "state: #{@state}"
|
176
|
+
case @state
|
177
|
+
when :WaitingForData
|
178
|
+
dlog ":WaitingForData msg: #{msg[0].inspect}"
|
179
|
+
case msg[0]
|
180
|
+
when "DATA"
|
181
|
+
chall = msg[1]
|
182
|
+
resp, chall = @authenticator.data(chall)
|
183
|
+
dlog ":WaitingForData/DATA resp: #{resp.inspect}"
|
184
|
+
case resp
|
185
|
+
when :AuthContinue
|
186
|
+
send("DATA", chall)
|
187
|
+
@state = :WaitingForData
|
188
|
+
when :AuthOk
|
189
|
+
send("DATA", chall)
|
190
|
+
@state = :WaitingForOk
|
191
|
+
when :AuthError
|
192
|
+
send("ERROR")
|
193
|
+
@state = :WaitingForData
|
194
|
+
end
|
195
|
+
when "REJECTED"
|
196
|
+
next_authenticator
|
197
|
+
@state = :WaitingForData
|
198
|
+
when "ERROR"
|
199
|
+
send("CANCEL")
|
200
|
+
@state = :WaitingForReject
|
201
|
+
when "OK"
|
202
|
+
send("BEGIN")
|
203
|
+
@state = :Authenticated
|
204
|
+
else
|
205
|
+
send("ERROR")
|
206
|
+
@state = :WaitingForData
|
207
|
+
end
|
208
|
+
when :WaitingForOk
|
209
|
+
dlog ":WaitingForOk msg: #{msg[0].inspect}"
|
210
|
+
case msg[0]
|
211
|
+
when "OK"
|
212
|
+
send("BEGIN")
|
213
|
+
@state = :Authenticated
|
214
|
+
when "REJECT"
|
215
|
+
next_authenticator
|
216
|
+
@state = :WaitingForData
|
217
|
+
when "DATA", "ERROR"
|
218
|
+
send("CANCEL")
|
219
|
+
@state = :WaitingForReject
|
220
|
+
else
|
221
|
+
send("ERROR")
|
222
|
+
@state = :WaitingForOk
|
223
|
+
end
|
224
|
+
when :WaitingForReject
|
225
|
+
dlog ":WaitingForReject msg: #{msg[0].inspect}"
|
226
|
+
case msg[0]
|
227
|
+
when "REJECT"
|
228
|
+
next_authenticator
|
229
|
+
@state = :WaitingForOk
|
230
|
+
else
|
231
|
+
@socket.close
|
232
|
+
return false
|
233
|
+
end
|
234
|
+
end
|
235
|
+
return true
|
236
|
+
end # def next_state
|
237
|
+
end # class Client
|
238
|
+
end # module D-Bus
|
data/lib/dbus/bus.rb
ADDED
@@ -0,0 +1,719 @@
|
|
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
|
+
require 'socket'
|
11
|
+
require 'thread'
|
12
|
+
require 'singleton'
|
13
|
+
|
14
|
+
# = D-Bus main module
|
15
|
+
#
|
16
|
+
# Module containing all the D-Bus modules and classes.
|
17
|
+
module DBus
|
18
|
+
# This represents a remote service. It should not be instancied directly
|
19
|
+
# Use Bus::service()
|
20
|
+
class Service
|
21
|
+
# The service name.
|
22
|
+
attr_reader :name
|
23
|
+
# The bus the service is running on.
|
24
|
+
attr_reader :bus
|
25
|
+
# The service root (FIXME).
|
26
|
+
attr_reader :root
|
27
|
+
|
28
|
+
# Create a new service with a given _name_ on a given _bus_.
|
29
|
+
def initialize(name, bus)
|
30
|
+
@name, @bus = name, bus
|
31
|
+
@root = Node.new("/")
|
32
|
+
end
|
33
|
+
|
34
|
+
# Determine whether the serice name already exists.
|
35
|
+
def exists?
|
36
|
+
bus.proxy.ListName.member?(@name)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Perform an introspection on all the objects on the service
|
40
|
+
# (starting recursively from the root).
|
41
|
+
def introspect
|
42
|
+
if block_given?
|
43
|
+
raise NotImplementedError
|
44
|
+
else
|
45
|
+
rec_introspect(@root, "/")
|
46
|
+
end
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
# Retrieves an object at the given _path_.
|
51
|
+
def object(path)
|
52
|
+
node = get_node(path, true)
|
53
|
+
if node.object.nil?
|
54
|
+
node.object = ProxyObject.new(@bus, @name, path)
|
55
|
+
end
|
56
|
+
node.object
|
57
|
+
end
|
58
|
+
|
59
|
+
# Export an object _obj_ (an DBus::Object subclass instance).
|
60
|
+
def export(obj)
|
61
|
+
obj.service = self
|
62
|
+
get_node(obj.path, true).object = obj
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get the object node corresponding to the given _path_. if _create_ is
|
66
|
+
# true, the the nodes in the path are created if they do not already exist.
|
67
|
+
def get_node(path, create = false)
|
68
|
+
n = @root
|
69
|
+
path.sub(/^\//, "").split("/").each do |elem|
|
70
|
+
if not n[elem]
|
71
|
+
if not create
|
72
|
+
return nil
|
73
|
+
else
|
74
|
+
n[elem] = Node.new(elem)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
n = n[elem]
|
78
|
+
end
|
79
|
+
if n.nil?
|
80
|
+
wlog "Unknown object #{path.inspect}"
|
81
|
+
end
|
82
|
+
n
|
83
|
+
end
|
84
|
+
|
85
|
+
#########
|
86
|
+
private
|
87
|
+
#########
|
88
|
+
|
89
|
+
# Perform a recursive retrospection on the given current _node_
|
90
|
+
# on the given _path_.
|
91
|
+
def rec_introspect(node, path)
|
92
|
+
xml = bus.introspect_data(@name, path)
|
93
|
+
intfs, subnodes = IntrospectXMLParser.new(xml).parse
|
94
|
+
subnodes.each do |nodename|
|
95
|
+
subnode = node[nodename] = Node.new(nodename)
|
96
|
+
if path == "/"
|
97
|
+
subpath = "/" + nodename
|
98
|
+
else
|
99
|
+
subpath = path + "/" + nodename
|
100
|
+
end
|
101
|
+
rec_introspect(subnode, subpath)
|
102
|
+
end
|
103
|
+
if intfs.size > 0
|
104
|
+
node.object = ProxyObjectFactory.new(xml, @bus, @name, path).build
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# = Object path node class
|
110
|
+
#
|
111
|
+
# Class representing a node on an object path.
|
112
|
+
class Node < Hash
|
113
|
+
# The D-Bus object contained by the node.
|
114
|
+
attr_accessor :object
|
115
|
+
# The name of the node.
|
116
|
+
attr_reader :name
|
117
|
+
|
118
|
+
# Create a new node with a given _name_.
|
119
|
+
def initialize(name)
|
120
|
+
@name = name
|
121
|
+
@object = nil
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return an XML string representation of the node.
|
125
|
+
def to_xml
|
126
|
+
xml = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
127
|
+
<node>'
|
128
|
+
|
129
|
+
self.each_pair do |k, v|
|
130
|
+
xml += "<node name=\"#{k}\" />"
|
131
|
+
end
|
132
|
+
if @object
|
133
|
+
@object.intfs.each_pair do |k, v|
|
134
|
+
xml += %{<interface name="#{v.name}">\n}
|
135
|
+
v.methods.each_value { |m| xml += m.to_xml }
|
136
|
+
v.signals.each_value { |m| xml += m.to_xml }
|
137
|
+
xml +="</interface>\n"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
xml += '</node>'
|
141
|
+
return xml
|
142
|
+
end
|
143
|
+
|
144
|
+
# Return inspect information of the node.
|
145
|
+
def inspect
|
146
|
+
# Need something here
|
147
|
+
"<DBus::Node #{sub_inspect}>"
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return instance inspect information, used by Node#inspect.
|
151
|
+
def sub_inspect
|
152
|
+
s = ""
|
153
|
+
if not @object.nil?
|
154
|
+
s += "%x " % @object.object_id
|
155
|
+
end
|
156
|
+
s + "{" + keys.collect { |k| "#{k} => #{self[k].sub_inspect}" }.join(",") + "}"
|
157
|
+
end
|
158
|
+
end # class Inspect
|
159
|
+
|
160
|
+
# FIXME: rename Connection to Bus?
|
161
|
+
|
162
|
+
# D-Bus main connection class
|
163
|
+
#
|
164
|
+
# Main class that maintains a connection to a bus and can handle incoming
|
165
|
+
# and outgoing messages.
|
166
|
+
class Connection
|
167
|
+
# The unique name (by specification) of the message.
|
168
|
+
attr_reader :unique_name
|
169
|
+
# The socket that is used to connect with the bus.
|
170
|
+
attr_reader :socket
|
171
|
+
|
172
|
+
# Create a new connection to the bus for a given connect _path_. _path_
|
173
|
+
# format is described in the D-Bus specification:
|
174
|
+
# http://dbus.freedesktop.org/doc/dbus-specification.html#addresses
|
175
|
+
# and is something like:
|
176
|
+
# "transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2"
|
177
|
+
# e.g. "unix:path=/tmp/dbus-test" or "tcp:host=localhost,port=2687"
|
178
|
+
def initialize(path)
|
179
|
+
@path = path
|
180
|
+
@unique_name = nil
|
181
|
+
@buffer = ""
|
182
|
+
@method_call_replies = Hash.new
|
183
|
+
@method_call_msgs = Hash.new
|
184
|
+
@signal_matchrules = Array.new
|
185
|
+
@proxy = nil
|
186
|
+
@object_root = Node.new("/")
|
187
|
+
end
|
188
|
+
|
189
|
+
# Connect to the bus and initialize the connection.
|
190
|
+
def connect
|
191
|
+
connect_to_tcp if @path.include? "tcp:" #connect to tcp socket
|
192
|
+
connect_to_unix if @path.include? "unix:" #connect to unix socket
|
193
|
+
end
|
194
|
+
|
195
|
+
# Connect to a bus over tcp and initialize the connection.
|
196
|
+
def connect_to_tcp
|
197
|
+
#check if the path is sufficient
|
198
|
+
if @path.include? "host=" and @path.include? "port="
|
199
|
+
host,port,family = "","",""
|
200
|
+
#get the parameters
|
201
|
+
@path.split(",").each do |para|
|
202
|
+
host = para.sub("tcp:","").sub("host=","") if para.include? "host="
|
203
|
+
port = para.sub("port=","").to_i if para.include? "port="
|
204
|
+
family = para.sub("family=","") if para.include? "family="
|
205
|
+
end
|
206
|
+
dlog "host,port,family : #{host},#{port},#{family}"
|
207
|
+
begin
|
208
|
+
#initialize the tcp socket
|
209
|
+
@socket = TCPSocket.new(host,port)
|
210
|
+
init_connection
|
211
|
+
rescue
|
212
|
+
elog "Could not establish connection to: #{@path}, will now exit."
|
213
|
+
exit(0) #a little harsh
|
214
|
+
end
|
215
|
+
else
|
216
|
+
#Danger, Will Robinson: the specified "path" is not usable
|
217
|
+
elog "supplied path: #{@path}, unusable! sorry."
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Connect to an abstract unix bus and initialize the connection.
|
222
|
+
def connect_to_unix
|
223
|
+
@socket = Socket.new(Socket::Constants::PF_UNIX,Socket::Constants::SOCK_STREAM, 0)
|
224
|
+
parse_session_string
|
225
|
+
if @transport == "unix" and @type == "abstract"
|
226
|
+
if HOST_END == LIL_END
|
227
|
+
sockaddr = "\1\0\0#{@unix_abstract}"
|
228
|
+
else
|
229
|
+
sockaddr = "\0\1\0#{@unix_abstract}"
|
230
|
+
end
|
231
|
+
elsif @transport == "unix" and @type == "path"
|
232
|
+
sockaddr = Socket.pack_sockaddr_un(@unix)
|
233
|
+
end
|
234
|
+
@socket.connect(sockaddr)
|
235
|
+
init_connection
|
236
|
+
end
|
237
|
+
|
238
|
+
# Parse the session string (socket address).
|
239
|
+
def parse_session_string
|
240
|
+
path_parsed = /^([^:]*):([^;]*)$/.match(@path)
|
241
|
+
@transport = path_parsed[1]
|
242
|
+
adr = path_parsed[2]
|
243
|
+
if @transport == "unix"
|
244
|
+
adr.split(",").each do |eqstr|
|
245
|
+
idx, val = eqstr.split("=")
|
246
|
+
case idx
|
247
|
+
when "path"
|
248
|
+
@type = idx
|
249
|
+
@unix = val
|
250
|
+
when "abstract"
|
251
|
+
@type = idx
|
252
|
+
@unix_abstract = val
|
253
|
+
when "guid"
|
254
|
+
@guid = val
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Send the buffer _buf_ to the bus using Connection#writel.
|
261
|
+
def send(buf)
|
262
|
+
@socket.write(buf) unless @socket.nil?
|
263
|
+
end
|
264
|
+
|
265
|
+
# Tell a bus to register itself on the glib main loop
|
266
|
+
def glibize
|
267
|
+
require 'glib2'
|
268
|
+
# Circumvent a ruby-glib bug
|
269
|
+
@channels ||= Array.new
|
270
|
+
|
271
|
+
gio = GLib::IOChannel.new(@socket.fileno)
|
272
|
+
@channels << gio
|
273
|
+
gio.add_watch(GLib::IOChannel::IN) do |c, ch|
|
274
|
+
update_buffer
|
275
|
+
messages.each do |msg|
|
276
|
+
process(msg)
|
277
|
+
end
|
278
|
+
true
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# FIXME: describe the following names, flags and constants.
|
283
|
+
# See DBus spec for definition
|
284
|
+
NAME_FLAG_ALLOW_REPLACEMENT = 0x1
|
285
|
+
NAME_FLAG_REPLACE_EXISTING = 0x2
|
286
|
+
NAME_FLAG_DO_NOT_QUEUE = 0x4
|
287
|
+
|
288
|
+
REQUEST_NAME_REPLY_PRIMARY_OWNER = 0x1
|
289
|
+
REQUEST_NAME_REPLY_IN_QUEUE = 0x2
|
290
|
+
REQUEST_NAME_REPLY_EXISTS = 0x3
|
291
|
+
REQUEST_NAME_REPLY_ALREADY_OWNER = 0x4
|
292
|
+
|
293
|
+
DBUSXMLINTRO = '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
294
|
+
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
295
|
+
<node>
|
296
|
+
<interface name="org.freedesktop.DBus.Introspectable">
|
297
|
+
<method name="Introspect">
|
298
|
+
<arg name="data" direction="out" type="s"/>
|
299
|
+
</method>
|
300
|
+
</interface>
|
301
|
+
<interface name="org.freedesktop.DBus">
|
302
|
+
<method name="RequestName">
|
303
|
+
<arg direction="in" type="s"/>
|
304
|
+
<arg direction="in" type="u"/>
|
305
|
+
<arg direction="out" type="u"/>
|
306
|
+
</method>
|
307
|
+
<method name="ReleaseName">
|
308
|
+
<arg direction="in" type="s"/>
|
309
|
+
<arg direction="out" type="u"/>
|
310
|
+
</method>
|
311
|
+
<method name="StartServiceByName">
|
312
|
+
<arg direction="in" type="s"/>
|
313
|
+
<arg direction="in" type="u"/>
|
314
|
+
<arg direction="out" type="u"/>
|
315
|
+
</method>
|
316
|
+
<method name="Hello">
|
317
|
+
<arg direction="out" type="s"/>
|
318
|
+
</method>
|
319
|
+
<method name="NameHasOwner">
|
320
|
+
<arg direction="in" type="s"/>
|
321
|
+
<arg direction="out" type="b"/>
|
322
|
+
</method>
|
323
|
+
<method name="ListNames">
|
324
|
+
<arg direction="out" type="as"/>
|
325
|
+
</method>
|
326
|
+
<method name="ListActivatableNames">
|
327
|
+
<arg direction="out" type="as"/>
|
328
|
+
</method>
|
329
|
+
<method name="AddMatch">
|
330
|
+
<arg direction="in" type="s"/>
|
331
|
+
</method>
|
332
|
+
<method name="RemoveMatch">
|
333
|
+
<arg direction="in" type="s"/>
|
334
|
+
</method>
|
335
|
+
<method name="GetNameOwner">
|
336
|
+
<arg direction="in" type="s"/>
|
337
|
+
<arg direction="out" type="s"/>
|
338
|
+
</method>
|
339
|
+
<method name="ListQueuedOwners">
|
340
|
+
<arg direction="in" type="s"/>
|
341
|
+
<arg direction="out" type="as"/>
|
342
|
+
</method>
|
343
|
+
<method name="GetConnectionUnixUser">
|
344
|
+
<arg direction="in" type="s"/>
|
345
|
+
<arg direction="out" type="u"/>
|
346
|
+
</method>
|
347
|
+
<method name="GetConnectionUnixProcessID">
|
348
|
+
<arg direction="in" type="s"/>
|
349
|
+
<arg direction="out" type="u"/>
|
350
|
+
</method>
|
351
|
+
<method name="GetConnectionSELinuxSecurityContext">
|
352
|
+
<arg direction="in" type="s"/>
|
353
|
+
<arg direction="out" type="ay"/>
|
354
|
+
</method>
|
355
|
+
<method name="ReloadConfig">
|
356
|
+
</method>
|
357
|
+
<signal name="NameOwnerChanged">
|
358
|
+
<arg type="s"/>
|
359
|
+
<arg type="s"/>
|
360
|
+
<arg type="s"/>
|
361
|
+
</signal>
|
362
|
+
<signal name="NameLost">
|
363
|
+
<arg type="s"/>
|
364
|
+
</signal>
|
365
|
+
<signal name="NameAcquired">
|
366
|
+
<arg type="s"/>
|
367
|
+
</signal>
|
368
|
+
</interface>
|
369
|
+
</node>
|
370
|
+
'
|
371
|
+
|
372
|
+
def introspect_data(dest, path)
|
373
|
+
m = DBus::Message.new(DBus::Message::METHOD_CALL)
|
374
|
+
m.path = path
|
375
|
+
m.interface = "org.freedesktop.DBus.Introspectable"
|
376
|
+
m.destination = dest
|
377
|
+
m.member = "Introspect"
|
378
|
+
m.sender = unique_name
|
379
|
+
if not block_given?
|
380
|
+
# introspect in synchronous !
|
381
|
+
send_sync(m) do |rmsg|
|
382
|
+
if rmsg.is_a?(Error)
|
383
|
+
raise rmsg
|
384
|
+
else
|
385
|
+
return rmsg.params[0]
|
386
|
+
end
|
387
|
+
end
|
388
|
+
else
|
389
|
+
send(m.marshall)
|
390
|
+
on_return(m) do |rmsg|
|
391
|
+
if rmsg.is_a?(Error)
|
392
|
+
yield rmsg
|
393
|
+
else
|
394
|
+
yield rmsg.params[0]
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
nil
|
399
|
+
end
|
400
|
+
|
401
|
+
# Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method
|
402
|
+
# _dest_ is the service and _path_ the object path you want to introspect
|
403
|
+
# If a code block is given, the introspect call in asynchronous. If not
|
404
|
+
# data is returned
|
405
|
+
#
|
406
|
+
# FIXME: link to ProxyObject data definition
|
407
|
+
# The returned object is a ProxyObject that has methods you can call to
|
408
|
+
# issue somme METHOD_CALL messages, and to setup to receive METHOD_RETURN
|
409
|
+
def introspect(dest, path)
|
410
|
+
if not block_given?
|
411
|
+
# introspect in synchronous !
|
412
|
+
data = introspect_data(dest, path)
|
413
|
+
pof = DBus::ProxyObjectFactory.new(data, self, dest, path)
|
414
|
+
return pof.build
|
415
|
+
else
|
416
|
+
introspect_data(dest, path) do |data|
|
417
|
+
yield(DBus::ProxyObjectFactory.new(data, self, dest, path).build)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
# Exception raised when a service name is requested that is not available.
|
423
|
+
class NameRequestError < Exception
|
424
|
+
end
|
425
|
+
|
426
|
+
# Attempt to request a service _name_.
|
427
|
+
def request_service(name)
|
428
|
+
r = proxy.RequestName(name, NAME_FLAG_REPLACE_EXISTING)
|
429
|
+
raise NameRequestError if r[0] != REQUEST_NAME_REPLY_PRIMARY_OWNER
|
430
|
+
@service = Service.new(name, self)
|
431
|
+
@service
|
432
|
+
end
|
433
|
+
|
434
|
+
# Set up a ProxyObject for the bus itself, since the bus is introspectable.
|
435
|
+
# Returns the object.
|
436
|
+
def proxy
|
437
|
+
if @proxy == nil
|
438
|
+
path = "/org/freedesktop/DBus"
|
439
|
+
dest = "org.freedesktop.DBus"
|
440
|
+
pof = DBus::ProxyObjectFactory.new(DBUSXMLINTRO, self, dest, path)
|
441
|
+
@proxy = pof.build["org.freedesktop.DBus"]
|
442
|
+
end
|
443
|
+
@proxy
|
444
|
+
end
|
445
|
+
|
446
|
+
# Fill (append) the buffer from data that might be available on the
|
447
|
+
# socket.
|
448
|
+
def update_buffer
|
449
|
+
@buffer += @socket.read_nonblock(MSG_BUF_SIZE) unless @socket.nil?
|
450
|
+
end
|
451
|
+
|
452
|
+
# Get one message from the bus and remove it from the buffer.
|
453
|
+
# Return the message.
|
454
|
+
def pop_message
|
455
|
+
ret = nil
|
456
|
+
begin
|
457
|
+
ret, size = Message.new.unmarshall_buffer(@buffer)
|
458
|
+
@buffer.slice!(0, size)
|
459
|
+
rescue IncompleteBufferException => e
|
460
|
+
# fall through, let ret be null
|
461
|
+
end
|
462
|
+
ret
|
463
|
+
end
|
464
|
+
|
465
|
+
# Retrieve all the messages that are currently in the buffer.
|
466
|
+
def messages
|
467
|
+
ret = Array.new
|
468
|
+
while msg = pop_message
|
469
|
+
ret << msg
|
470
|
+
end
|
471
|
+
ret
|
472
|
+
end
|
473
|
+
|
474
|
+
# The buffer size for messages.
|
475
|
+
MSG_BUF_SIZE = 4096
|
476
|
+
|
477
|
+
# Update the buffer and retrieve all messages using Connection#messages.
|
478
|
+
# Return the messages.
|
479
|
+
def poll_messages
|
480
|
+
ret = nil
|
481
|
+
r, d, d = IO.select([@socket], nil, nil, 0)
|
482
|
+
if r and r.size > 0
|
483
|
+
update_buffer
|
484
|
+
end
|
485
|
+
messages
|
486
|
+
end
|
487
|
+
|
488
|
+
# Wait for a message to arrive. Return it once it is available.
|
489
|
+
def wait_for_message
|
490
|
+
if @socket.nil?
|
491
|
+
elog "Can't wait for messages, @socket is nil."
|
492
|
+
return
|
493
|
+
end
|
494
|
+
ret = pop_message
|
495
|
+
while ret == nil
|
496
|
+
r, d, d = IO.select([@socket])
|
497
|
+
if r and r[0] == @socket
|
498
|
+
update_buffer
|
499
|
+
ret = pop_message
|
500
|
+
end
|
501
|
+
end
|
502
|
+
ret
|
503
|
+
end
|
504
|
+
|
505
|
+
# Send a message _m_ on to the bus. This is done synchronously, thus
|
506
|
+
# the call will block until a reply message arrives.
|
507
|
+
def send_sync(m, &retc) # :yields: reply/return message
|
508
|
+
return if m.nil? #check if somethings wrong
|
509
|
+
send(m.marshall)
|
510
|
+
@method_call_msgs[m.serial] = m
|
511
|
+
@method_call_replies[m.serial] = retc
|
512
|
+
|
513
|
+
retm = wait_for_message
|
514
|
+
|
515
|
+
return if retm.nil? #check if somethings wrong
|
516
|
+
|
517
|
+
process(retm)
|
518
|
+
until [DBus::Message::ERROR,
|
519
|
+
DBus::Message::METHOD_RETURN].include?(retm.message_type) and
|
520
|
+
retm.reply_serial == m.serial
|
521
|
+
retm = wait_for_message
|
522
|
+
process(retm)
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
# Specify a code block that has to be executed when a reply for
|
527
|
+
# message _m_ is received.
|
528
|
+
def on_return(m, &retc)
|
529
|
+
# Have a better exception here
|
530
|
+
if m.message_type != Message::METHOD_CALL
|
531
|
+
elog "Funky exception, occured."
|
532
|
+
raise "on_return should only get method_calls"
|
533
|
+
end
|
534
|
+
@method_call_msgs[m.serial] = m
|
535
|
+
@method_call_replies[m.serial] = retc
|
536
|
+
end
|
537
|
+
|
538
|
+
# Asks bus to send us messages matching mr, and execute slot when
|
539
|
+
# received
|
540
|
+
def add_match(mr, &slot)
|
541
|
+
# check this is a signal.
|
542
|
+
@signal_matchrules << [mr, slot]
|
543
|
+
self.proxy.AddMatch(mr.to_s)
|
544
|
+
end
|
545
|
+
|
546
|
+
# Process a message _m_ based on its type.
|
547
|
+
def process(m)
|
548
|
+
return if m.nil? #check if somethings wrong
|
549
|
+
case m.message_type
|
550
|
+
when Message::ERROR, Message::METHOD_RETURN
|
551
|
+
raise InvalidPacketException if m.reply_serial == nil
|
552
|
+
mcs = @method_call_replies[m.reply_serial]
|
553
|
+
if not mcs
|
554
|
+
dlog "no return code for mcs: #{mcs.inspect} m: #{m.inspect}"
|
555
|
+
else
|
556
|
+
if m.message_type == Message::ERROR
|
557
|
+
mcs.call(Error.new(m))
|
558
|
+
else
|
559
|
+
mcs.call(m)
|
560
|
+
end
|
561
|
+
@method_call_replies.delete(m.reply_serial)
|
562
|
+
@method_call_msgs.delete(m.reply_serial)
|
563
|
+
end
|
564
|
+
when DBus::Message::METHOD_CALL
|
565
|
+
if m.path == "/org/freedesktop/DBus"
|
566
|
+
dlog "Got method call on /org/freedesktop/DBus"
|
567
|
+
end
|
568
|
+
# handle introspectable as an exception:
|
569
|
+
if m.interface == "org.freedesktop.DBus.Introspectable" and
|
570
|
+
m.member == "Introspect"
|
571
|
+
reply = Message.new(Message::METHOD_RETURN).reply_to(m)
|
572
|
+
reply.sender = @unique_name
|
573
|
+
node = @service.get_node(m.path)
|
574
|
+
raise NotImplementedError if not node
|
575
|
+
reply.sender = @unique_name
|
576
|
+
reply.add_param(Type::STRING, @service.get_node(m.path).to_xml)
|
577
|
+
send(reply.marshall)
|
578
|
+
else
|
579
|
+
node = @service.get_node(m.path)
|
580
|
+
return if node.nil?
|
581
|
+
obj = node.object
|
582
|
+
return if obj.nil?
|
583
|
+
obj.dispatch(m) if obj
|
584
|
+
end
|
585
|
+
when DBus::Message::SIGNAL
|
586
|
+
@signal_matchrules.each do |elem|
|
587
|
+
mr, slot = elem
|
588
|
+
if mr.match(m)
|
589
|
+
slot.call(m)
|
590
|
+
return
|
591
|
+
end
|
592
|
+
end
|
593
|
+
else
|
594
|
+
dlog "Unknown message type: #{m.message_type}"
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
# Retrieves the service with the given _name_.
|
599
|
+
def service(name)
|
600
|
+
# The service might not exist at this time so we cannot really check
|
601
|
+
# anything
|
602
|
+
Service.new(name, self)
|
603
|
+
end
|
604
|
+
alias :[] :service
|
605
|
+
|
606
|
+
# Emit a signal event for the given _service_, object _obj_, interface
|
607
|
+
# _intf_ and signal _sig_ with arguments _args_.
|
608
|
+
def emit(service, obj, intf, sig, *args)
|
609
|
+
m = Message.new(DBus::Message::SIGNAL)
|
610
|
+
m.path = obj.path
|
611
|
+
m.interface = intf.name
|
612
|
+
m.member = sig.name
|
613
|
+
m.sender = service.name
|
614
|
+
i = 0
|
615
|
+
sig.params.each do |par|
|
616
|
+
m.add_param(par[1], args[i])
|
617
|
+
i += 1
|
618
|
+
end
|
619
|
+
send(m.marshall)
|
620
|
+
end
|
621
|
+
|
622
|
+
###########################################################################
|
623
|
+
private
|
624
|
+
|
625
|
+
# Send a hello messages to the bus to let it know we are here.
|
626
|
+
def send_hello
|
627
|
+
m = Message.new(DBus::Message::METHOD_CALL)
|
628
|
+
m.path = "/org/freedesktop/DBus"
|
629
|
+
m.destination = "org.freedesktop.DBus"
|
630
|
+
m.interface = "org.freedesktop.DBus"
|
631
|
+
m.member = "Hello"
|
632
|
+
send_sync(m) do |rmsg|
|
633
|
+
@unique_name = rmsg.destination
|
634
|
+
dlog "Got hello reply. Our unique_name is #{@unique_name}, i feel special."
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
# Initialize the connection to the bus.
|
639
|
+
def init_connection
|
640
|
+
@client = Client.new(@socket)
|
641
|
+
@client.authenticate
|
642
|
+
end
|
643
|
+
end # class Connection
|
644
|
+
|
645
|
+
# = D-Bus session bus class
|
646
|
+
#
|
647
|
+
# The session bus is a session specific bus (mostly for desktop use).
|
648
|
+
# This is a singleton class.
|
649
|
+
class SessionBus < Connection
|
650
|
+
include Singleton
|
651
|
+
|
652
|
+
# Get the the default session bus.
|
653
|
+
def initialize socket_name=SessionSocketName
|
654
|
+
super(socket_name)
|
655
|
+
connect
|
656
|
+
send_hello
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
# = D-Bus system bus class
|
661
|
+
#
|
662
|
+
# The system bus is a system-wide bus mostly used for global or
|
663
|
+
# system usages. This is a singleton class.
|
664
|
+
class SystemBus < Connection
|
665
|
+
include Singleton
|
666
|
+
|
667
|
+
# Get the default system bus.
|
668
|
+
def initialize socket_name=SystemSocketName
|
669
|
+
super(socket_name)
|
670
|
+
connect
|
671
|
+
send_hello
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
# FIXME: we should get rid of these singeltons
|
676
|
+
|
677
|
+
def DBus.system_bus
|
678
|
+
SystemBus.instance
|
679
|
+
end
|
680
|
+
|
681
|
+
def DBus.session_bus
|
682
|
+
SessionBus.instance
|
683
|
+
end
|
684
|
+
|
685
|
+
# = Main event loop class.
|
686
|
+
#
|
687
|
+
# Class that takes care of handling message and signal events
|
688
|
+
# asynchronously. *Note:* This is a native implement and therefore does
|
689
|
+
# not integrate with a graphical widget set main loop.
|
690
|
+
class Main
|
691
|
+
# Create a new main event loop.
|
692
|
+
def initialize
|
693
|
+
@buses = Hash.new
|
694
|
+
end
|
695
|
+
|
696
|
+
# Add a _bus_ to the list of buses to watch for events.
|
697
|
+
def <<(bus)
|
698
|
+
@buses[bus.socket] = bus
|
699
|
+
end
|
700
|
+
|
701
|
+
# Run the main loop. This is a blocking call!
|
702
|
+
def run
|
703
|
+
loop do
|
704
|
+
ready, dum, dum = IO.select(@buses.keys)
|
705
|
+
ready.each do |socket|
|
706
|
+
b = @buses[socket]
|
707
|
+
begin
|
708
|
+
b.update_buffer
|
709
|
+
rescue EOFError
|
710
|
+
return # the bus died
|
711
|
+
end
|
712
|
+
while m = b.pop_message
|
713
|
+
b.process(m)
|
714
|
+
end
|
715
|
+
end
|
716
|
+
end
|
717
|
+
end
|
718
|
+
end # class Main
|
719
|
+
end # module DBus
|