dbus/proxies.py: get INTROSPECTABLE_IFACE from _dbus_bindings
[dbus-python-phuang.git] / dbus / proxies.py
blobe2a1aed7f5c929120434d7ed6122c0e43f0ea008
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
22 import sys
23 import logging
25 try:
26 from threading import RLock
27 except ImportError:
28 from dummy_threading import RLock
30 import _dbus_bindings
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,\
41 INTROSPECTABLE_IFACE
44 class _DeferredMethod:
45 """A proxy method which will only get called once we have its
46 introspection reply.
47 """
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
52 self._append = append
53 self._block = block
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)
59 return None
60 else:
61 # we're being synchronous, so block
62 self._block()
63 return self._proxy_method(*args, **keywords)
65 def call_async(self, *args, **keywords):
66 self._append(self._proxy_method, args, keywords)
69 class _ProxyMethod:
70 """A proxy method.
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.
75 """
76 def __init__(self, proxy, connection, named_service, object_path, method_name, iface):
77 if object_path == LOCAL_PATH:
78 raise DBusException('Methods may not be called on the reserved '
79 'path %s' % LOCAL_PATH)
81 # trust that the proxy, and the properties it had, are OK
82 self._proxy = proxy
83 self._connection = connection
84 self._named_service = named_service
85 self._object_path = object_path
86 # fail early if the method name is bad
87 _dbus_bindings.validate_member_name(method_name)
88 # the test suite relies on the existence of this property
89 self._method_name = method_name
90 # fail early if the interface name is bad
91 if iface is not None:
92 _dbus_bindings.validate_interface_name(iface)
93 self._dbus_interface = iface
95 def __call__(self, *args, **keywords):
96 reply_handler = keywords.pop('reply_handler', None)
97 error_handler = keywords.pop('error_handler', None)
98 ignore_reply = keywords.pop('ignore_reply', False)
100 if reply_handler is not None or error_handler is not None:
101 if reply_handler is None:
102 raise MissingErrorHandlerException()
103 elif error_handler is None:
104 raise MissingReplyHandlerException()
105 elif ignore_reply:
106 raise TypeError('ignore_reply and reply_handler cannot be '
107 'used together')
109 dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
111 if dbus_interface is None:
112 key = self._method_name
113 else:
114 key = dbus_interface + '.' + self._method_name
115 introspect_sig = self._proxy._introspect_method_map.get(key, None)
117 if ignore_reply or reply_handler is not None:
118 self._connection.call_async(self._named_service,
119 self._object_path,
120 dbus_interface,
121 self._method_name,
122 introspect_sig,
123 args,
124 reply_handler,
125 error_handler,
126 **keywords)
127 else:
128 return self._connection.call_blocking(self._named_service,
129 self._object_path,
130 dbus_interface,
131 self._method_name,
132 introspect_sig,
133 args,
134 **keywords)
136 def call_async(self, *args, **keywords):
137 reply_handler = keywords.pop('reply_handler', None)
138 error_handler = keywords.pop('error_handler', None)
140 dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
142 if dbus_interface:
143 key = dbus_interface + '.' + self._method_name
144 else:
145 key = self._method_name
146 introspect_sig = self._proxy._introspect_method_map.get(key, None)
148 self._connection.call_async(self._named_service,
149 self._object_path,
150 dbus_interface,
151 self._method_name,
152 introspect_sig,
153 args,
154 reply_handler,
155 error_handler,
156 **keywords)
159 class ProxyObject(object):
160 """A proxy to the remote Object.
162 A ProxyObject is provided by the Bus. ProxyObjects
163 have member functions, and can be called like normal Python objects.
165 ProxyMethodClass = _ProxyMethod
166 DeferredMethodClass = _DeferredMethod
168 INTROSPECT_STATE_DONT_INTROSPECT = 0
169 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
170 INTROSPECT_STATE_INTROSPECT_DONE = 2
172 def __init__(self, bus, named_service, object_path, introspect=True,
173 follow_name_owner_changes=False):
174 """Initialize the proxy object.
176 :Parameters:
177 `bus` : `dbus.Bus`
178 The bus on which to find this object
179 `named_service` : str
180 A bus name for the endpoint owning the object (need not
181 actually be a service name)
182 `object_path` : str
183 The object path at which the endpoint exports the object
184 `introspect` : bool
185 If true (default), attempt to introspect the remote
186 object to find out supported methods and their signatures
187 `follow_name_owner_changes` : bool
188 If true (default is false) and the `named_service` is a
189 well-known name, follow ownership changes for that name
191 if follow_name_owner_changes:
192 # we don't get the signals unless the Bus has a main loop
193 # XXX: using Bus internals
194 bus._require_main_loop()
196 self._bus = bus
198 if named_service is not None:
199 _dbus_bindings.validate_bus_name(named_service)
200 self._named_service = self._requested_bus_name = named_service
202 _dbus_bindings.validate_object_path(object_path)
203 self.__dbus_object_path__ = object_path
205 # XXX: assumes it's a bus daemon
206 if (named_service is not None and named_service[:1] != ':'
207 and named_service != BUS_DAEMON_NAME
208 and not follow_name_owner_changes):
209 bus_object = bus.get_object(BUS_DAEMON_NAME, BUS_DAEMON_PATH)
210 try:
211 self._named_service = bus_object.GetNameOwner(named_service,
212 dbus_interface=BUS_DAEMON_IFACE)
213 except DBusException, e:
214 # FIXME: detect whether it's NameHasNoOwner, but properly
215 #if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
216 # raise
217 # it might not exist: try to start it
218 bus_object.StartServiceByName(named_service,
219 _dbus_bindings.UInt32(0))
220 self._named_service = bus_object.GetNameOwner(named_service,
221 dbus_interface=BUS_DAEMON_IFACE)
223 #PendingCall object for Introspect call
224 self._pending_introspect = None
225 #queue of async calls waiting on the Introspect to return
226 self._pending_introspect_queue = []
227 #dictionary mapping method names to their input signatures
228 self._introspect_method_map = {}
230 # must be a recursive lock because block() is called while locked,
231 # and calls the callback which re-takes the lock
232 self._introspect_lock = RLock()
234 if not introspect or self.__dbus_object_path__ == LOCAL_PATH:
235 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
236 else:
237 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
239 self._pending_introspect = self._Introspect()
241 bus_name = property(lambda self: self._named_service, None, None,
242 """The bus name to which this proxy is bound. (Read-only,
243 may change.)
245 If the proxy was instantiated using a unique name, this property
246 is that unique name.
248 If the proxy was instantiated with a well-known name and with
249 `follow_name_owner_changes` set false (the default), this
250 property is the unique name of the connection that owned that
251 well-known name when the proxy was instantiated, which might
252 not actually own the requested well-known name any more.
254 If the proxy was instantiated with a well-known name and with
255 `follow_name_owner_changes` set true, this property is that
256 well-known name.
257 """)
259 requested_bus_name = property(lambda self: self._requested_bus_name,
260 None, None,
261 """The bus name which was requested when this proxy was
262 instantiated.
263 """)
265 object_path = property(lambda self: self.__dbus_object_path__,
266 None, None,
267 """The object-path of this proxy.""")
269 # XXX: We don't currently support this because it's the signal receiver
270 # that's responsible for tracking name owner changes, but it
271 # seems a natural thing to add in future.
272 #unique_bus_name = property(lambda self: something, None, None,
273 # """The unique name of the connection to which this proxy is
274 # currently bound. (Read-only, may change.)
275 # """)
277 def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
278 """Arrange for the given function to be called when the given signal
279 is received.
281 :Parameters:
282 `signal_name` : str
283 The name of the signal
284 `handler_function` : callable
285 A function to be called when the signal is emitted by
286 the remote object. Its positional arguments will be the
287 arguments of the signal; optionally, it may be given
288 keyword arguments as described below.
289 `dbus_interface` : str
290 Optional interface with which to qualify the signal name.
291 If None (the default) the handler will be called whenever a
292 signal of the given member name is received, whatever
293 its interface.
294 :Keywords:
295 `utf8_strings` : bool
296 If True, the handler function will receive any string
297 arguments as dbus.UTF8String objects (a subclass of str
298 guaranteed to be UTF-8). If False (default) it will receive
299 any string arguments as dbus.String objects (a subclass of
300 unicode).
301 `byte_arrays` : bool
302 If True, the handler function will receive any byte-array
303 arguments as dbus.ByteArray objects (a subclass of str).
304 If False (default) it will receive any byte-array
305 arguments as a dbus.Array of dbus.Byte (subclasses of:
306 a list of ints).
307 `sender_keyword` : str
308 If not None (the default), the handler function will receive
309 the unique name of the sending endpoint as a keyword
310 argument with this name
311 `destination_keyword` : str
312 If not None (the default), the handler function will receive
313 the bus name of the destination (or None if the signal is a
314 broadcast, as is usual) as a keyword argument with this name.
315 `interface_keyword` : str
316 If not None (the default), the handler function will receive
317 the signal interface as a keyword argument with this name.
318 `member_keyword` : str
319 If not None (the default), the handler function will receive
320 the signal name as a keyword argument with this name.
321 `path_keyword` : str
322 If not None (the default), the handler function will receive
323 the object-path of the sending object as a keyword argument
324 with this name
325 `message_keyword` : str
326 If not None (the default), the handler function will receive
327 the `dbus.lowlevel.SignalMessage` as a keyword argument with
328 this name.
329 `arg...` : unicode or UTF-8 str
330 If there are additional keyword parameters of the form
331 ``arg``\ *n*, match only signals where the *n*\ th argument
332 is the value given for that keyword parameter. As of this time
333 only string arguments can be matched (in particular,
334 object paths and signatures can't).
336 return \
337 self._bus.add_signal_receiver(handler_function,
338 signal_name=signal_name,
339 dbus_interface=dbus_interface,
340 named_service=self._named_service,
341 path=self.__dbus_object_path__,
342 **keywords)
344 def _Introspect(self):
345 return self._bus.call_async(self._named_service,
346 self.__dbus_object_path__,
347 INTROSPECTABLE_IFACE, 'Introspect', '', (),
348 self._introspect_reply_handler,
349 self._introspect_error_handler,
350 utf8_strings=True,
351 require_main_loop=False)
353 def _introspect_execute_queue(self):
354 # FIXME: potential to flood the bus
355 # We should make sure mainloops all have idle handlers
356 # and do one message per idle
357 for (proxy_method, args, keywords) in self._pending_introspect_queue:
358 proxy_method(*args, **keywords)
360 def _introspect_reply_handler(self, data):
361 self._introspect_lock.acquire()
362 try:
363 try:
364 self._introspect_method_map = process_introspection_data(data)
365 except IntrospectionParserException, e:
366 self._introspect_error_handler(e)
367 return
369 self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
370 self._pending_introspect = None
371 self._introspect_execute_queue()
372 finally:
373 self._introspect_lock.release()
375 def _introspect_error_handler(self, error):
376 self._introspect_lock.acquire()
377 try:
378 self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
379 self._pending_introspect = None
380 self._introspect_execute_queue()
381 sys.stderr.write("Introspect error: " + str(error) + "\n")
382 finally:
383 self._introspect_lock.release()
385 def _introspect_block(self):
386 self._introspect_lock.acquire()
387 try:
388 if self._pending_introspect is not None:
389 self._pending_introspect.block()
390 # else someone still has a _DeferredMethod from before we
391 # finished introspection: no need to do anything special any more
392 finally:
393 self._introspect_lock.release()
395 def _introspect_add_to_queue(self, callback, args, kwargs):
396 self._introspect_lock.acquire()
397 try:
398 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
399 self._pending_introspect_queue.append((callback, args, kwargs))
400 else:
401 # someone still has a _DeferredMethod from before we
402 # finished introspection
403 callback(*args, **kwargs)
404 finally:
405 self._introspect_lock.release()
407 def __getattr__(self, member):
408 if member.startswith('__') and member.endswith('__'):
409 raise AttributeError(member)
410 else:
411 return self.get_dbus_method(member)
413 def get_dbus_method(self, member, dbus_interface=None):
414 """Return a proxy method representing the given D-Bus method. The
415 returned proxy method can be called in the usual way. For instance, ::
417 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
419 is equivalent to::
421 proxy.Foo(123, dbus_interface='com.example.Bar')
423 or even::
425 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
427 However, using `get_dbus_method` is the only way to call D-Bus
428 methods with certain awkward names - if the author of a service
429 implements a method called ``connect_to_signal`` or even
430 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
432 For services which follow the D-Bus convention of CamelCaseMethodNames
433 this won't be a problem.
436 ret = self.ProxyMethodClass(self, self._bus.get_connection(),
437 self._named_service,
438 self.__dbus_object_path__, member,
439 dbus_interface)
441 # this can be done without taking the lock - the worst that can
442 # happen is that we accidentally return a _DeferredMethod just after
443 # finishing introspection, in which case _introspect_add_to_queue and
444 # _introspect_block will do the right thing anyway
445 if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
446 ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue,
447 self._introspect_block)
449 return ret
451 def __repr__(self):
452 return '<ProxyObject wrapping %s %s %s at %#x>'%(
453 self._bus, self._named_service, self.__dbus_object_path__, id(self))
454 __str__ = __repr__
457 class Interface(object):
458 """An interface into a remote object.
460 An Interface can be used to wrap ProxyObjects
461 so that calls can be routed to their correct
462 D-Bus interface.
465 def __init__(self, object, dbus_interface):
466 """Construct a proxy for the given interface on the given object.
468 :Parameters:
469 `object` : `dbus.proxies.ProxyObject` or `dbus.Interface`
470 The remote object or another of its interfaces
471 `dbus_interface` : str
472 An interface the `object` implements
474 if isinstance(object, Interface):
475 self._obj = object.proxy_object
476 else:
477 self._obj = object
478 self._dbus_interface = dbus_interface
480 object_path = property (lambda self: self._obj.object_path, None, None,
481 "The D-Bus object path of the underlying object")
482 __dbus_object_path__ = object_path
483 bus_name = property (lambda self: self._obj.bus_name, None, None,
484 "The bus name to which the underlying proxy object "
485 "is bound")
486 requested_bus_name = property (lambda self: self._obj.requested_bus_name,
487 None, None,
488 "The bus name which was requested when the "
489 "underlying object was created")
490 proxy_object = property (lambda self: self._obj, None, None,
491 """The underlying proxy object""")
492 dbus_interface = property (lambda self: self._dbus_interface, None, None,
493 """The D-Bus interface represented""")
495 def connect_to_signal(self, signal_name, handler_function,
496 dbus_interface=None, **keywords):
497 """Arrange for a function to be called when the given signal is
498 emitted.
500 The parameters and keyword arguments are the same as for
501 `dbus.proxies.ProxyObject.connect_to_signal`, except that if
502 `dbus_interface` is None (the default), the D-Bus interface that
503 was passed to the `Interface` constructor is used.
505 if not dbus_interface:
506 dbus_interface = self._dbus_interface
508 return self._obj.connect_to_signal(signal_name, handler_function,
509 dbus_interface, **keywords)
511 def __getattr__(self, member):
512 if member.startswith('__') and member.endswith('__'):
513 raise AttributeError(member)
514 else:
515 return self._obj.get_dbus_method(member, self._dbus_interface)
517 def get_dbus_method(self, member, dbus_interface=None):
518 """Return a proxy method representing the given D-Bus method.
520 This is the same as `dbus.proxies.ProxyObject.get_dbus_method`
521 except that if `dbus_interface` is None (the default),
522 the D-Bus interface that was passed to the `Interface` constructor
523 is used.
525 if dbus_interface is None:
526 dbus_interface = self._dbus_interface
527 return self._obj.get_dbus_method(member, dbus_interface)
529 def __repr__(self):
530 return '<Interface %r implementing %r at %#x>'%(
531 self._obj, self._dbus_interface, id(self))
532 __str__ = __repr__