1 # Copyright (C) 2003, 2004, 2005, 2006, 2007 Red Hat Inc. <http://www.redhat.com/>
2 # Copyright (C) 2003 David Zeuthen
3 # Copyright (C) 2004 Rob Taylor
4 # Copyright (C) 2005, 2006, 2007 Collabora Ltd. <http://www.collabora.co.uk/>
6 # Licensed under the Academic Free License version 2.1
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 from threading
import RLock
28 from dummy_threading
import RLock
31 from dbus
._expat
_introspect
_parser
import process_introspection_data
32 from dbus
.exceptions
import MissingReplyHandlerException
, MissingErrorHandlerException
, IntrospectionParserException
, DBusException
34 __docformat__
= 'restructuredtext'
37 _logger
= logging
.getLogger('dbus.proxies')
39 from _dbus_bindings
import LOCAL_PATH
, \
40 BUS_DAEMON_NAME
, BUS_DAEMON_PATH
, BUS_DAEMON_IFACE
,\
44 class _DeferredMethod
:
45 """A proxy method which will only get called once we have its
48 def __init__(self
, proxy_method
, append
, block
):
49 self
._proxy
_method
= proxy_method
50 # the test suite relies on the existence of this property
51 self
._method
_name
= proxy_method
._method
_name
55 def __call__(self
, *args
, **keywords
):
56 if keywords
.has_key('reply_handler'):
57 # defer the async call til introspection finishes
58 self
._append
(self
._proxy
_method
, args
, keywords
)
61 # we're being synchronous, so block
63 return self
._proxy
_method
(*args
, **keywords
)
65 def call_async(self
, *args
, **keywords
):
66 self
._append
(self
._proxy
_method
, args
, keywords
)
72 Typically a member of a ProxyObject. Calls to the
73 method produce messages that travel over the Bus and are routed
74 to a specific named Service.
76 def __init__(self
, proxy
, connection
, bus_name
, object_path
, method_name
,
78 if object_path
== LOCAL_PATH
:
79 raise DBusException('Methods may not be called on the reserved '
80 'path %s' % LOCAL_PATH
)
82 # trust that the proxy, and the properties it had, are OK
84 self
._connection
= connection
85 self
._named
_service
= bus_name
86 self
._object
_path
= object_path
87 # fail early if the method name is bad
88 _dbus_bindings
.validate_member_name(method_name
)
89 # the test suite relies on the existence of this property
90 self
._method
_name
= method_name
91 # fail early if the interface name is bad
93 _dbus_bindings
.validate_interface_name(iface
)
94 self
._dbus
_interface
= iface
96 def __call__(self
, *args
, **keywords
):
97 reply_handler
= keywords
.pop('reply_handler', None)
98 error_handler
= keywords
.pop('error_handler', None)
99 ignore_reply
= keywords
.pop('ignore_reply', False)
101 if reply_handler
is not None or error_handler
is not None:
102 if reply_handler
is None:
103 raise MissingErrorHandlerException()
104 elif error_handler
is None:
105 raise MissingReplyHandlerException()
107 raise TypeError('ignore_reply and reply_handler cannot be '
110 dbus_interface
= keywords
.pop('dbus_interface', self
._dbus
_interface
)
112 if dbus_interface
is None:
113 key
= self
._method
_name
115 key
= dbus_interface
+ '.' + self
._method
_name
116 introspect_sig
= self
._proxy
._introspect
_method
_map
.get(key
, None)
118 if ignore_reply
or reply_handler
is not None:
119 self
._connection
.call_async(self
._named
_service
,
129 return self
._connection
.call_blocking(self
._named
_service
,
137 def call_async(self
, *args
, **keywords
):
138 reply_handler
= keywords
.pop('reply_handler', None)
139 error_handler
= keywords
.pop('error_handler', None)
141 dbus_interface
= keywords
.pop('dbus_interface', self
._dbus
_interface
)
144 key
= dbus_interface
+ '.' + self
._method
_name
146 key
= self
._method
_name
147 introspect_sig
= self
._proxy
._introspect
_method
_map
.get(key
, None)
149 self
._connection
.call_async(self
._named
_service
,
160 class ProxyObject(object):
161 """A proxy to the remote Object.
163 A ProxyObject is provided by the Bus. ProxyObjects
164 have member functions, and can be called like normal Python objects.
166 ProxyMethodClass
= _ProxyMethod
167 DeferredMethodClass
= _DeferredMethod
169 INTROSPECT_STATE_DONT_INTROSPECT
= 0
170 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
= 1
171 INTROSPECT_STATE_INTROSPECT_DONE
= 2
173 def __init__(self
, conn
=None, bus_name
=None, object_path
=None,
174 introspect
=True, follow_name_owner_changes
=False, **kwargs
):
175 """Initialize the proxy object.
178 `conn` : `dbus.connection.Connection`
179 The bus or connection on which to find this object.
180 The keyword argument `bus` is a deprecated alias for this.
182 A bus name for the application owning the object, to be used
183 as the destination for method calls and the sender for
184 signal matches. The keyword argument `named_service` is a
185 deprecated alias for this.
187 The object path at which the application exports the object
189 If true (default), attempt to introspect the remote
190 object to find out supported methods and their signatures
191 `follow_name_owner_changes` : bool
192 If true (default is false) and the `bus_name` is a
193 well-known name, follow ownership changes for that name
195 bus
= kwargs
.pop('bus', None)
198 raise TypeError('conn and bus cannot both be specified')
200 from warnings
import warn
201 warn('Passing the bus parameter to ProxyObject by name is '
202 'deprecated: please use positional parameters',
203 DeprecationWarning, stacklevel
=2)
204 named_service
= kwargs
.pop('named_service', None)
205 if named_service
is not None:
206 if bus_name
is not None:
207 raise TypeError('bus_name and named_service cannot both be '
209 bus_name
= named_service
210 from warnings
import warn
211 warn('Passing the named_service parameter to ProxyObject by name '
212 'is deprecated: please use positional parameters',
213 DeprecationWarning, stacklevel
=2)
215 raise TypeError('ProxyObject.__init__ does not take these '
216 'keyword arguments: %s'
217 % ', '.join(kwargs
.iterkeys()))
219 if follow_name_owner_changes
:
220 # we don't get the signals unless the Bus has a main loop
221 # XXX: using Bus internals
222 conn
._require
_main
_loop
()
226 if bus_name
is not None:
227 _dbus_bindings
.validate_bus_name(bus_name
)
228 # the attribute is still called _named_service for the moment,
229 # for the benefit of telepathy-python
230 self
._named
_service
= self
._requested
_bus
_name
= bus_name
232 _dbus_bindings
.validate_object_path(object_path
)
233 self
.__dbus
_object
_path
__ = object_path
235 if not follow_name_owner_changes
:
236 self
._named
_service
= conn
.activate_name_owner(bus_name
)
238 #PendingCall object for Introspect call
239 self
._pending
_introspect
= None
240 #queue of async calls waiting on the Introspect to return
241 self
._pending
_introspect
_queue
= []
242 #dictionary mapping method names to their input signatures
243 self
._introspect
_method
_map
= {}
245 # must be a recursive lock because block() is called while locked,
246 # and calls the callback which re-takes the lock
247 self
._introspect
_lock
= RLock()
249 if not introspect
or self
.__dbus
_object
_path
__ == LOCAL_PATH
:
250 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
252 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
254 self
._pending
_introspect
= self
._Introspect
()
256 bus_name
= property(lambda self
: self
._named
_service
, None, None,
257 """The bus name to which this proxy is bound. (Read-only,
260 If the proxy was instantiated using a unique name, this property
263 If the proxy was instantiated with a well-known name and with
264 `follow_name_owner_changes` set false (the default), this
265 property is the unique name of the connection that owned that
266 well-known name when the proxy was instantiated, which might
267 not actually own the requested well-known name any more.
269 If the proxy was instantiated with a well-known name and with
270 `follow_name_owner_changes` set true, this property is that
274 requested_bus_name
= property(lambda self
: self
._requested
_bus
_name
,
276 """The bus name which was requested when this proxy was
280 object_path
= property(lambda self
: self
.__dbus
_object
_path
__,
282 """The object-path of this proxy.""")
284 # XXX: We don't currently support this because it's the signal receiver
285 # that's responsible for tracking name owner changes, but it
286 # seems a natural thing to add in future.
287 #unique_bus_name = property(lambda self: something, None, None,
288 # """The unique name of the connection to which this proxy is
289 # currently bound. (Read-only, may change.)
292 def connect_to_signal(self
, signal_name
, handler_function
, dbus_interface
=None, **keywords
):
293 """Arrange for the given function to be called when the given signal
298 The name of the signal
299 `handler_function` : callable
300 A function to be called when the signal is emitted by
301 the remote object. Its positional arguments will be the
302 arguments of the signal; optionally, it may be given
303 keyword arguments as described below.
304 `dbus_interface` : str
305 Optional interface with which to qualify the signal name.
306 If None (the default) the handler will be called whenever a
307 signal of the given member name is received, whatever
310 `utf8_strings` : bool
311 If True, the handler function will receive any string
312 arguments as dbus.UTF8String objects (a subclass of str
313 guaranteed to be UTF-8). If False (default) it will receive
314 any string arguments as dbus.String objects (a subclass of
317 If True, the handler function will receive any byte-array
318 arguments as dbus.ByteArray objects (a subclass of str).
319 If False (default) it will receive any byte-array
320 arguments as a dbus.Array of dbus.Byte (subclasses of:
322 `sender_keyword` : str
323 If not None (the default), the handler function will receive
324 the unique name of the sending endpoint as a keyword
325 argument with this name
326 `destination_keyword` : str
327 If not None (the default), the handler function will receive
328 the bus name of the destination (or None if the signal is a
329 broadcast, as is usual) as a keyword argument with this name.
330 `interface_keyword` : str
331 If not None (the default), the handler function will receive
332 the signal interface as a keyword argument with this name.
333 `member_keyword` : str
334 If not None (the default), the handler function will receive
335 the signal name as a keyword argument with this name.
337 If not None (the default), the handler function will receive
338 the object-path of the sending object as a keyword argument
340 `message_keyword` : str
341 If not None (the default), the handler function will receive
342 the `dbus.lowlevel.SignalMessage` as a keyword argument with
344 `arg...` : unicode or UTF-8 str
345 If there are additional keyword parameters of the form
346 ``arg``\ *n*, match only signals where the *n*\ th argument
347 is the value given for that keyword parameter. As of this time
348 only string arguments can be matched (in particular,
349 object paths and signatures can't).
352 self
._bus
.add_signal_receiver(handler_function
,
353 signal_name
=signal_name
,
354 dbus_interface
=dbus_interface
,
355 bus_name
=self
._named
_service
,
356 path
=self
.__dbus
_object
_path
__,
359 def _Introspect(self
):
360 return self
._bus
.call_async(self
._named
_service
,
361 self
.__dbus
_object
_path
__,
362 INTROSPECTABLE_IFACE
, 'Introspect', '', (),
363 self
._introspect
_reply
_handler
,
364 self
._introspect
_error
_handler
,
366 require_main_loop
=False)
368 def _introspect_execute_queue(self
):
369 # FIXME: potential to flood the bus
370 # We should make sure mainloops all have idle handlers
371 # and do one message per idle
372 for (proxy_method
, args
, keywords
) in self
._pending
_introspect
_queue
:
373 proxy_method(*args
, **keywords
)
375 def _introspect_reply_handler(self
, data
):
376 self
._introspect
_lock
.acquire()
379 self
._introspect
_method
_map
= process_introspection_data(data
)
380 except IntrospectionParserException
, e
:
381 self
._introspect
_error
_handler
(e
)
384 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_DONE
385 self
._pending
_introspect
= None
386 self
._introspect
_execute
_queue
()
388 self
._introspect
_lock
.release()
390 def _introspect_error_handler(self
, error
):
391 self
._introspect
_lock
.acquire()
393 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
394 self
._pending
_introspect
= None
395 self
._introspect
_execute
_queue
()
396 sys
.stderr
.write("Introspect error: " + str(error
) + "\n")
398 self
._introspect
_lock
.release()
400 def _introspect_block(self
):
401 self
._introspect
_lock
.acquire()
403 if self
._pending
_introspect
is not None:
404 self
._pending
_introspect
.block()
405 # else someone still has a _DeferredMethod from before we
406 # finished introspection: no need to do anything special any more
408 self
._introspect
_lock
.release()
410 def _introspect_add_to_queue(self
, callback
, args
, kwargs
):
411 self
._introspect
_lock
.acquire()
413 if self
._introspect
_state
== self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
:
414 self
._pending
_introspect
_queue
.append((callback
, args
, kwargs
))
416 # someone still has a _DeferredMethod from before we
417 # finished introspection
418 callback(*args
, **kwargs
)
420 self
._introspect
_lock
.release()
422 def __getattr__(self
, member
):
423 if member
.startswith('__') and member
.endswith('__'):
424 raise AttributeError(member
)
426 return self
.get_dbus_method(member
)
428 def get_dbus_method(self
, member
, dbus_interface
=None):
429 """Return a proxy method representing the given D-Bus method. The
430 returned proxy method can be called in the usual way. For instance, ::
432 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
436 proxy.Foo(123, dbus_interface='com.example.Bar')
440 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
442 However, using `get_dbus_method` is the only way to call D-Bus
443 methods with certain awkward names - if the author of a service
444 implements a method called ``connect_to_signal`` or even
445 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
447 For services which follow the D-Bus convention of CamelCaseMethodNames
448 this won't be a problem.
451 ret
= self
.ProxyMethodClass(self
, self
._bus
,
453 self
.__dbus
_object
_path
__, member
,
456 # this can be done without taking the lock - the worst that can
457 # happen is that we accidentally return a _DeferredMethod just after
458 # finishing introspection, in which case _introspect_add_to_queue and
459 # _introspect_block will do the right thing anyway
460 if self
._introspect
_state
== self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
:
461 ret
= self
.DeferredMethodClass(ret
, self
._introspect
_add
_to
_queue
,
462 self
._introspect
_block
)
467 return '<ProxyObject wrapping %s %s %s at %#x>'%(
468 self
._bus
, self
._named
_service
, self
.__dbus
_object
_path
__, id(self
))
472 class Interface(object):
473 """An interface into a remote object.
475 An Interface can be used to wrap ProxyObjects
476 so that calls can be routed to their correct
480 def __init__(self
, object, dbus_interface
):
481 """Construct a proxy for the given interface on the given object.
484 `object` : `dbus.proxies.ProxyObject` or `dbus.Interface`
485 The remote object or another of its interfaces
486 `dbus_interface` : str
487 An interface the `object` implements
489 if isinstance(object, Interface
):
490 self
._obj
= object.proxy_object
493 self
._dbus
_interface
= dbus_interface
495 object_path
= property (lambda self
: self
._obj
.object_path
, None, None,
496 "The D-Bus object path of the underlying object")
497 __dbus_object_path__
= object_path
498 bus_name
= property (lambda self
: self
._obj
.bus_name
, None, None,
499 "The bus name to which the underlying proxy object "
501 requested_bus_name
= property (lambda self
: self
._obj
.requested_bus_name
,
503 "The bus name which was requested when the "
504 "underlying object was created")
505 proxy_object
= property (lambda self
: self
._obj
, None, None,
506 """The underlying proxy object""")
507 dbus_interface
= property (lambda self
: self
._dbus
_interface
, None, None,
508 """The D-Bus interface represented""")
510 def connect_to_signal(self
, signal_name
, handler_function
,
511 dbus_interface
=None, **keywords
):
512 """Arrange for a function to be called when the given signal is
515 The parameters and keyword arguments are the same as for
516 `dbus.proxies.ProxyObject.connect_to_signal`, except that if
517 `dbus_interface` is None (the default), the D-Bus interface that
518 was passed to the `Interface` constructor is used.
520 if not dbus_interface
:
521 dbus_interface
= self
._dbus
_interface
523 return self
._obj
.connect_to_signal(signal_name
, handler_function
,
524 dbus_interface
, **keywords
)
526 def __getattr__(self
, member
):
527 if member
.startswith('__') and member
.endswith('__'):
528 raise AttributeError(member
)
530 return self
._obj
.get_dbus_method(member
, self
._dbus
_interface
)
532 def get_dbus_method(self
, member
, dbus_interface
=None):
533 """Return a proxy method representing the given D-Bus method.
535 This is the same as `dbus.proxies.ProxyObject.get_dbus_method`
536 except that if `dbus_interface` is None (the default),
537 the D-Bus interface that was passed to the `Interface` constructor
540 if dbus_interface
is None:
541 dbus_interface
= self
._dbus
_interface
542 return self
._obj
.get_dbus_method(member
, dbus_interface
)
545 return '<Interface %r implementing %r at %#x>'%(
546 self
._obj
, self
._dbus
_interface
, id(self
))