1
# Copyright (C) 2003-2007 Red Hat Inc. <http://www.redhat.com/>
2
# Copyright (C) 2003 David Zeuthen
3
# Copyright (C) 2004 Rob Taylor
4
# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
6
# Permission is hereby granted, free of charge, to any person
7
# obtaining a copy of this software and associated documentation
8
# files (the "Software"), to deal in the Software without
9
# restriction, including without limitation the rights to use, copy,
10
# modify, merge, publish, distribute, sublicense, and/or sell copies
11
# of the Software, and to permit persons to whom the Software is
12
# furnished to do so, subject to the following conditions:
14
# The above copyright notice and this permission notice shall be
15
# included in all copies or substantial portions of the Software.
17
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24
# DEALINGS IN THE SOFTWARE.
30
from threading import RLock
32
from dummy_threading import RLock
35
from dbus._expat_introspect_parser import process_introspection_data
36
from dbus.exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException, DBusException
38
__docformat__ = 'restructuredtext'
41
_logger = logging.getLogger('dbus.proxies')
43
from _dbus_bindings import LOCAL_PATH, \
44
BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
48
class _DeferredMethod:
49
"""A proxy method which will only get called once we have its
52
def __init__(self, proxy_method, append, block):
53
self._proxy_method = proxy_method
54
# the test suite relies on the existence of this property
55
self._method_name = proxy_method._method_name
59
def __call__(self, *args, **keywords):
60
if (keywords.has_key('reply_handler') or
61
keywords.get('ignore_reply', False)):
62
# defer the async call til introspection finishes
63
self._append(self._proxy_method, args, keywords)
66
# we're being synchronous, so block
68
return self._proxy_method(*args, **keywords)
70
def call_async(self, *args, **keywords):
71
self._append(self._proxy_method, args, keywords)
77
Typically a member of a ProxyObject. Calls to the
78
method produce messages that travel over the Bus and are routed
79
to a specific named Service.
81
def __init__(self, proxy, connection, bus_name, object_path, method_name,
83
if object_path == LOCAL_PATH:
84
raise DBusException('Methods may not be called on the reserved '
85
'path %s' % LOCAL_PATH)
87
# trust that the proxy, and the properties it had, are OK
89
self._connection = connection
90
self._named_service = bus_name
91
self._object_path = object_path
92
# fail early if the method name is bad
93
_dbus_bindings.validate_member_name(method_name)
94
# the test suite relies on the existence of this property
95
self._method_name = method_name
96
# fail early if the interface name is bad
98
_dbus_bindings.validate_interface_name(iface)
99
self._dbus_interface = iface
101
def __call__(self, *args, **keywords):
102
reply_handler = keywords.pop('reply_handler', None)
103
error_handler = keywords.pop('error_handler', None)
104
ignore_reply = keywords.pop('ignore_reply', False)
105
signature = keywords.pop('signature', None)
107
if reply_handler is not None or error_handler is not None:
108
if reply_handler is None:
109
raise MissingReplyHandlerException()
110
elif error_handler is None:
111
raise MissingErrorHandlerException()
113
raise TypeError('ignore_reply and reply_handler cannot be '
116
dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
118
if signature is None:
119
if dbus_interface is None:
120
key = self._method_name
122
key = dbus_interface + '.' + self._method_name
124
signature = self._proxy._introspect_method_map.get(key, None)
126
if ignore_reply or reply_handler is not None:
127
self._connection.call_async(self._named_service,
137
return self._connection.call_blocking(self._named_service,
145
def call_async(self, *args, **keywords):
146
reply_handler = keywords.pop('reply_handler', None)
147
error_handler = keywords.pop('error_handler', None)
148
signature = keywords.pop('signature', None)
150
dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
152
if signature is None:
154
key = dbus_interface + '.' + self._method_name
156
key = self._method_name
157
signature = self._proxy._introspect_method_map.get(key, None)
159
self._connection.call_async(self._named_service,
170
class ProxyObject(object):
171
"""A proxy to the remote Object.
173
A ProxyObject is provided by the Bus. ProxyObjects
174
have member functions, and can be called like normal Python objects.
176
ProxyMethodClass = _ProxyMethod
177
DeferredMethodClass = _DeferredMethod
179
INTROSPECT_STATE_DONT_INTROSPECT = 0
180
INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
181
INTROSPECT_STATE_INTROSPECT_DONE = 2
183
def __init__(self, conn=None, bus_name=None, object_path=None,
184
introspect=True, follow_name_owner_changes=False, **kwargs):
185
"""Initialize the proxy object.
188
`conn` : `dbus.connection.Connection`
189
The bus or connection on which to find this object.
190
The keyword argument `bus` is a deprecated alias for this.
192
A bus name for the application owning the object, to be used
193
as the destination for method calls and the sender for
194
signal matches. The keyword argument ``named_service`` is a
195
deprecated alias for this.
197
The object path at which the application exports the object
199
If true (default), attempt to introspect the remote
200
object to find out supported methods and their signatures
201
`follow_name_owner_changes` : bool
202
If true (default is false) and the `bus_name` is a
203
well-known name, follow ownership changes for that name
205
bus = kwargs.pop('bus', None)
208
raise TypeError('conn and bus cannot both be specified')
210
from warnings import warn
211
warn('Passing the bus parameter to ProxyObject by name is '
212
'deprecated: please use positional parameters',
213
DeprecationWarning, stacklevel=2)
214
named_service = kwargs.pop('named_service', None)
215
if named_service is not None:
216
if bus_name is not None:
217
raise TypeError('bus_name and named_service cannot both be '
219
bus_name = named_service
220
from warnings import warn
221
warn('Passing the named_service parameter to ProxyObject by name '
222
'is deprecated: please use positional parameters',
223
DeprecationWarning, stacklevel=2)
225
raise TypeError('ProxyObject.__init__ does not take these '
226
'keyword arguments: %s'
227
% ', '.join(kwargs.iterkeys()))
229
if follow_name_owner_changes:
230
# we don't get the signals unless the Bus has a main loop
231
# XXX: using Bus internals
232
conn._require_main_loop()
236
if bus_name is not None:
237
_dbus_bindings.validate_bus_name(bus_name)
238
# the attribute is still called _named_service for the moment,
239
# for the benefit of telepathy-python
240
self._named_service = self._requested_bus_name = bus_name
242
_dbus_bindings.validate_object_path(object_path)
243
self.__dbus_object_path__ = object_path
245
if not follow_name_owner_changes:
246
self._named_service = conn.activate_name_owner(bus_name)
248
#PendingCall object for Introspect call
249
self._pending_introspect = None
250
#queue of async calls waiting on the Introspect to return
251
self._pending_introspect_queue = []
252
#dictionary mapping method names to their input signatures
253
self._introspect_method_map = {}
255
# must be a recursive lock because block() is called while locked,
256
# and calls the callback which re-takes the lock
257
self._introspect_lock = RLock()
259
if not introspect or self.__dbus_object_path__ == LOCAL_PATH:
260
self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
262
self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
264
self._pending_introspect = self._Introspect()
266
bus_name = property(lambda self: self._named_service, None, None,
267
"""The bus name to which this proxy is bound. (Read-only,
270
If the proxy was instantiated using a unique name, this property
273
If the proxy was instantiated with a well-known name and with
274
``follow_name_owner_changes`` set false (the default), this
275
property is the unique name of the connection that owned that
276
well-known name when the proxy was instantiated, which might
277
not actually own the requested well-known name any more.
279
If the proxy was instantiated with a well-known name and with
280
``follow_name_owner_changes`` set true, this property is that
284
requested_bus_name = property(lambda self: self._requested_bus_name,
286
"""The bus name which was requested when this proxy was
290
object_path = property(lambda self: self.__dbus_object_path__,
292
"""The object-path of this proxy.""")
294
# XXX: We don't currently support this because it's the signal receiver
295
# that's responsible for tracking name owner changes, but it
296
# seems a natural thing to add in future.
297
#unique_bus_name = property(lambda self: something, None, None,
298
# """The unique name of the connection to which this proxy is
299
# currently bound. (Read-only, may change.)
302
def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
303
"""Arrange for the given function to be called when the given signal
308
The name of the signal
309
`handler_function` : callable
310
A function to be called when the signal is emitted by
311
the remote object. Its positional arguments will be the
312
arguments of the signal; optionally, it may be given
313
keyword arguments as described below.
314
`dbus_interface` : str
315
Optional interface with which to qualify the signal name.
316
If None (the default) the handler will be called whenever a
317
signal of the given member name is received, whatever
320
`utf8_strings` : bool
321
If True, the handler function will receive any string
322
arguments as dbus.UTF8String objects (a subclass of str
323
guaranteed to be UTF-8). If False (default) it will receive
324
any string arguments as dbus.String objects (a subclass of
327
If True, the handler function will receive any byte-array
328
arguments as dbus.ByteArray objects (a subclass of str).
329
If False (default) it will receive any byte-array
330
arguments as a dbus.Array of dbus.Byte (subclasses of:
332
`sender_keyword` : str
333
If not None (the default), the handler function will receive
334
the unique name of the sending endpoint as a keyword
335
argument with this name
336
`destination_keyword` : str
337
If not None (the default), the handler function will receive
338
the bus name of the destination (or None if the signal is a
339
broadcast, as is usual) as a keyword argument with this name.
340
`interface_keyword` : str
341
If not None (the default), the handler function will receive
342
the signal interface as a keyword argument with this name.
343
`member_keyword` : str
344
If not None (the default), the handler function will receive
345
the signal name as a keyword argument with this name.
347
If not None (the default), the handler function will receive
348
the object-path of the sending object as a keyword argument
350
`message_keyword` : str
351
If not None (the default), the handler function will receive
352
the `dbus.lowlevel.SignalMessage` as a keyword argument with
354
`arg...` : unicode or UTF-8 str
355
If there are additional keyword parameters of the form
356
``arg``\ *n*, match only signals where the *n*\ th argument
357
is the value given for that keyword parameter. As of this time
358
only string arguments can be matched (in particular,
359
object paths and signatures can't).
362
self._bus.add_signal_receiver(handler_function,
363
signal_name=signal_name,
364
dbus_interface=dbus_interface,
365
bus_name=self._named_service,
366
path=self.__dbus_object_path__,
369
def _Introspect(self):
370
return self._bus.call_async(self._named_service,
371
self.__dbus_object_path__,
372
INTROSPECTABLE_IFACE, 'Introspect', '', (),
373
self._introspect_reply_handler,
374
self._introspect_error_handler,
376
require_main_loop=False)
378
def _introspect_execute_queue(self):
379
# FIXME: potential to flood the bus
380
# We should make sure mainloops all have idle handlers
381
# and do one message per idle
382
for (proxy_method, args, keywords) in self._pending_introspect_queue:
383
proxy_method(*args, **keywords)
384
self._pending_introspect_queue = []
386
def _introspect_reply_handler(self, data):
387
self._introspect_lock.acquire()
390
self._introspect_method_map = process_introspection_data(data)
391
except IntrospectionParserException as e:
392
self._introspect_error_handler(e)
395
self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
396
self._pending_introspect = None
397
self._introspect_execute_queue()
399
self._introspect_lock.release()
401
def _introspect_error_handler(self, error):
402
logging.basicConfig()
403
_logger.error("Introspect error on %s:%s: %s.%s: %s",
404
self._named_service, self.__dbus_object_path__,
405
error.__class__.__module__, error.__class__.__name__,
407
self._introspect_lock.acquire()
409
_logger.debug('Executing introspect queue due to error')
410
self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
411
self._pending_introspect = None
412
self._introspect_execute_queue()
414
self._introspect_lock.release()
416
def _introspect_block(self):
417
self._introspect_lock.acquire()
419
if self._pending_introspect is not None:
420
self._pending_introspect.block()
421
# else someone still has a _DeferredMethod from before we
422
# finished introspection: no need to do anything special any more
424
self._introspect_lock.release()
426
def _introspect_add_to_queue(self, callback, args, kwargs):
427
self._introspect_lock.acquire()
429
if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
430
self._pending_introspect_queue.append((callback, args, kwargs))
432
# someone still has a _DeferredMethod from before we
433
# finished introspection
434
callback(*args, **kwargs)
436
self._introspect_lock.release()
438
def __getattr__(self, member):
439
if member.startswith('__') and member.endswith('__'):
440
raise AttributeError(member)
442
return self.get_dbus_method(member)
444
def get_dbus_method(self, member, dbus_interface=None):
445
"""Return a proxy method representing the given D-Bus method. The
446
returned proxy method can be called in the usual way. For instance, ::
448
proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
452
proxy.Foo(123, dbus_interface='com.example.Bar')
456
getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
458
However, using `get_dbus_method` is the only way to call D-Bus
459
methods with certain awkward names - if the author of a service
460
implements a method called ``connect_to_signal`` or even
461
``__getattr__``, you'll need to use `get_dbus_method` to call them.
463
For services which follow the D-Bus convention of CamelCaseMethodNames
464
this won't be a problem.
467
ret = self.ProxyMethodClass(self, self._bus,
469
self.__dbus_object_path__, member,
472
# this can be done without taking the lock - the worst that can
473
# happen is that we accidentally return a _DeferredMethod just after
474
# finishing introspection, in which case _introspect_add_to_queue and
475
# _introspect_block will do the right thing anyway
476
if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
477
ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue,
478
self._introspect_block)
483
return '<ProxyObject wrapping %s %s %s at %#x>'%(
484
self._bus, self._named_service, self.__dbus_object_path__, id(self))
488
class Interface(object):
489
"""An interface into a remote object.
491
An Interface can be used to wrap ProxyObjects
492
so that calls can be routed to their correct
496
def __init__(self, object, dbus_interface):
497
"""Construct a proxy for the given interface on the given object.
500
`object` : `dbus.proxies.ProxyObject` or `dbus.Interface`
501
The remote object or another of its interfaces
502
`dbus_interface` : str
503
An interface the `object` implements
505
if isinstance(object, Interface):
506
self._obj = object.proxy_object
509
self._dbus_interface = dbus_interface
511
object_path = property (lambda self: self._obj.object_path, None, None,
512
"The D-Bus object path of the underlying object")
513
__dbus_object_path__ = object_path
514
bus_name = property (lambda self: self._obj.bus_name, None, None,
515
"The bus name to which the underlying proxy object "
517
requested_bus_name = property (lambda self: self._obj.requested_bus_name,
519
"The bus name which was requested when the "
520
"underlying object was created")
521
proxy_object = property (lambda self: self._obj, None, None,
522
"""The underlying proxy object""")
523
dbus_interface = property (lambda self: self._dbus_interface, None, None,
524
"""The D-Bus interface represented""")
526
def connect_to_signal(self, signal_name, handler_function,
527
dbus_interface=None, **keywords):
528
"""Arrange for a function to be called when the given signal is
531
The parameters and keyword arguments are the same as for
532
`dbus.proxies.ProxyObject.connect_to_signal`, except that if
533
`dbus_interface` is None (the default), the D-Bus interface that
534
was passed to the `Interface` constructor is used.
536
if not dbus_interface:
537
dbus_interface = self._dbus_interface
539
return self._obj.connect_to_signal(signal_name, handler_function,
540
dbus_interface, **keywords)
542
def __getattr__(self, member):
543
if member.startswith('__') and member.endswith('__'):
544
raise AttributeError(member)
546
return self._obj.get_dbus_method(member, self._dbus_interface)
548
def get_dbus_method(self, member, dbus_interface=None):
549
"""Return a proxy method representing the given D-Bus method.
551
This is the same as `dbus.proxies.ProxyObject.get_dbus_method`
552
except that if `dbus_interface` is None (the default),
553
the D-Bus interface that was passed to the `Interface` constructor
556
if dbus_interface is None:
557
dbus_interface = self._dbus_interface
558
return self._obj.get_dbus_method(member, dbus_interface)
561
return '<Interface %r implementing %r at %#x>'%(
562
self._obj, self._dbus_interface, id(self))