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')
40 BUS_DAEMON_NAME
= 'org.freedesktop.DBus'
41 BUS_DAEMON_PATH
= '/org/freedesktop/DBus'
42 BUS_DAEMON_IFACE
= BUS_DAEMON_NAME
44 # This is special in libdbus - the bus daemon will kick us off if we try to
45 # send any message to it :-/
46 LOCAL_PATH
= '/org/freedesktop/DBus/Local'
49 class _ReplyHandler(object):
50 __slots__
= ('_on_error', '_on_reply', '_get_args_options')
51 def __init__(self
, on_reply
, on_error
, **get_args_options
):
52 self
._on
_error
= on_error
53 self
._on
_reply
= on_reply
54 self
._get
_args
_options
= get_args_options
56 def __call__(self
, message
):
57 if isinstance(message
, _dbus_bindings
.MethodReturnMessage
):
58 self
._on
_reply
(*message
.get_args_list(**self
._get
_args
_options
))
59 elif isinstance(message
, _dbus_bindings
.ErrorMessage
):
60 args
= message
.get_args_list()
62 self
._on
_error
(DBusException(args
[0]))
64 self
._on
_error
(DBusException())
66 self
._on
_error
(DBusException('Unexpected reply message type: %s'
70 class _DeferredMethod
:
71 """A proxy method which will only get called once we have its
74 def __init__(self
, proxy_method
, append
, block
):
75 self
._proxy
_method
= proxy_method
76 # the test suite relies on the existence of this property
77 self
._method
_name
= proxy_method
._method
_name
81 def __call__(self
, *args
, **keywords
):
82 if keywords
.has_key('reply_handler'):
83 # defer the async call til introspection finishes
84 self
._append
(self
._proxy
_method
, args
, keywords
)
87 # we're being synchronous, so block
89 return self
._proxy
_method
(*args
, **keywords
)
95 Typically a member of a ProxyObject. Calls to the
96 method produce messages that travel over the Bus and are routed
97 to a specific named Service.
99 def __init__(self
, proxy
, connection
, named_service
, object_path
, method_name
, iface
):
100 if object_path
== LOCAL_PATH
:
101 raise DBusException('Methods may not be called on the reserved '
102 'path %s' % LOCAL_PATH
)
104 # trust that the proxy, and the properties it had, are OK
106 self
._connection
= connection
107 self
._named
_service
= named_service
108 self
._object
_path
= object_path
109 # fail early if the method name is bad
110 _dbus_bindings
.validate_member_name(method_name
)
111 # the test suite relies on the existence of this property
112 self
._method
_name
= method_name
113 # fail early if the interface name is bad
114 if iface
is not None:
115 _dbus_bindings
.validate_interface_name(iface
)
116 self
._dbus
_interface
= iface
118 def __call__(self
, *args
, **keywords
):
120 if keywords
.has_key('timeout'):
121 timeout
= keywords
['timeout']
124 if keywords
.has_key('reply_handler'):
125 reply_handler
= keywords
['reply_handler']
128 if keywords
.has_key('error_handler'):
129 error_handler
= keywords
['error_handler']
132 if keywords
.has_key('ignore_reply'):
133 ignore_reply
= keywords
['ignore_reply']
135 get_args_options
= {}
136 if keywords
.has_key('utf8_strings'):
137 get_args_options
['utf8_strings'] = keywords
['utf8_strings']
138 if keywords
.has_key('byte_arrays'):
139 get_args_options
['byte_arrays'] = keywords
['byte_arrays']
141 if not(reply_handler
and error_handler
):
143 raise MissingErrorHandlerException()
145 raise MissingReplyHandlerException()
147 dbus_interface
= self
._dbus
_interface
148 if keywords
.has_key('dbus_interface'):
149 dbus_interface
= keywords
['dbus_interface']
153 tmp_iface
= dbus_interface
+ '.'
155 key
= tmp_iface
+ self
._method
_name
157 introspect_sig
= None
158 if self
._proxy
._introspect
_method
_map
.has_key (key
):
159 introspect_sig
= self
._proxy
._introspect
_method
_map
[key
]
161 message
= _dbus_bindings
.MethodCallMessage(destination
=None,
162 path
=self
._object
_path
,
163 interface
=dbus_interface
,
164 method
=self
._method
_name
)
165 message
.set_destination(self
._named
_service
)
167 # Add the arguments to the function
169 message
.append(signature
=introspect_sig
, *args
)
171 _logger
.error('Unable to set arguments %r according to '
172 'introspected signature %r: %s: %s',
173 args
, introspect_sig
, e
.__class
__, e
)
177 self
._connection
.send_message(message
)
180 self
._connection
.send_message_with_reply(message
, _ReplyHandler(reply_handler
, error_handler
, **get_args_options
), timeout
/1000.0, require_main_loop
=1)
183 reply_message
= self
._connection
.send_message_with_reply_and_block(message
, timeout
)
184 args_list
= reply_message
.get_args_list(**get_args_options
)
185 if len(args_list
) == 0:
187 elif len(args_list
) == 1:
190 return tuple(args_list
)
194 """A proxy to the remote Object.
196 A ProxyObject is provided by the Bus. ProxyObjects
197 have member functions, and can be called like normal Python objects.
199 ProxyMethodClass
= _ProxyMethod
200 DeferredMethodClass
= _DeferredMethod
202 INTROSPECT_STATE_DONT_INTROSPECT
= 0
203 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
= 1
204 INTROSPECT_STATE_INTROSPECT_DONE
= 2
206 def __init__(self
, bus
, named_service
, object_path
, introspect
=True,
207 follow_name_owner_changes
=False):
208 """Initialize the proxy object.
212 The bus on which to find this object
213 `named_service` : str
214 A bus name for the endpoint owning the object (need not
215 actually be a service name)
217 The object path at which the endpoint exports the object
219 If true (default), attempt to introspect the remote
220 object to find out supported methods and their signatures
221 `follow_name_owner_changes` : bool
222 If true (default is false) and the `named_service` is a
223 well-known name, follow ownership changes for that name
225 if follow_name_owner_changes
:
226 bus
._require
_main
_loop
() # we don't get the signals otherwise
230 if named_service
is not None:
231 _dbus_bindings
.validate_bus_name(named_service
)
232 self
._named
_service
= named_service
234 _dbus_bindings
.validate_object_path(object_path
)
235 self
.__dbus
_object
_path
__ = object_path
237 if (named_service
is not None and named_service
[:1] != ':'
238 and named_service
!= BUS_DAEMON_NAME
239 and not follow_name_owner_changes
):
240 bus_object
= bus
.get_object(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
)
242 self
._named
_service
= bus_object
.GetNameOwner(named_service
,
243 dbus_interface
=BUS_DAEMON_IFACE
)
244 except DBusException
, e
:
245 # FIXME: detect whether it's NameHasNoOwner, but properly
246 #if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
248 # it might not exist: try to start it
249 bus_object
.StartServiceByName(named_service
,
250 _dbus_bindings
.UInt32(0))
251 self
._named
_service
= bus_object
.GetNameOwner(named_service
,
252 dbus_interface
=BUS_DAEMON_IFACE
)
254 #PendingCall object for Introspect call
255 self
._pending
_introspect
= None
256 #queue of async calls waiting on the Introspect to return
257 self
._pending
_introspect
_queue
= []
258 #dictionary mapping method names to their input signatures
259 self
._introspect
_method
_map
= {}
261 # must be a recursive lock because block() is called while locked,
262 # and calls the callback which re-takes the lock
263 self
._introspect
_lock
= RLock()
265 if not introspect
or self
.__dbus
_object
_path
__ == LOCAL_PATH
:
266 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
268 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
270 self
._pending
_introspect
= self
._Introspect
()
272 def connect_to_signal(self
, signal_name
, handler_function
, dbus_interface
=None, **keywords
):
273 """Arrange for the given function to be called when the given signal
278 The name of the signal
279 `handler_function` : callable
280 A function to be called when the signal is emitted by
281 the remote object. Its positional arguments will be the
282 arguments of the signal; optionally, it may be given
283 keyword arguments as described below.
284 `dbus_interface` : str
285 Optional interface with which to qualify the signal name.
286 If None (the default) the handler will be called whenever a
287 signal of the given member name is received, whatever
290 `utf8_strings` : bool
291 If True, the handler function will receive any string
292 arguments as dbus.UTF8String objects (a subclass of str
293 guaranteed to be UTF-8). If False (default) it will receive
294 any string arguments as dbus.String objects (a subclass of
297 If True, the handler function will receive any byte-array
298 arguments as dbus.ByteArray objects (a subclass of str).
299 If False (default) it will receive any byte-array
300 arguments as a dbus.Array of dbus.Byte (subclasses of:
302 `sender_keyword` : str
303 If not None (the default), the handler function will receive
304 the unique name of the sending endpoint as a keyword
305 argument with this name
306 `destination_keyword` : str
307 If not None (the default), the handler function will receive
308 the bus name of the destination (or None if the signal is a
309 broadcast, as is usual) as a keyword argument with this name.
310 `interface_keyword` : str
311 If not None (the default), the handler function will receive
312 the signal interface as a keyword argument with this name.
313 `member_keyword` : str
314 If not None (the default), the handler function will receive
315 the signal name as a keyword argument with this name.
317 If not None (the default), the handler function will receive
318 the object-path of the sending object as a keyword argument
320 `message_keyword` : str
321 If not None (the default), the handler function will receive
322 the `dbus.lowlevel.SignalMessage` as a keyword argument with
324 `arg...` : unicode or UTF-8 str
325 If there are additional keyword parameters of the form
326 ``arg``\ *n*, match only signals where the *n*\ th argument
327 is the value given for that keyword parameter. As of this time
328 only string arguments can be matched (in particular,
329 object paths and signatures can't).
332 self
._bus
.add_signal_receiver(handler_function
,
333 signal_name
=signal_name
,
334 dbus_interface
=dbus_interface
,
335 named_service
=self
._named
_service
,
336 path
=self
.__dbus
_object
_path
__,
339 def _Introspect(self
):
340 message
= _dbus_bindings
.MethodCallMessage(None, self
.__dbus
_object
_path
__, 'org.freedesktop.DBus.Introspectable', 'Introspect')
341 message
.set_destination(self
._named
_service
)
343 result
= self
._bus
.get_connection().send_message_with_reply(message
, _ReplyHandler(self
._introspect
_reply
_handler
, self
._introspect
_error
_handler
, utf8_strings
=True), -1)
346 def _introspect_execute_queue(self
):
347 # FIXME: potential to flood the bus
348 # We should make sure mainloops all have idle handlers
349 # and do one message per idle
350 for (proxy_method
, args
, keywords
) in self
._pending
_introspect
_queue
:
351 proxy_method(*args
, **keywords
)
353 def _introspect_reply_handler(self
, data
):
354 self
._introspect
_lock
.acquire()
357 self
._introspect
_method
_map
= process_introspection_data(data
)
358 except IntrospectionParserException
, e
:
359 self
._introspect
_error
_handler
(e
)
362 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_DONE
363 self
._pending
_introspect
= None
364 self
._introspect
_execute
_queue
()
366 self
._introspect
_lock
.release()
368 def _introspect_error_handler(self
, error
):
369 self
._introspect
_lock
.acquire()
371 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
372 self
._pending
_introspect
= None
373 self
._introspect
_execute
_queue
()
374 sys
.stderr
.write("Introspect error: " + str(error
) + "\n")
376 self
._introspect
_lock
.release()
378 def _introspect_block(self
):
379 self
._introspect
_lock
.acquire()
381 if self
._pending
_introspect
is not None:
382 self
._pending
_introspect
.block()
383 # else someone still has a _DeferredMethod from before we
384 # finished introspection: no need to do anything special any more
386 self
._introspect
_lock
.release()
388 def _introspect_add_to_queue(self
, callback
, args
, kwargs
):
389 self
._introspect
_lock
.acquire()
391 if self
._introspect
_state
== self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
:
392 self
._pending
_introspect
_queue
.append((callback
, args
, kwargs
))
394 # someone still has a _DeferredMethod from before we
395 # finished introspection
396 callback(*args
, **kwargs
)
398 self
._introspect
_lock
.release()
400 def __getattr__(self
, member
, dbus_interface
=None):
401 if member
== '__call__':
402 return object.__call
__
403 elif member
.startswith('__') and member
.endswith('__'):
404 raise AttributeError(member
)
406 return self
.get_dbus_method(member
, dbus_interface
)
408 def get_dbus_method(self
, member
, dbus_interface
=None):
409 """Return a proxy method representing the given D-Bus method. The
410 returned proxy method can be called in the usual way. For instance, ::
412 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
416 proxy.Foo(123, dbus_interface='com.example.Bar')
420 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
422 However, using `get_dbus_method` is the only way to call D-Bus
423 methods with certain awkward names - if the author of a service
424 implements a method called ``connect_to_signal`` or even
425 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
427 For services which follow the D-Bus convention of CamelCaseMethodNames
428 this won't be a problem.
431 ret
= self
.ProxyMethodClass(self
, self
._bus
.get_connection(),
433 self
.__dbus
_object
_path
__, member
,
436 # this can be done without taking the lock - the worst that can
437 # happen is that we accidentally return a _DeferredMethod just after
438 # finishing introspection, in which case _introspect_add_to_queue and
439 # _introspect_block will do the right thing anyway
440 if self
._introspect
_state
== self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
:
441 ret
= self
.DeferredMethodClass(ret
, self
._introspect
_add
_to
_queue
,
442 self
._introspect
_block
)
447 return '<ProxyObject wrapping %s %s %s at %#x>'%(
448 self
._bus
, self
._named
_service
, self
.__dbus
_object
_path
__, id(self
))