1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
|
# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
# Copyright (C) 2003 David Zeuthen
# Copyright (C) 2004 Rob Taylor
# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
#
# Licensed under the Academic Free License version 2.1
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import sys
import logging
import _dbus_bindings
from dbus._expat_introspect_parser import process_introspection_data
from dbus.exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException, DBusException
__docformat__ = 'restructuredtext'
_logger = logging.getLogger('dbus.proxies')
BUS_DAEMON_NAME = 'org.freedesktop.DBus'
BUS_DAEMON_PATH = '/org/freedesktop/DBus'
BUS_DAEMON_IFACE = BUS_DAEMON_NAME
class _ReplyHandler(object):
__slots__ = ('_on_error', '_on_reply', '_get_args_options')
def __init__(self, on_reply, on_error, **get_args_options):
self._on_error = on_error
self._on_reply = on_reply
self._get_args_options = get_args_options
def __call__(self, message):
if isinstance(message, _dbus_bindings.MethodReturnMessage):
self._on_reply(*message.get_args_list(**self._get_args_options))
elif isinstance(message, _dbus_bindings.ErrorMessage):
args = message.get_args_list()
if len(args) > 0:
self._on_error(DBusException(args[0]))
else:
self._on_error(DBusException())
else:
self._on_error(DBusException('Unexpected reply message type: %s'
% message))
class DeferedMethod:
"""A DeferedMethod
This is returned instead of ProxyMethod when we are defering DBus calls
while waiting for introspection data to be returned
"""
def __init__(self, proxy_method):
self._proxy_method = proxy_method
self._method_name = proxy_method._method_name
def __call__(self, *args, **keywords):
reply_handler = None
if keywords.has_key('reply_handler'):
reply_handler = keywords['reply_handler']
#block for now even on async
# FIXME: put ret in async queue in future if we have a reply handler
self._proxy_method._proxy._pending_introspect.block()
ret = self._proxy_method (*args, **keywords)
return ret
class ProxyMethod:
"""A proxy Method.
Typically a member of a ProxyObject. Calls to the
method produce messages that travel over the Bus and are routed
to a specific named Service.
"""
def __init__(self, proxy, connection, named_service, object_path, method_name, iface):
self._proxy = proxy
self._connection = connection
self._named_service = named_service
self._object_path = object_path
self._method_name = method_name
self._dbus_interface = iface
def __call__(self, *args, **keywords):
timeout = -1
if keywords.has_key('timeout'):
timeout = keywords['timeout']
reply_handler = None
if keywords.has_key('reply_handler'):
reply_handler = keywords['reply_handler']
error_handler = None
if keywords.has_key('error_handler'):
error_handler = keywords['error_handler']
ignore_reply = False
if keywords.has_key('ignore_reply'):
ignore_reply = keywords['ignore_reply']
get_args_options = {}
if keywords.has_key('utf8_strings'):
get_args_options['utf8_strings'] = keywords['utf8_strings']
if keywords.has_key('byte_arrays'):
get_args_options['byte_arrays'] = keywords['byte_arrays']
if not(reply_handler and error_handler):
if reply_handler:
raise MissingErrorHandlerException()
elif error_handler:
raise MissingReplyHandlerException()
dbus_interface = self._dbus_interface
if keywords.has_key('dbus_interface'):
dbus_interface = keywords['dbus_interface']
tmp_iface = ''
if dbus_interface:
tmp_iface = dbus_interface + '.'
key = tmp_iface + self._method_name
introspect_sig = None
if self._proxy._introspect_method_map.has_key (key):
introspect_sig = self._proxy._introspect_method_map[key]
message = _dbus_bindings.MethodCallMessage(destination=None,
path=self._object_path,
interface=dbus_interface,
method=self._method_name)
message.set_destination(self._named_service)
# Add the arguments to the function
try:
message.append(signature=introspect_sig, *args)
except Exception, e:
_logger.error('Unable to set arguments %r according to '
'introspected signature %r: %s: %s',
args, introspect_sig, e.__class__, e)
raise
if ignore_reply:
self._connection.send_message(message)
return None
elif reply_handler:
self._connection.send_message_with_reply(message, _ReplyHandler(reply_handler, error_handler, **get_args_options), timeout/1000.0, require_main_loop=1)
return None
else:
reply_message = self._connection.send_message_with_reply_and_block(message, timeout)
args_list = reply_message.get_args_list(**get_args_options)
if len(args_list) == 0:
return None
elif len(args_list) == 1:
return args_list[0]
else:
return tuple(args_list)
class ProxyObject:
"""A proxy to the remote Object.
A ProxyObject is provided by the Bus. ProxyObjects
have member functions, and can be called like normal Python objects.
"""
ProxyMethodClass = ProxyMethod
DeferedMethodClass = DeferedMethod
INTROSPECT_STATE_DONT_INTROSPECT = 0
INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
INTROSPECT_STATE_INTROSPECT_DONE = 2
def __init__(self, bus, named_service, object_path, introspect=True,
follow_name_owner_changes=False):
"""Initialize the proxy object.
:Parameters:
`bus` : `dbus.Bus`
The bus on which to find this object
`named_service` : str
A bus name for the endpoint owning the object (need not
actually be a service name)
`object_path` : str
The object path at which the endpoint exports the object
`introspect` : bool
If true (default), attempt to introspect the remote
object to find out supported methods and their signatures
`follow_name_owner_changes` : bool
If true (default is false) and the `named_service` is a
well-known name, follow ownership changes for that name
"""
if follow_name_owner_changes:
bus._require_main_loop() # we don't get the signals otherwise
self._bus = bus
self._named_service = named_service
self.__dbus_object_path__ = object_path
if (named_service[:1] != ':' and named_service != BUS_DAEMON_NAME
and not follow_name_owner_changes):
bus_object = bus.get_object(BUS_DAEMON_NAME, BUS_DAEMON_PATH)
try:
self._named_service = bus_object.GetNameOwner(named_service,
dbus_interface=BUS_DAEMON_IFACE)
except DBusException, e:
# FIXME: detect whether it's NameHasNoOwner
#if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
# raise
# it might not exist: try to start it
bus_object.StartServiceByName(named_service,
_dbus_bindings.UInt32(0))
self._named_service = bus_object.GetNameOwner(named_service,
dbus_interface=BUS_DAEMON_IFACE)
#PendingCall object for Introspect call
self._pending_introspect = None
#queue of async calls waiting on the Introspect to return
self._pending_introspect_queue = []
#dictionary mapping method names to their input signatures
self._introspect_method_map = {}
if not introspect:
self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
else:
self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
self._pending_introspect = self._Introspect()
def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
"""Arrange for the given function to be called when the given signal
is received.
:Parameters:
`signal_name` : str
The name of the signal
`handler_function` : callable
A function to be called when the signal is emitted by
the remote object. Its positional arguments will be the
arguments of the signal; optionally, it may be given
keyword arguments as described below.
`dbus_interface` : str
Optional interface with which to qualify the signal name.
If None (the default) the handler will be called whenever a
signal of the given member name is received, whatever
its interface.
:Keywords:
`utf8_strings` : bool
If True, the handler function will receive any string
arguments as dbus.UTF8String objects (a subclass of str
guaranteed to be UTF-8). If False (default) it will receive
any string arguments as dbus.String objects (a subclass of
unicode).
`byte_arrays` : bool
If True, the handler function will receive any byte-array
arguments as dbus.ByteArray objects (a subclass of str).
If False (default) it will receive any byte-array
arguments as a dbus.Array of dbus.Byte (subclasses of:
a list of ints).
`sender_keyword` : str
If not None (the default), the handler function will receive
the unique name of the sending endpoint as a keyword
argument with this name
`destination_keyword` : str
If not None (the default), the handler function will receive
the bus name of the destination (or None if the signal is a
broadcast, as is usual) as a keyword argument with this name.
`interface_keyword` : str
If not None (the default), the handler function will receive
the signal interface as a keyword argument with this name.
`member_keyword` : str
If not None (the default), the handler function will receive
the signal name as a keyword argument with this name.
`path_keyword` : str
If not None (the default), the handler function will receive
the object-path of the sending object as a keyword argument
with this name
`message_keyword` : str
If not None (the default), the handler function will receive
the `dbus.lowlevel.SignalMessage` as a keyword argument with
this name.
`arg...` : unicode or UTF-8 str
If there are additional keyword parameters of the form
``arg``\ *n*, match only signals where the *n*\ th argument
is the value given for that keyword parameter. As of this time
only string arguments can be matched (in particular,
object paths and signatures can't).
"""
return \
self._bus.add_signal_receiver(handler_function,
signal_name=signal_name,
dbus_interface=dbus_interface,
named_service=self._named_service,
path=self.__dbus_object_path__,
**keywords)
def _Introspect(self):
message = _dbus_bindings.MethodCallMessage(None, self.__dbus_object_path__, 'org.freedesktop.DBus.Introspectable', 'Introspect')
message.set_destination(self._named_service)
result = self._bus.get_connection().send_message_with_reply(message, _ReplyHandler(self._introspect_reply_handler, self._introspect_error_handler, utf8_strings=True), -1)
return result
def _introspect_execute_queue(self):
for call in self._pending_introspect_queue:
(member, iface, args, keywords) = call
introspect_sig = None
tmp_iface = ''
if iface:
tmp_iface = iface + '.'
key = tmp_iface + '.' + member
if self._introspect_method_map.has_key (key):
introspect_sig = self._introspect_method_map[key]
call_object = self.ProxyMethodClass(self._bus.get_connection(),
self._named_service,
self.__dbus_object_path__,
iface,
member,
introspect_sig)
call_object(args, keywords)
def _introspect_reply_handler(self, data):
try:
self._introspect_method_map = process_introspection_data(data)
except IntrospectionParserException, e:
self._introspect_error_handler(e)
return
self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
#self._introspect_execute_queue()
def _introspect_error_handler(self, error):
self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
self._introspect_execute_queue()
sys.stderr.write("Introspect error: " + str(error) + "\n")
def __getattr__(self, member, dbus_interface=None):
if member == '__call__':
return object.__call__
elif member.startswith('__') and member.endswith('__'):
raise AttributeError(member)
else:
return self.get_dbus_method(member, dbus_interface)
def get_dbus_method(self, member, dbus_interface=None):
"""Return a proxy method representing the given D-Bus method. The
returned proxy method can be called in the usual way. For instance, ::
proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
is equivalent to::
proxy.Foo(123, dbus_interface='com.example.Bar')
or even::
getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
However, using `get_dbus_method` is the only way to call D-Bus
methods with certain awkward names - if the author of a service
implements a method called ``connect_to_signal`` or even
``__getattr__``, you'll need to use `get_dbus_method` to call them.
For services which follow the D-Bus convention of CamelCaseMethodNames
this won't be a problem.
"""
ret = self.ProxyMethodClass(self, self._bus.get_connection(),
self._named_service,
self.__dbus_object_path__, member,
dbus_interface)
if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
ret = self.DeferedMethodClass(ret)
return ret
def __repr__(self):
return '<ProxyObject wrapping %s %s %s at %#x>'%(
self._bus, self._named_service, self.__dbus_object_path__, id(self))
__str__ = __repr__
|