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)
106 if reply_handler
is not None or error_handler
is not None:
107 if reply_handler
is None:
108 raise MissingReplyHandlerException()
109 elif error_handler
is None:
110 raise MissingErrorHandlerException()
112 raise TypeError('ignore_reply and reply_handler cannot be '
115 dbus_interface
= keywords
.pop('dbus_interface', self
._dbus
_interface
)
117 if dbus_interface
is None:
118 key
= self
._method
_name
120 key
= dbus_interface
+ '.' + self
._method
_name
121 introspect_sig
= self
._proxy
._introspect
_method
_map
.get(key
, None)
123 if ignore_reply
or reply_handler
is not None:
124 self
._connection
.call_async(self
._named
_service
,
134 return self
._connection
.call_blocking(self
._named
_service
,
142 def call_async(self
, *args
, **keywords
):
143 reply_handler
= keywords
.pop('reply_handler', None)
144 error_handler
= keywords
.pop('error_handler', None)
146 dbus_interface
= keywords
.pop('dbus_interface', self
._dbus
_interface
)
149 key
= dbus_interface
+ '.' + self
._method
_name
151 key
= self
._method
_name
152 introspect_sig
= self
._proxy
._introspect
_method
_map
.get(key
, None)
154 self
._connection
.call_async(self
._named
_service
,
165 class ProxyObject(object):
166 """A proxy to the remote Object.
168 A ProxyObject is provided by the Bus. ProxyObjects
169 have member functions, and can be called like normal Python objects.
171 ProxyMethodClass
= _ProxyMethod
172 DeferredMethodClass
= _DeferredMethod
174 INTROSPECT_STATE_DONT_INTROSPECT
= 0
175 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
= 1
176 INTROSPECT_STATE_INTROSPECT_DONE
= 2
178 def __init__(self
, conn
=None, bus_name
=None, object_path
=None,
179 introspect
=True, follow_name_owner_changes
=False, **kwargs
):
180 """Initialize the proxy object.
183 `conn` : `dbus.connection.Connection`
184 The bus or connection on which to find this object.
185 The keyword argument `bus` is a deprecated alias for this.
187 A bus name for the application owning the object, to be used
188 as the destination for method calls and the sender for
189 signal matches. The keyword argument ``named_service`` is a
190 deprecated alias for this.
192 The object path at which the application exports the object
194 If true (default), attempt to introspect the remote
195 object to find out supported methods and their signatures
196 `follow_name_owner_changes` : bool
197 If true (default is false) and the `bus_name` is a
198 well-known name, follow ownership changes for that name
200 bus
= kwargs
.pop('bus', None)
203 raise TypeError('conn and bus cannot both be specified')
205 from warnings
import warn
206 warn('Passing the bus parameter to ProxyObject by name is '
207 'deprecated: please use positional parameters',
208 DeprecationWarning, stacklevel
=2)
209 named_service
= kwargs
.pop('named_service', None)
210 if named_service
is not None:
211 if bus_name
is not None:
212 raise TypeError('bus_name and named_service cannot both be '
214 bus_name
= named_service
215 from warnings
import warn
216 warn('Passing the named_service parameter to ProxyObject by name '
217 'is deprecated: please use positional parameters',
218 DeprecationWarning, stacklevel
=2)
220 raise TypeError('ProxyObject.__init__ does not take these '
221 'keyword arguments: %s'
222 % ', '.join(kwargs
.iterkeys()))
224 if follow_name_owner_changes
:
225 # we don't get the signals unless the Bus has a main loop
226 # XXX: using Bus internals
227 conn
._require
_main
_loop
()
231 if bus_name
is not None:
232 _dbus_bindings
.validate_bus_name(bus_name
)
233 # the attribute is still called _named_service for the moment,
234 # for the benefit of telepathy-python
235 self
._named
_service
= self
._requested
_bus
_name
= bus_name
237 _dbus_bindings
.validate_object_path(object_path
)
238 self
.__dbus
_object
_path
__ = object_path
240 if not follow_name_owner_changes
:
241 self
._named
_service
= conn
.activate_name_owner(bus_name
)
243 #PendingCall object for Introspect call
244 self
._pending
_introspect
= None
245 #queue of async calls waiting on the Introspect to return
246 self
._pending
_introspect
_queue
= []
247 #dictionary mapping method names to their input signatures
248 self
._introspect
_method
_map
= {}
250 # must be a recursive lock because block() is called while locked,
251 # and calls the callback which re-takes the lock
252 self
._introspect
_lock
= RLock()
254 if not introspect
or self
.__dbus
_object
_path
__ == LOCAL_PATH
:
255 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
257 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
259 self
._pending
_introspect
= self
._Introspect
()
261 bus_name
= property(lambda self
: self
._named
_service
, None, None,
262 """The bus name to which this proxy is bound. (Read-only,
265 If the proxy was instantiated using a unique name, this property
268 If the proxy was instantiated with a well-known name and with
269 ``follow_name_owner_changes`` set false (the default), this
270 property is the unique name of the connection that owned that
271 well-known name when the proxy was instantiated, which might
272 not actually own the requested well-known name any more.
274 If the proxy was instantiated with a well-known name and with
275 ``follow_name_owner_changes`` set true, this property is that
279 requested_bus_name
= property(lambda self
: self
._requested
_bus
_name
,
281 """The bus name which was requested when this proxy was
285 object_path
= property(lambda self
: self
.__dbus
_object
_path
__,
287 """The object-path of this proxy.""")
289 # XXX: We don't currently support this because it's the signal receiver
290 # that's responsible for tracking name owner changes, but it
291 # seems a natural thing to add in future.
292 #unique_bus_name = property(lambda self: something, None, None,
293 # """The unique name of the connection to which this proxy is
294 # currently bound. (Read-only, may change.)
297 def connect_to_signal(self
, signal_name
, handler_function
, dbus_interface
=None, **keywords
):
298 """Arrange for the given function to be called when the given signal
303 The name of the signal
304 `handler_function` : callable
305 A function to be called when the signal is emitted by
306 the remote object. Its positional arguments will be the
307 arguments of the signal; optionally, it may be given
308 keyword arguments as described below.
309 `dbus_interface` : str
310 Optional interface with which to qualify the signal name.
311 If None (the default) the handler will be called whenever a
312 signal of the given member name is received, whatever
315 `utf8_strings` : bool
316 If True, the handler function will receive any string
317 arguments as dbus.UTF8String objects (a subclass of str
318 guaranteed to be UTF-8). If False (default) it will receive
319 any string arguments as dbus.String objects (a subclass of
322 If True, the handler function will receive any byte-array
323 arguments as dbus.ByteArray objects (a subclass of str).
324 If False (default) it will receive any byte-array
325 arguments as a dbus.Array of dbus.Byte (subclasses of:
327 `sender_keyword` : str
328 If not None (the default), the handler function will receive
329 the unique name of the sending endpoint as a keyword
330 argument with this name
331 `destination_keyword` : str
332 If not None (the default), the handler function will receive
333 the bus name of the destination (or None if the signal is a
334 broadcast, as is usual) as a keyword argument with this name.
335 `interface_keyword` : str
336 If not None (the default), the handler function will receive
337 the signal interface as a keyword argument with this name.
338 `member_keyword` : str
339 If not None (the default), the handler function will receive
340 the signal name as a keyword argument with this name.
342 If not None (the default), the handler function will receive
343 the object-path of the sending object as a keyword argument
345 `message_keyword` : str
346 If not None (the default), the handler function will receive
347 the `dbus.lowlevel.SignalMessage` as a keyword argument with
349 `arg...` : unicode or UTF-8 str
350 If there are additional keyword parameters of the form
351 ``arg``\ *n*, match only signals where the *n*\ th argument
352 is the value given for that keyword parameter. As of this time
353 only string arguments can be matched (in particular,
354 object paths and signatures can't).
357 self
._bus
.add_signal_receiver(handler_function
,
358 signal_name
=signal_name
,
359 dbus_interface
=dbus_interface
,
360 bus_name
=self
._named
_service
,
361 path
=self
.__dbus
_object
_path
__,
364 def _Introspect(self
):
365 return self
._bus
.call_async(self
._named
_service
,
366 self
.__dbus
_object
_path
__,
367 INTROSPECTABLE_IFACE
, 'Introspect', '', (),
368 self
._introspect
_reply
_handler
,
369 self
._introspect
_error
_handler
,
371 require_main_loop
=False)
373 def _introspect_execute_queue(self
):
374 # FIXME: potential to flood the bus
375 # We should make sure mainloops all have idle handlers
376 # and do one message per idle
377 for (proxy_method
, args
, keywords
) in self
._pending
_introspect
_queue
:
378 proxy_method(*args
, **keywords
)
380 def _introspect_reply_handler(self
, data
):
381 self
._introspect
_lock
.acquire()
384 self
._introspect
_method
_map
= process_introspection_data(data
)
385 except IntrospectionParserException
, e
:
386 self
._introspect
_error
_handler
(e
)
389 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_DONE
390 self
._pending
_introspect
= None
391 self
._introspect
_execute
_queue
()
393 self
._introspect
_lock
.release()
395 def _introspect_error_handler(self
, error
):
396 logging
.basicConfig()
397 _logger
.error("Introspect error on %s:%s: %s.%s: %s",
398 self
._named
_service
, self
.__dbus
_object
_path
__,
399 error
.__class
__.__module
__, error
.__class
__.__name
__,
401 self
._introspect
_lock
.acquire()
403 _logger
.debug('Executing introspect queue due to error')
404 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
405 self
._pending
_introspect
= None
406 self
._introspect
_execute
_queue
()
408 self
._introspect
_lock
.release()
410 def _introspect_block(self
):
411 self
._introspect
_lock
.acquire()
413 if self
._pending
_introspect
is not None:
414 self
._pending
_introspect
.block()
415 # else someone still has a _DeferredMethod from before we
416 # finished introspection: no need to do anything special any more
418 self
._introspect
_lock
.release()
420 def _introspect_add_to_queue(self
, callback
, args
, kwargs
):
421 self
._introspect
_lock
.acquire()
423 if self
._introspect
_state
== self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
:
424 self
._pending
_introspect
_queue
.append((callback
, args
, kwargs
))
426 # someone still has a _DeferredMethod from before we
427 # finished introspection
428 callback(*args
, **kwargs
)
430 self
._introspect
_lock
.release()
432 def __getattr__(self
, member
):
433 if member
.startswith('__') and member
.endswith('__'):
434 raise AttributeError(member
)
436 return self
.get_dbus_method(member
)
438 def get_dbus_method(self
, member
, dbus_interface
=None):
439 """Return a proxy method representing the given D-Bus method. The
440 returned proxy method can be called in the usual way. For instance, ::
442 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
446 proxy.Foo(123, dbus_interface='com.example.Bar')
450 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
452 However, using `get_dbus_method` is the only way to call D-Bus
453 methods with certain awkward names - if the author of a service
454 implements a method called ``connect_to_signal`` or even
455 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
457 For services which follow the D-Bus convention of CamelCaseMethodNames
458 this won't be a problem.
461 ret
= self
.ProxyMethodClass(self
, self
._bus
,
463 self
.__dbus
_object
_path
__, member
,
466 # this can be done without taking the lock - the worst that can
467 # happen is that we accidentally return a _DeferredMethod just after
468 # finishing introspection, in which case _introspect_add_to_queue and
469 # _introspect_block will do the right thing anyway
470 if self
._introspect
_state
== self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
:
471 ret
= self
.DeferredMethodClass(ret
, self
._introspect
_add
_to
_queue
,
472 self
._introspect
_block
)
477 return '<ProxyObject wrapping %s %s %s at %#x>'%(
478 self
._bus
, self
._named
_service
, self
.__dbus
_object
_path
__, id(self
))
482 class Interface(object):
483 """An interface into a remote object.
485 An Interface can be used to wrap ProxyObjects
486 so that calls can be routed to their correct
490 def __init__(self
, object, dbus_interface
):
491 """Construct a proxy for the given interface on the given object.
494 `object` : `dbus.proxies.ProxyObject` or `dbus.Interface`
495 The remote object or another of its interfaces
496 `dbus_interface` : str
497 An interface the `object` implements
499 if isinstance(object, Interface
):
500 self
._obj
= object.proxy_object
503 self
._dbus
_interface
= dbus_interface
505 object_path
= property (lambda self
: self
._obj
.object_path
, None, None,
506 "The D-Bus object path of the underlying object")
507 __dbus_object_path__
= object_path
508 bus_name
= property (lambda self
: self
._obj
.bus_name
, None, None,
509 "The bus name to which the underlying proxy object "
511 requested_bus_name
= property (lambda self
: self
._obj
.requested_bus_name
,
513 "The bus name which was requested when the "
514 "underlying object was created")
515 proxy_object
= property (lambda self
: self
._obj
, None, None,
516 """The underlying proxy object""")
517 dbus_interface
= property (lambda self
: self
._dbus
_interface
, None, None,
518 """The D-Bus interface represented""")
520 def connect_to_signal(self
, signal_name
, handler_function
,
521 dbus_interface
=None, **keywords
):
522 """Arrange for a function to be called when the given signal is
525 The parameters and keyword arguments are the same as for
526 `dbus.proxies.ProxyObject.connect_to_signal`, except that if
527 `dbus_interface` is None (the default), the D-Bus interface that
528 was passed to the `Interface` constructor is used.
530 if not dbus_interface
:
531 dbus_interface
= self
._dbus
_interface
533 return self
._obj
.connect_to_signal(signal_name
, handler_function
,
534 dbus_interface
, **keywords
)
536 def __getattr__(self
, member
):
537 if member
.startswith('__') and member
.endswith('__'):
538 raise AttributeError(member
)
540 return self
._obj
.get_dbus_method(member
, self
._dbus
_interface
)
542 def get_dbus_method(self
, member
, dbus_interface
=None):
543 """Return a proxy method representing the given D-Bus method.
545 This is the same as `dbus.proxies.ProxyObject.get_dbus_method`
546 except that if `dbus_interface` is None (the default),
547 the D-Bus interface that was passed to the `Interface` constructor
550 if dbus_interface
is None:
551 dbus_interface
= self
._dbus
_interface
552 return self
._obj
.get_dbus_method(member
, dbus_interface
)
555 return '<Interface %r implementing %r at %#x>'%(
556 self
._obj
, self
._dbus
_interface
, id(self
))