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') or
57 keywords
.get('ignore_reply', False)):
58 # defer the async call til introspection finishes
59 self
._append
(self
._proxy
_method
, args
, keywords
)
62 # we're being synchronous, so block
64 return self
._proxy
_method
(*args
, **keywords
)
66 def call_async(self
, *args
, **keywords
):
67 self
._append
(self
._proxy
_method
, args
, keywords
)
73 Typically a member of a ProxyObject. Calls to the
74 method produce messages that travel over the Bus and are routed
75 to a specific named Service.
77 def __init__(self
, proxy
, connection
, bus_name
, object_path
, method_name
,
79 if object_path
== LOCAL_PATH
:
80 raise DBusException('Methods may not be called on the reserved '
81 'path %s' % LOCAL_PATH
)
83 # trust that the proxy, and the properties it had, are OK
85 self
._connection
= connection
86 self
._named
_service
= bus_name
87 self
._object
_path
= object_path
88 # fail early if the method name is bad
89 _dbus_bindings
.validate_member_name(method_name
)
90 # the test suite relies on the existence of this property
91 self
._method
_name
= method_name
92 # fail early if the interface name is bad
94 _dbus_bindings
.validate_interface_name(iface
)
95 self
._dbus
_interface
= iface
97 def __call__(self
, *args
, **keywords
):
98 reply_handler
= keywords
.pop('reply_handler', None)
99 error_handler
= keywords
.pop('error_handler', None)
100 ignore_reply
= keywords
.pop('ignore_reply', False)
102 if reply_handler
is not None or error_handler
is not None:
103 if reply_handler
is None:
104 raise MissingErrorHandlerException()
105 elif error_handler
is None:
106 raise MissingReplyHandlerException()
108 raise TypeError('ignore_reply and reply_handler cannot be '
111 dbus_interface
= keywords
.pop('dbus_interface', self
._dbus
_interface
)
113 if dbus_interface
is None:
114 key
= self
._method
_name
116 key
= dbus_interface
+ '.' + self
._method
_name
117 introspect_sig
= self
._proxy
._introspect
_method
_map
.get(key
, None)
119 if ignore_reply
or reply_handler
is not None:
120 self
._connection
.call_async(self
._named
_service
,
130 return self
._connection
.call_blocking(self
._named
_service
,
138 def call_async(self
, *args
, **keywords
):
139 reply_handler
= keywords
.pop('reply_handler', None)
140 error_handler
= keywords
.pop('error_handler', None)
142 dbus_interface
= keywords
.pop('dbus_interface', self
._dbus
_interface
)
145 key
= dbus_interface
+ '.' + self
._method
_name
147 key
= self
._method
_name
148 introspect_sig
= self
._proxy
._introspect
_method
_map
.get(key
, None)
150 self
._connection
.call_async(self
._named
_service
,
161 class ProxyObject(object):
162 """A proxy to the remote Object.
164 A ProxyObject is provided by the Bus. ProxyObjects
165 have member functions, and can be called like normal Python objects.
167 ProxyMethodClass
= _ProxyMethod
168 DeferredMethodClass
= _DeferredMethod
170 INTROSPECT_STATE_DONT_INTROSPECT
= 0
171 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
= 1
172 INTROSPECT_STATE_INTROSPECT_DONE
= 2
174 def __init__(self
, conn
=None, bus_name
=None, object_path
=None,
175 introspect
=True, follow_name_owner_changes
=False, **kwargs
):
176 """Initialize the proxy object.
179 `conn` : `dbus.connection.Connection`
180 The bus or connection on which to find this object.
181 The keyword argument `bus` is a deprecated alias for this.
183 A bus name for the application owning the object, to be used
184 as the destination for method calls and the sender for
185 signal matches. The keyword argument ``named_service`` is a
186 deprecated alias for this.
188 The object path at which the application exports the object
190 If true (default), attempt to introspect the remote
191 object to find out supported methods and their signatures
192 `follow_name_owner_changes` : bool
193 If true (default is false) and the `bus_name` is a
194 well-known name, follow ownership changes for that name
196 bus
= kwargs
.pop('bus', None)
199 raise TypeError('conn and bus cannot both be specified')
201 from warnings
import warn
202 warn('Passing the bus parameter to ProxyObject by name is '
203 'deprecated: please use positional parameters',
204 DeprecationWarning, stacklevel
=2)
205 named_service
= kwargs
.pop('named_service', None)
206 if named_service
is not None:
207 if bus_name
is not None:
208 raise TypeError('bus_name and named_service cannot both be '
210 bus_name
= named_service
211 from warnings
import warn
212 warn('Passing the named_service parameter to ProxyObject by name '
213 'is deprecated: please use positional parameters',
214 DeprecationWarning, stacklevel
=2)
216 raise TypeError('ProxyObject.__init__ does not take these '
217 'keyword arguments: %s'
218 % ', '.join(kwargs
.iterkeys()))
220 if follow_name_owner_changes
:
221 # we don't get the signals unless the Bus has a main loop
222 # XXX: using Bus internals
223 conn
._require
_main
_loop
()
227 if bus_name
is not None:
228 _dbus_bindings
.validate_bus_name(bus_name
)
229 # the attribute is still called _named_service for the moment,
230 # for the benefit of telepathy-python
231 self
._named
_service
= self
._requested
_bus
_name
= bus_name
233 _dbus_bindings
.validate_object_path(object_path
)
234 self
.__dbus
_object
_path
__ = object_path
236 if not follow_name_owner_changes
:
237 self
._named
_service
= conn
.activate_name_owner(bus_name
)
239 #PendingCall object for Introspect call
240 self
._pending
_introspect
= None
241 #queue of async calls waiting on the Introspect to return
242 self
._pending
_introspect
_queue
= []
243 #dictionary mapping method names to their input signatures
244 self
._introspect
_method
_map
= {}
246 # must be a recursive lock because block() is called while locked,
247 # and calls the callback which re-takes the lock
248 self
._introspect
_lock
= RLock()
250 if not introspect
or self
.__dbus
_object
_path
__ == LOCAL_PATH
:
251 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
253 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
255 self
._pending
_introspect
= self
._Introspect
()
257 bus_name
= property(lambda self
: self
._named
_service
, None, None,
258 """The bus name to which this proxy is bound. (Read-only,
261 If the proxy was instantiated using a unique name, this property
264 If the proxy was instantiated with a well-known name and with
265 ``follow_name_owner_changes`` set false (the default), this
266 property is the unique name of the connection that owned that
267 well-known name when the proxy was instantiated, which might
268 not actually own the requested well-known name any more.
270 If the proxy was instantiated with a well-known name and with
271 ``follow_name_owner_changes`` set true, this property is that
275 requested_bus_name
= property(lambda self
: self
._requested
_bus
_name
,
277 """The bus name which was requested when this proxy was
281 object_path
= property(lambda self
: self
.__dbus
_object
_path
__,
283 """The object-path of this proxy.""")
285 # XXX: We don't currently support this because it's the signal receiver
286 # that's responsible for tracking name owner changes, but it
287 # seems a natural thing to add in future.
288 #unique_bus_name = property(lambda self: something, None, None,
289 # """The unique name of the connection to which this proxy is
290 # currently bound. (Read-only, may change.)
293 def connect_to_signal(self
, signal_name
, handler_function
, dbus_interface
=None, **keywords
):
294 """Arrange for the given function to be called when the given signal
299 The name of the signal
300 `handler_function` : callable
301 A function to be called when the signal is emitted by
302 the remote object. Its positional arguments will be the
303 arguments of the signal; optionally, it may be given
304 keyword arguments as described below.
305 `dbus_interface` : str
306 Optional interface with which to qualify the signal name.
307 If None (the default) the handler will be called whenever a
308 signal of the given member name is received, whatever
311 `utf8_strings` : bool
312 If True, the handler function will receive any string
313 arguments as dbus.UTF8String objects (a subclass of str
314 guaranteed to be UTF-8). If False (default) it will receive
315 any string arguments as dbus.String objects (a subclass of
318 If True, the handler function will receive any byte-array
319 arguments as dbus.ByteArray objects (a subclass of str).
320 If False (default) it will receive any byte-array
321 arguments as a dbus.Array of dbus.Byte (subclasses of:
323 `sender_keyword` : str
324 If not None (the default), the handler function will receive
325 the unique name of the sending endpoint as a keyword
326 argument with this name
327 `destination_keyword` : str
328 If not None (the default), the handler function will receive
329 the bus name of the destination (or None if the signal is a
330 broadcast, as is usual) as a keyword argument with this name.
331 `interface_keyword` : str
332 If not None (the default), the handler function will receive
333 the signal interface as a keyword argument with this name.
334 `member_keyword` : str
335 If not None (the default), the handler function will receive
336 the signal name as a keyword argument with this name.
338 If not None (the default), the handler function will receive
339 the object-path of the sending object as a keyword argument
341 `message_keyword` : str
342 If not None (the default), the handler function will receive
343 the `dbus.lowlevel.SignalMessage` as a keyword argument with
345 `arg...` : unicode or UTF-8 str
346 If there are additional keyword parameters of the form
347 ``arg``\ *n*, match only signals where the *n*\ th argument
348 is the value given for that keyword parameter. As of this time
349 only string arguments can be matched (in particular,
350 object paths and signatures can't).
353 self
._bus
.add_signal_receiver(handler_function
,
354 signal_name
=signal_name
,
355 dbus_interface
=dbus_interface
,
356 bus_name
=self
._named
_service
,
357 path
=self
.__dbus
_object
_path
__,
360 def _Introspect(self
):
361 return self
._bus
.call_async(self
._named
_service
,
362 self
.__dbus
_object
_path
__,
363 INTROSPECTABLE_IFACE
, 'Introspect', '', (),
364 self
._introspect
_reply
_handler
,
365 self
._introspect
_error
_handler
,
367 require_main_loop
=False)
369 def _introspect_execute_queue(self
):
370 # FIXME: potential to flood the bus
371 # We should make sure mainloops all have idle handlers
372 # and do one message per idle
373 for (proxy_method
, args
, keywords
) in self
._pending
_introspect
_queue
:
374 proxy_method(*args
, **keywords
)
376 def _introspect_reply_handler(self
, data
):
377 self
._introspect
_lock
.acquire()
380 self
._introspect
_method
_map
= process_introspection_data(data
)
381 except IntrospectionParserException
, e
:
382 self
._introspect
_error
_handler
(e
)
385 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_DONE
386 self
._pending
_introspect
= None
387 self
._introspect
_execute
_queue
()
389 self
._introspect
_lock
.release()
391 def _introspect_error_handler(self
, error
):
392 logging
.basicConfig()
393 _logger
.error("Introspect error on %s:%s: %s.%s: %s",
394 self
._named
_service
, self
.__dbus
_object
_path
__,
395 error
.__class
__.__module
__, error
.__class
__.__name
__,
397 self
._introspect
_lock
.acquire()
399 _logger
.debug('Executing introspect queue due to error')
400 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
401 self
._pending
_introspect
= None
402 self
._introspect
_execute
_queue
()
404 self
._introspect
_lock
.release()
406 def _introspect_block(self
):
407 self
._introspect
_lock
.acquire()
409 if self
._pending
_introspect
is not None:
410 self
._pending
_introspect
.block()
411 # else someone still has a _DeferredMethod from before we
412 # finished introspection: no need to do anything special any more
414 self
._introspect
_lock
.release()
416 def _introspect_add_to_queue(self
, callback
, args
, kwargs
):
417 self
._introspect
_lock
.acquire()
419 if self
._introspect
_state
== self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
:
420 self
._pending
_introspect
_queue
.append((callback
, args
, kwargs
))
422 # someone still has a _DeferredMethod from before we
423 # finished introspection
424 callback(*args
, **kwargs
)
426 self
._introspect
_lock
.release()
428 def __getattr__(self
, member
):
429 if member
.startswith('__') and member
.endswith('__'):
430 raise AttributeError(member
)
432 return self
.get_dbus_method(member
)
434 def get_dbus_method(self
, member
, dbus_interface
=None):
435 """Return a proxy method representing the given D-Bus method. The
436 returned proxy method can be called in the usual way. For instance, ::
438 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
442 proxy.Foo(123, dbus_interface='com.example.Bar')
446 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
448 However, using `get_dbus_method` is the only way to call D-Bus
449 methods with certain awkward names - if the author of a service
450 implements a method called ``connect_to_signal`` or even
451 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
453 For services which follow the D-Bus convention of CamelCaseMethodNames
454 this won't be a problem.
457 ret
= self
.ProxyMethodClass(self
, self
._bus
,
459 self
.__dbus
_object
_path
__, member
,
462 # this can be done without taking the lock - the worst that can
463 # happen is that we accidentally return a _DeferredMethod just after
464 # finishing introspection, in which case _introspect_add_to_queue and
465 # _introspect_block will do the right thing anyway
466 if self
._introspect
_state
== self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
:
467 ret
= self
.DeferredMethodClass(ret
, self
._introspect
_add
_to
_queue
,
468 self
._introspect
_block
)
473 return '<ProxyObject wrapping %s %s %s at %#x>'%(
474 self
._bus
, self
._named
_service
, self
.__dbus
_object
_path
__, id(self
))
478 class Interface(object):
479 """An interface into a remote object.
481 An Interface can be used to wrap ProxyObjects
482 so that calls can be routed to their correct
486 def __init__(self
, object, dbus_interface
):
487 """Construct a proxy for the given interface on the given object.
490 `object` : `dbus.proxies.ProxyObject` or `dbus.Interface`
491 The remote object or another of its interfaces
492 `dbus_interface` : str
493 An interface the `object` implements
495 if isinstance(object, Interface
):
496 self
._obj
= object.proxy_object
499 self
._dbus
_interface
= dbus_interface
501 object_path
= property (lambda self
: self
._obj
.object_path
, None, None,
502 "The D-Bus object path of the underlying object")
503 __dbus_object_path__
= object_path
504 bus_name
= property (lambda self
: self
._obj
.bus_name
, None, None,
505 "The bus name to which the underlying proxy object "
507 requested_bus_name
= property (lambda self
: self
._obj
.requested_bus_name
,
509 "The bus name which was requested when the "
510 "underlying object was created")
511 proxy_object
= property (lambda self
: self
._obj
, None, None,
512 """The underlying proxy object""")
513 dbus_interface
= property (lambda self
: self
._dbus
_interface
, None, None,
514 """The D-Bus interface represented""")
516 def connect_to_signal(self
, signal_name
, handler_function
,
517 dbus_interface
=None, **keywords
):
518 """Arrange for a function to be called when the given signal is
521 The parameters and keyword arguments are the same as for
522 `dbus.proxies.ProxyObject.connect_to_signal`, except that if
523 `dbus_interface` is None (the default), the D-Bus interface that
524 was passed to the `Interface` constructor is used.
526 if not dbus_interface
:
527 dbus_interface
= self
._dbus
_interface
529 return self
._obj
.connect_to_signal(signal_name
, handler_function
,
530 dbus_interface
, **keywords
)
532 def __getattr__(self
, member
):
533 if member
.startswith('__') and member
.endswith('__'):
534 raise AttributeError(member
)
536 return self
._obj
.get_dbus_method(member
, self
._dbus
_interface
)
538 def get_dbus_method(self
, member
, dbus_interface
=None):
539 """Return a proxy method representing the given D-Bus method.
541 This is the same as `dbus.proxies.ProxyObject.get_dbus_method`
542 except that if `dbus_interface` is None (the default),
543 the D-Bus interface that was passed to the `Interface` constructor
546 if dbus_interface
is None:
547 dbus_interface
= self
._dbus
_interface
548 return self
._obj
.get_dbus_method(member
, dbus_interface
)
551 return '<Interface %r implementing %r at %#x>'%(
552 self
._obj
, self
._dbus
_interface
, id(self
))