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 import dbus
.introspect_parser
as introspect_parser
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')
39 def __init__(self
, on_reply
, on_error
):
40 self
._on
_error
= on_error
41 self
._on
_reply
= on_reply
43 def __call__(self
, message
):
44 if isinstance(message
, _dbus_bindings
.MethodReturnMessage
):
45 self
._on
_reply
(*message
.get_args_list())
46 elif isinstance(message
, _dbus_bindings
.ErrorMessage
):
47 args
= message
.get_args_list()
49 self
._on
_error
(DBusException(args
[0]))
51 self
._on
_error
(DBusException())
53 self
._on
_error
(DBusException('Unexpected reply message type: %s'
60 This is returned instead of ProxyMethod when we are defering DBus calls
61 while waiting for introspection data to be returned
63 def __init__(self
, proxy_method
):
64 self
._proxy
_method
= proxy_method
65 self
._method
_name
= proxy_method
._method
_name
67 def __call__(self
, *args
, **keywords
):
69 if keywords
.has_key('reply_handler'):
70 reply_handler
= keywords
['reply_handler']
72 #block for now even on async
73 # FIXME: put ret in async queue in future if we have a reply handler
75 self
._proxy
_method
._proxy
._pending
_introspect
._block
()
76 ret
= self
._proxy
_method
(*args
, **keywords
)
83 Typically a member of a ProxyObject. Calls to the
84 method produce messages that travel over the Bus and are routed
85 to a specific named Service.
87 def __init__(self
, proxy
, connection
, named_service
, object_path
, method_name
, iface
):
89 self
._connection
= connection
90 self
._named
_service
= named_service
91 self
._object
_path
= object_path
92 self
._method
_name
= method_name
93 self
._dbus
_interface
= iface
95 def __call__(self
, *args
, **keywords
):
97 if keywords
.has_key('timeout'):
98 timeout
= keywords
['timeout']
101 if keywords
.has_key('reply_handler'):
102 reply_handler
= keywords
['reply_handler']
105 if keywords
.has_key('error_handler'):
106 error_handler
= keywords
['error_handler']
109 if keywords
.has_key('ignore_reply'):
110 ignore_reply
= keywords
['ignore_reply']
112 get_args_options
= {}
113 if keywords
.has_key('utf8_strings'):
114 get_args_options
['utf8_strings'] = keywords
['utf8_strings']
115 if keywords
.has_key('byte_arrays'):
116 get_args_options
['byte_arrays'] = keywords
['byte_arrays']
118 if not(reply_handler
and error_handler
):
120 raise MissingErrorHandlerException()
122 raise MissingReplyHandlerException()
124 dbus_interface
= self
._dbus
_interface
125 if keywords
.has_key('dbus_interface'):
126 dbus_interface
= keywords
['dbus_interface']
130 tmp_iface
= dbus_interface
+ '.'
132 key
= tmp_iface
+ self
._method
_name
134 introspect_sig
= None
135 if self
._proxy
._introspect
_method
_map
.has_key (key
):
136 introspect_sig
= self
._proxy
._introspect
_method
_map
[key
]
138 message
= _dbus_bindings
.MethodCallMessage(destination
=None,
139 path
=self
._object
_path
,
140 interface
=dbus_interface
,
141 method
=self
._method
_name
)
142 message
.set_destination(self
._named
_service
)
144 # Add the arguments to the function
146 message
.append(signature
=introspect_sig
, *args
)
148 _logger
.error('Unable to set arguments %r according to '
149 'introspected signature %r: %s: %s',
150 args
, introspect_sig
, e
.__class
__, e
)
153 # FIXME: using private API on Connection while I decide whether to
154 # make it public or what
156 result
= self
._connection
._send
(message
)
159 result
= self
._connection
._send
_with
_reply
(message
, _ReplyHandler(reply_handler
, error_handler
), timeout
/1000.0)
162 reply_message
= self
._connection
._send
_with
_reply
_and
_block
(message
, timeout
)
163 args_list
= reply_message
.get_args_list(**get_args_options
)
164 if len(args_list
) == 0:
166 elif len(args_list
) == 1:
169 return tuple(args_list
)
173 """A proxy to the remote Object.
175 A ProxyObject is provided by the Bus. ProxyObjects
176 have member functions, and can be called like normal Python objects.
178 ProxyMethodClass
= ProxyMethod
179 DeferedMethodClass
= DeferedMethod
181 INTROSPECT_STATE_DONT_INTROSPECT
= 0
182 INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
= 1
183 INTROSPECT_STATE_INTROSPECT_DONE
= 2
185 def __init__(self
, bus
, named_service
, object_path
, introspect
=True):
186 """Initialize the proxy object.
190 The bus on which to find this object
191 `named_service` : str
192 A bus name for the endpoint owning the object (need not
193 actually be a service name)
195 The object path at which the endpoint exports the object
197 If true (default), attempt to introspect the remote
198 object to find out supported methods and their signatures
201 self
._named
_service
= named_service
202 self
._object
_path
= object_path
204 #PendingCall object for Introspect call
205 self
._pending
_introspect
= None
206 #queue of async calls waiting on the Introspect to return
207 self
._pending
_introspect
_queue
= []
208 #dictionary mapping method names to their input signatures
209 self
._introspect
_method
_map
= {}
212 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
214 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
216 self
._pending
_introspect
= self
._Introspect
()
219 def connect_to_signal(self
, signal_name
, handler_function
, dbus_interface
=None, **keywords
):
220 """Arrange for the given function to be called when the given signal
225 The name of the signal
226 `handler_function` : callable
227 A function to be called when the signal is emitted by
228 the remote object. Its positional arguments will be the
229 arguments of the signal; optionally, it may be given
230 keyword arguments as described below.
231 `dbus_interface` : str
232 Optional interface with which to qualify the signal name.
233 If None (the default) the handler will be called whenever a
234 signal of the given member name is received, whatever
237 `utf8_strings` : bool
238 If True, the handler function will receive any string
239 arguments as dbus.UTF8String objects (a subclass of str
240 guaranteed to be UTF-8). If False (default) it will receive
241 any string arguments as dbus.String objects (a subclass of
244 If True, the handler function will receive any byte-array
245 arguments as dbus.ByteArray objects (a subclass of str).
246 If False (default) it will receive any byte-array
247 arguments as a dbus.Array of dbus.Byte (subclasses of:
249 `sender_keyword` : str
250 If not None (the default), the handler function will receive
251 the unique name of the sending endpoint as a keyword
252 argument with this name
254 If not None (the default), the handler function will receive
255 the object-path of the sending object as a keyword argument
257 `arg...` : unicode or UTF-8 str
258 If there are additional keyword parameters of the form
259 ``arg``\ *n*, match only signals where the *n*\ th argument
260 is the value given for that keyword parameter. As of this time
261 only string arguments can be matched (in particular,
262 object paths and signatures can't).
264 self
._bus
.add_signal_receiver(handler_function
,
265 signal_name
=signal_name
,
266 dbus_interface
=dbus_interface
,
267 named_service
=self
._named
_service
,
268 path
=self
._object
_path
,
271 def _Introspect(self
):
272 message
= _dbus_bindings
.MethodCallMessage(None, self
._object
_path
, 'org.freedesktop.DBus.Introspectable', 'Introspect')
273 message
.set_destination(self
._named
_service
)
275 result
= self
._bus
.get_connection()._send
_with
_reply
(message
, _ReplyHandler(self
._introspect
_reply
_handler
, self
._introspect
_error
_handler
), -1)
278 def _introspect_execute_queue(self
):
279 for call
in self
._pending
_introspect
_queue
:
280 (member
, iface
, args
, keywords
) = call
282 introspect_sig
= None
286 tmp_iface
= iface
+ '.'
288 key
= tmp_iface
+ '.' + member
289 if self
._introspect
_method
_map
.has_key (key
):
290 introspect_sig
= self
._introspect
_method
_map
[key
]
293 call_object
= self
.ProxyMethodClass(self
._bus
.get_connection(),
300 call_object(args
, keywords
)
302 def _introspect_reply_handler(self
, data
):
304 self
._introspect
_method
_map
= introspect_parser
.process_introspection_data(data
)
305 except IntrospectionParserException
, e
:
306 self
._introspect
_error
_handler
(e
)
309 self
._introspect
_state
= self
.INTROSPECT_STATE_INTROSPECT_DONE
310 #self._introspect_execute_queue()
312 def _introspect_error_handler(self
, error
):
313 self
._introspect
_state
= self
.INTROSPECT_STATE_DONT_INTROSPECT
314 self
._introspect
_execute
_queue
()
315 sys
.stderr
.write("Introspect error: " + str(error
) + "\n")
317 def __getattr__(self
, member
, dbus_interface
=None):
318 if member
== '__call__':
319 return object.__call
__
320 elif member
.startswith('__') and member
.endswith('__'):
321 raise AttributeError(member
)
323 ret
= self
.ProxyMethodClass(self
, self
._bus
.get_connection(),
325 self
._object
_path
, member
,
328 if self
._introspect
_state
== self
.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
:
329 ret
= self
.DeferedMethodClass(ret
)
334 return '<ProxyObject wrapping %s %s %s at %#x>'%(
335 self
._bus
, self
._named
_service
, self
._object
_path
, id(self
))