1 # Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
2 # Copyright (C) 2003 David Zeuthen
3 # Copyright (C) 2004 Rob Taylor
4 # Copyright (C) 2005, 2006 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 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 from dbus
._expat
_introspect
_parser
import process_introspection_data
29 from dbus
.exceptions
import MissingReplyHandlerException
, MissingErrorHandlerException
, IntrospectionParserException
, DBusException
31 __docformat__
= 'restructuredtext'
34 _logger
= logging
.getLogger('dbus.proxies')
37 class _ReplyHandler(object):
38 __slots__
= ('_on_error', '_on_reply', '_get_args_options')
39 def __init__(self
, on_reply
, on_error
, **get_args_options
):
40 self
._on
_error
= on_error
41 self
._on
_reply
= on_reply
42 self
._get
_args
_options
= get_args_options
44 def __call__(self
, message
):
45 if isinstance(message
, _dbus_bindings
.MethodReturnMessage
):
46 self
._on
_reply
(*message
.get_args_list(**self
._get
_args
_options
))
47 elif isinstance(message
, _dbus_bindings
.ErrorMessage
):
48 args
= message
.get_args_list()
50 self
._on
_error
(DBusException(args
[0]))
52 self
._on
_error
(DBusException())
54 self
._on
_error
(DBusException('Unexpected reply message type: %s'
61 This is returned instead of ProxyMethod when we are defering DBus calls
62 while waiting for introspection data to be returned
64 def __init__(self
, proxy_method
):
65 self
._proxy
_method
= proxy_method
66 self
._method
_name
= proxy_method
._method
_name
68 def __call__(self
, *args
, **keywords
):
70 if keywords
.has_key('reply_handler'):
71 reply_handler
= keywords
['reply_handler']
73 #block for now even on async
74 # FIXME: put ret in async queue in future if we have a reply handler
76 self
._proxy
_method
._proxy
._pending
_introspect
.block()
77 ret
= self
._proxy
_method
(*args
, **keywords
)
84 Typically a member of a ProxyObject. Calls to the
85 method produce messages that travel over the Bus and are routed
86 to a specific named Service.
88 def __init__(self
, proxy
, connection
, named_service
, object_path
, method_name
, iface
):
90 self
._connection
= connection
91 self
._named
_service
= named_service
92 self
._object
_path
= object_path
93 self
._method
_name
= method_name
94 self
._dbus
_interface
= iface
96 def __call__(self
, *args
, **keywords
):
98 if keywords
.has_key('timeout'):
99 timeout
= keywords
['timeout']
102 if keywords
.has_key('reply_handler'):
103 reply_handler
= keywords
['reply_handler']
106 if keywords
.has_key('error_handler'):
107 error_handler
= keywords
['error_handler']
110 if keywords
.has_key('ignore_reply'):
111 ignore_reply
= keywords
['ignore_reply']
113 get_args_options
= {}
114 if keywords
.has_key('utf8_strings'):
115 get_args_options
['utf8_strings'] = keywords
['utf8_strings']
116 if keywords
.has_key('byte_arrays'):
117 get_args_options
['byte_arrays'] = keywords
['byte_arrays']
119 if not(reply_handler
and error_handler
):
121 raise MissingErrorHandlerException()
123 raise MissingReplyHandlerException()
125 dbus_interface
= self
._dbus
_interface
126 if keywords
.has_key('dbus_interface'):
127 dbus_interface
= keywords
['dbus_interface']
131 tmp_iface
= dbus_interface
+ '.'
133 key
= tmp_iface
+ self
._method
_name
135 introspect_sig
= None
136 if self
._proxy
._introspect
_method
_map
.has_key (key
):
137 introspect_sig
= self
._proxy
._introspect
_method
_map
[key
]
139 message
= _dbus_bindings
.MethodCallMessage(destination
=None,
140 path
=self
._object
_path
,
141 interface
=dbus_interface
,
142 method
=self
._method
_name
)
143 message
.set_destination(self
._named
_service
)
145 # Add the arguments to the function
147 message
.append(signature
=introspect_sig
, *args
)
149 _logger
.error('Unable to set arguments %r according to '
150 'introspected signature %r: %s: %s',
151 args
, introspect_sig
, e
.__class
__, e
)
155 self
._connection
.send_message(message
)
158 result
= self
._connection
.send_message_with_reply(message
, _ReplyHandler(reply_handler
, error_handler
, **get_args_options
), timeout
/1000.0)
161 reply_message
= self
._connection
.send_message_with_reply_and_block(message
, timeout
)
162 args_list
= reply_message
.get_args_list(**get_args_options
)
163 if len(args_list
) == 0:
165 elif len(args_list
) == 1:
168 return tuple(args_list
)
172 """A proxy to the remote Object.
174 A ProxyObject is provided by the Bus. ProxyObjects
175 have member functions, and can be called like normal Python objects.
177 ProxyMethodClass
= ProxyMethod
178 DeferedMethodClass
= DeferedMethod
180 INTROSPECT_STATE_DONT_INTROSPECT
= 0
181 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
= 1
182 INTROSPECT_STATE_INTROSPECT_DONE
= 2
184 def __init__(self
, bus
, named_service
, object_path
, introspect
=True):
185 """Initialize the proxy object.
189 The bus on which to find this object
190 `named_service` : str
191 A bus name for the endpoint owning the object (need not
192 actually be a service name)
194 The object path at which the endpoint exports the object
196 If true (default), attempt to introspect the remote
197 object to find out supported methods and their signatures
200 self
._named
_service
= named_service
201 self
.__dbus
_object
_path
__ = object_path
203 #PendingCall object for Introspect call
204 self
._pending
_introspect
= None
205 #queue of async calls waiting on the Introspect to return
206 self
._pending
_introspect
_queue
= []
207 #dictionary mapping method names to their input signatures
208 self
._introspect
_method
_map
= {}
211 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
213 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
215 self
._pending
_introspect
= self
._Introspect
()
218 def connect_to_signal(self
, signal_name
, handler_function
, dbus_interface
=None, **keywords
):
219 """Arrange for the given function to be called when the given signal
222 FIXME: this binds to the unique name regardless of whether this is a
223 by-unique-name or by-well-known-name proxy.
227 The name of the signal
228 `handler_function` : callable
229 A function to be called when the signal is emitted by
230 the remote object. Its positional arguments will be the
231 arguments of the signal; optionally, it may be given
232 keyword arguments as described below.
233 `dbus_interface` : str
234 Optional interface with which to qualify the signal name.
235 If None (the default) the handler will be called whenever a
236 signal of the given member name is received, whatever
239 `utf8_strings` : bool
240 If True, the handler function will receive any string
241 arguments as dbus.UTF8String objects (a subclass of str
242 guaranteed to be UTF-8). If False (default) it will receive
243 any string arguments as dbus.String objects (a subclass of
246 If True, the handler function will receive any byte-array
247 arguments as dbus.ByteArray objects (a subclass of str).
248 If False (default) it will receive any byte-array
249 arguments as a dbus.Array of dbus.Byte (subclasses of:
251 `sender_keyword` : str
252 If not None (the default), the handler function will receive
253 the unique name of the sending endpoint as a keyword
254 argument with this name
255 `destination_keyword` : str
256 If not None (the default), the handler function will receive
257 the bus name of the destination (or None if the signal is a
258 broadcast, as is usual) as a keyword argument with this name.
259 `interface_keyword` : str
260 If not None (the default), the handler function will receive
261 the signal interface as a keyword argument with this name.
262 `member_keyword` : str
263 If not None (the default), the handler function will receive
264 the signal name as a keyword argument with this name.
266 If not None (the default), the handler function will receive
267 the object-path of the sending object as a keyword argument
269 `message_keyword` : str
270 If not None (the default), the handler function will receive
271 the `dbus.lowlevel.SignalMessage` as a keyword argument with
273 `arg...` : unicode or UTF-8 str
274 If there are additional keyword parameters of the form
275 ``arg``\ *n*, match only signals where the *n*\ th argument
276 is the value given for that keyword parameter. As of this time
277 only string arguments can be matched (in particular,
278 object paths and signatures can't).
281 self
._bus
.add_signal_receiver(handler_function
,
282 signal_name
=signal_name
,
283 dbus_interface
=dbus_interface
,
284 named_service
=self
._named
_service
,
285 path
=self
.__dbus
_object
_path
__,
288 def _Introspect(self
):
289 message
= _dbus_bindings
.MethodCallMessage(None, self
.__dbus
_object
_path
__, 'org.freedesktop.DBus.Introspectable', 'Introspect')
290 message
.set_destination(self
._named
_service
)
292 result
= self
._bus
.get_connection().send_message_with_reply(message
, _ReplyHandler(self
._introspect
_reply
_handler
, self
._introspect
_error
_handler
, utf8_strings
=True), -1)
295 def _introspect_execute_queue(self
):
296 for call
in self
._pending
_introspect
_queue
:
297 (member
, iface
, args
, keywords
) = call
299 introspect_sig
= None
303 tmp_iface
= iface
+ '.'
305 key
= tmp_iface
+ '.' + member
306 if self
._introspect
_method
_map
.has_key (key
):
307 introspect_sig
= self
._introspect
_method
_map
[key
]
310 call_object
= self
.ProxyMethodClass(self
._bus
.get_connection(),
312 self
.__dbus
_object
_path
__,
317 call_object(args
, keywords
)
319 def _introspect_reply_handler(self
, data
):
321 self
._introspect
_method
_map
= process_introspection_data(data
)
322 except IntrospectionParserException
, e
:
323 self
._introspect
_error
_handler
(e
)
326 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_DONE
327 #self._introspect_execute_queue()
329 def _introspect_error_handler(self
, error
):
330 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
331 self
._introspect
_execute
_queue
()
332 sys
.stderr
.write("Introspect error: " + str(error
) + "\n")
334 def __getattr__(self
, member
, dbus_interface
=None):
335 if member
== '__call__':
336 return object.__call
__
337 elif member
.startswith('__') and member
.endswith('__'):
338 raise AttributeError(member
)
340 return self
.get_dbus_method(member
, dbus_interface
)
342 def get_dbus_method(self
, member
, dbus_interface
=None):
343 """Return a proxy method representing the given D-Bus method. The
344 returned proxy method can be called in the usual way. For instance, ::
346 proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
350 proxy.Foo(123, dbus_interface='com.example.Bar')
354 getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
356 However, using `get_dbus_method` is the only way to call D-Bus
357 methods with certain awkward names - if the author of a service
358 implements a method called ``connect_to_signal`` or even
359 ``__getattr__``, you'll need to use `get_dbus_method` to call them.
361 For services which follow the D-Bus convention of CamelCaseMethodNames
362 this won't be a problem.
365 ret
= self
.ProxyMethodClass(self
, self
._bus
.get_connection(),
367 self
.__dbus
_object
_path
__, member
,
370 if self
._introspect
_state
== self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
:
371 ret
= self
.DeferedMethodClass(ret
)
376 return '<ProxyObject wrapping %s %s %s at %#x>'%(
377 self
._bus
, self
._named
_service
, self
.__dbus
_object
_path
__, id(self
))