NEWS: Describe new DBusException 'args' support
[dbus-python-phuang.git] / dbus / connection.py
blob13040580006e3b68f45dc6a3594c269cd3165c97
1 # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
3 # Licensed under the Academic Free License version 2.1
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published
7 # by the Free Software Foundation; either version 2.1 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 __all__ = ('Connection', 'SignalMatch')
20 __docformat__ = 'reStructuredText'
22 import logging
23 try:
24 import thread
25 except ImportError:
26 import dummy_thread as thread
27 import weakref
29 from _dbus_bindings import Connection as _Connection, ErrorMessage, \
30 MethodCallMessage, MethodReturnMessage, \
31 LOCAL_PATH, LOCAL_IFACE, \
32 validate_interface_name, validate_member_name,\
33 validate_bus_name, validate_object_path,\
34 validate_error_name, \
35 HANDLER_RESULT_NOT_YET_HANDLED, \
36 UTF8String, SignalMessage
37 from dbus.exceptions import DBusException
38 from dbus.proxies import ProxyObject
41 _logger = logging.getLogger('dbus.connection')
44 def _noop(*args, **kwargs):
45 pass
48 class SignalMatch(object):
49 __slots__ = ('_sender_name_owner', '_member', '_interface', '_sender',
50 '_path', '_handler', '_args_match', '_rule',
51 '_utf8_strings', '_byte_arrays', '_conn_weakref',
52 '_destination_keyword', '_interface_keyword',
53 '_message_keyword', '_member_keyword',
54 '_sender_keyword', '_path_keyword', '_int_args_match')
56 def __init__(self, conn, sender, object_path, dbus_interface,
57 member, handler, utf8_strings=False, byte_arrays=False,
58 sender_keyword=None, path_keyword=None,
59 interface_keyword=None, member_keyword=None,
60 message_keyword=None, destination_keyword=None,
61 **kwargs):
62 if member is not None:
63 validate_member_name(member)
64 if dbus_interface is not None:
65 validate_interface_name(dbus_interface)
66 if sender is not None:
67 validate_bus_name(sender)
68 if object_path is not None:
69 validate_object_path(object_path)
71 self._rule = None
72 self._conn_weakref = weakref.ref(conn)
73 self._sender = sender
74 self._interface = dbus_interface
75 self._member = member
76 self._path = object_path
77 self._handler = handler
79 # if the connection is actually a bus, it's responsible for changing
80 # this later
81 self._sender_name_owner = sender
83 self._utf8_strings = utf8_strings
84 self._byte_arrays = byte_arrays
85 self._sender_keyword = sender_keyword
86 self._path_keyword = path_keyword
87 self._member_keyword = member_keyword
88 self._interface_keyword = interface_keyword
89 self._message_keyword = message_keyword
90 self._destination_keyword = destination_keyword
92 self._args_match = kwargs
93 if not kwargs:
94 self._int_args_match = None
95 else:
96 self._int_args_match = {}
97 for kwarg in kwargs:
98 if not kwarg.startswith('arg'):
99 raise TypeError('SignalMatch: unknown keyword argument %s'
100 % kwarg)
101 try:
102 index = int(kwarg[3:])
103 except ValueError:
104 raise TypeError('SignalMatch: unknown keyword argument %s'
105 % kwarg)
106 if index < 0 or index > 63:
107 raise TypeError('SignalMatch: arg match index must be in '
108 'range(64), not %d' % index)
109 self._int_args_match[index] = kwargs[kwarg]
111 def __hash__(self):
112 """SignalMatch objects are compared by identity."""
113 return hash(id(self))
115 def __eq__(self, other):
116 """SignalMatch objects are compared by identity."""
117 return self is other
119 def __ne__(self, other):
120 """SignalMatch objects are compared by identity."""
121 return self is not other
123 sender = property(lambda self: self._sender)
125 def __str__(self):
126 if self._rule is None:
127 rule = ["type='signal'"]
128 if self._sender is not None:
129 rule.append("sender='%s'" % self._sender)
130 if self._path is not None:
131 rule.append("path='%s'" % self._path)
132 if self._interface is not None:
133 rule.append("interface='%s'" % self._interface)
134 if self._member is not None:
135 rule.append("member='%s'" % self._member)
136 if self._int_args_match is not None:
137 for index, value in self._int_args_match.iteritems():
138 rule.append("arg%d='%s'" % (index, value))
140 self._rule = ','.join(rule)
142 return self._rule
144 def __repr__(self):
145 return ('<%s at %x "%s" on conn %r>'
146 % (self.__class__, id(self), self._rule, self._conn_weakref()))
148 def set_sender_name_owner(self, new_name):
149 self._sender_name_owner = new_name
151 def matches_removal_spec(self, sender, object_path,
152 dbus_interface, member, handler, **kwargs):
153 if handler not in (None, self._handler):
154 return False
155 if sender != self._sender:
156 return False
157 if object_path != self._path:
158 return False
159 if dbus_interface != self._interface:
160 return False
161 if member != self._member:
162 return False
163 if kwargs != self._args_match:
164 return False
165 return True
167 def maybe_handle_message(self, message):
168 args = None
170 # these haven't been checked yet by the match tree
171 if self._sender_name_owner not in (None, message.get_sender()):
172 return False
173 if self._int_args_match is not None:
174 # extracting args with utf8_strings and byte_arrays is less work
175 args = message.get_args_list(utf8_strings=True, byte_arrays=True)
176 for index, value in self._int_args_match.iteritems():
177 if (index >= len(args)
178 or not isinstance(args[index], UTF8String)
179 or args[index] != value):
180 return False
182 # these have likely already been checked by the match tree
183 if self._member not in (None, message.get_member()):
184 return False
185 if self._interface not in (None, message.get_interface()):
186 return False
187 if self._path not in (None, message.get_path()):
188 return False
190 try:
191 # minor optimization: if we already extracted the args with the
192 # right calling convention to do the args match, don't bother
193 # doing so again
194 if args is None or not self._utf8_strings or not self._byte_arrays:
195 args = message.get_args_list(utf8_strings=self._utf8_strings,
196 byte_arrays=self._byte_arrays)
197 kwargs = {}
198 if self._sender_keyword is not None:
199 kwargs[self._sender_keyword] = message.get_sender()
200 if self._destination_keyword is not None:
201 kwargs[self._destination_keyword] = message.get_destination()
202 if self._path_keyword is not None:
203 kwargs[self._path_keyword] = message.get_path()
204 if self._member_keyword is not None:
205 kwargs[self._member_keyword] = message.get_member()
206 if self._interface_keyword is not None:
207 kwargs[self._interface_keyword] = message.get_interface()
208 if self._message_keyword is not None:
209 kwargs[self._message_keyword] = message
210 self._handler(*args, **kwargs)
211 except:
212 # basicConfig is a no-op if logging is already configured
213 logging.basicConfig()
214 _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
216 return True
218 def remove(self):
219 conn = self._conn_weakref()
220 # do nothing if the connection has already vanished
221 if conn is not None:
222 conn.remove_signal_receiver(self, self._member,
223 self._interface, self._sender,
224 self._path,
225 **self._args_match)
228 class Connection(_Connection):
229 """A connection to another application. In this base class there is
230 assumed to be no bus daemon.
233 ProxyObjectClass = ProxyObject
235 def __init__(self, *args, **kwargs):
236 super(Connection, self).__init__(*args, **kwargs)
238 # this if-block is needed because shared bus connections can be
239 # __init__'ed more than once
240 if not hasattr(self, '_dbus_Connection_initialized'):
241 self._dbus_Connection_initialized = 1
243 self._signal_recipients_by_object_path = {}
244 """Map from object path to dict mapping dbus_interface to dict
245 mapping member to list of SignalMatch objects."""
247 self._signals_lock = thread.allocate_lock()
248 """Lock used to protect signal data structures if doing two
249 removals at the same time (everything else is atomic, thanks to
250 the GIL)"""
252 self.add_message_filter(self.__class__._signal_func)
254 def activate_name_owner(self, bus_name):
255 """Return the unique name for the given bus name, activating it
256 if necessary and possible.
258 If the name is already unique or this connection is not to a
259 bus daemon, just return it.
261 :Returns: a bus name. If the given `bus_name` exists, the returned
262 name identifies its current owner; otherwise the returned name
263 does not exist.
264 :Raises DBusException: if the implementation has failed
265 to activate the given bus name.
267 return bus_name
269 def get_object(self, bus_name=None, object_path=None, introspect=True,
270 **kwargs):
271 """Return a local proxy for the given remote object.
273 Method calls on the proxy are translated into method calls on the
274 remote object.
276 :Parameters:
277 `bus_name` : str
278 A bus name (either the unique name or a well-known name)
279 of the application owning the object. The keyword argument
280 named_service is a deprecated alias for this.
281 `object_path` : str
282 The object path of the desired object
283 `introspect` : bool
284 If true (default), attempt to introspect the remote
285 object to find out supported methods and their signatures
287 :Returns: a `dbus.proxies.ProxyObject`
289 named_service = kwargs.pop('named_service', None)
290 if named_service is not None:
291 if bus_name is not None:
292 raise TypeError('bus_name and named_service cannot both '
293 'be specified')
294 from warnings import warn
295 warn('Passing the named_service parameter to get_object by name '
296 'is deprecated: please use positional parameters',
297 DeprecationWarning, stacklevel=2)
298 bus_name = named_service
299 if kwargs:
300 raise TypeError('get_object does not take these keyword '
301 'arguments: %s' % ', '.join(kwargs.iterkeys()))
303 return self.ProxyObjectClass(self, bus_name, object_path,
304 introspect=introspect)
306 def add_signal_receiver(self, handler_function,
307 signal_name=None,
308 dbus_interface=None,
309 bus_name=None,
310 path=None,
311 **keywords):
312 """Arrange for the given function to be called when a signal matching
313 the parameters is received.
315 :Parameters:
316 `handler_function` : callable
317 The function to be called. Its positional arguments will
318 be the arguments of the signal. By default it will receive
319 no keyword arguments, but see the description of
320 the optional keyword arguments below.
321 `signal_name` : str
322 The signal name; None (the default) matches all names
323 `dbus_interface` : str
324 The D-Bus interface name with which to qualify the signal;
325 None (the default) matches all interface names
326 `bus_name` : str
327 A bus name for the sender, which will be resolved to a
328 unique name if it is not already; None (the default) matches
329 any sender.
330 `path` : str
331 The object path of the object which must have emitted the
332 signal; None (the default) matches any object path
333 :Keywords:
334 `utf8_strings` : bool
335 If True, the handler function will receive any string
336 arguments as dbus.UTF8String objects (a subclass of str
337 guaranteed to be UTF-8). If False (default) it will receive
338 any string arguments as dbus.String objects (a subclass of
339 unicode).
340 `byte_arrays` : bool
341 If True, the handler function will receive any byte-array
342 arguments as dbus.ByteArray objects (a subclass of str).
343 If False (default) it will receive any byte-array
344 arguments as a dbus.Array of dbus.Byte (subclasses of:
345 a list of ints).
346 `sender_keyword` : str
347 If not None (the default), the handler function will receive
348 the unique name of the sending endpoint as a keyword
349 argument with this name.
350 `destination_keyword` : str
351 If not None (the default), the handler function will receive
352 the bus name of the destination (or None if the signal is a
353 broadcast, as is usual) as a keyword argument with this name.
354 `interface_keyword` : str
355 If not None (the default), the handler function will receive
356 the signal interface as a keyword argument with this name.
357 `member_keyword` : str
358 If not None (the default), the handler function will receive
359 the signal name as a keyword argument with this name.
360 `path_keyword` : str
361 If not None (the default), the handler function will receive
362 the object-path of the sending object as a keyword argument
363 with this name.
364 `message_keyword` : str
365 If not None (the default), the handler function will receive
366 the `dbus.lowlevel.SignalMessage` as a keyword argument with
367 this name.
368 `arg...` : unicode or UTF-8 str
369 If there are additional keyword parameters of the form
370 ``arg``\ *n*, match only signals where the *n*\ th argument
371 is the value given for that keyword parameter. As of this
372 time only string arguments can be matched (in particular,
373 object paths and signatures can't).
374 `named_service` : str
375 A deprecated alias for `bus_name`.
377 self._require_main_loop()
379 named_service = keywords.pop('named_service', None)
380 if named_service is not None:
381 if bus_name is not None:
382 raise TypeError('bus_name and named_service cannot both be '
383 'specified')
384 bus_name = named_service
385 from warnings import warn
386 warn('Passing the named_service parameter to add_signal_receiver '
387 'by name is deprecated: please use positional parameters',
388 DeprecationWarning, stacklevel=2)
390 match = SignalMatch(self, bus_name, path, dbus_interface,
391 signal_name, handler_function, **keywords)
392 by_interface = self._signal_recipients_by_object_path.setdefault(path,
394 by_member = by_interface.setdefault(dbus_interface, {})
395 matches = by_member.setdefault(signal_name, [])
397 # make sure nobody is currently manipulating the list
398 self._signals_lock.acquire()
399 try:
400 matches.append(match)
401 finally:
402 self._signals_lock.release()
403 return match
405 def _iter_easy_matches(self, path, dbus_interface, member):
406 if path is not None:
407 path_keys = (None, path)
408 else:
409 path_keys = (None,)
410 if dbus_interface is not None:
411 interface_keys = (None, dbus_interface)
412 else:
413 interface_keys = (None,)
414 if member is not None:
415 member_keys = (None, member)
416 else:
417 member_keys = (None,)
419 for path in path_keys:
420 by_interface = self._signal_recipients_by_object_path.get(path,
421 None)
422 if by_interface is None:
423 continue
424 for dbus_interface in interface_keys:
425 by_member = by_interface.get(dbus_interface, None)
426 if by_member is None:
427 continue
428 for member in member_keys:
429 matches = by_member.get(member, None)
430 if matches is None:
431 continue
432 for m in matches:
433 yield m
435 def remove_signal_receiver(self, handler_or_match,
436 signal_name=None,
437 dbus_interface=None,
438 bus_name=None,
439 path=None,
440 **keywords):
441 named_service = keywords.pop('named_service', None)
442 if named_service is not None:
443 if bus_name is not None:
444 raise TypeError('bus_name and named_service cannot both be '
445 'specified')
446 bus_name = named_service
447 from warnings import warn
448 warn('Passing the named_service parameter to '
449 'remove_signal_receiver by name is deprecated: please use '
450 'positional parameters',
451 DeprecationWarning, stacklevel=2)
453 by_interface = self._signal_recipients_by_object_path.get(path, None)
454 if by_interface is None:
455 return
456 by_member = by_interface.get(dbus_interface, None)
457 if by_member is None:
458 return
459 matches = by_member.get(signal_name, None)
460 if matches is None:
461 return
462 self._signals_lock.acquire()
463 try:
464 new = []
465 for match in matches:
466 if (handler_or_match is match
467 or match.matches_removal_spec(bus_name,
468 path,
469 dbus_interface,
470 signal_name,
471 handler_or_match,
472 **keywords)):
473 self._clean_up_signal_match(match)
474 else:
475 new.append(match)
476 by_member[signal_name] = new
477 finally:
478 self._signals_lock.release()
480 def _clean_up_signal_match(self, match):
481 # Called with the signals lock held
482 pass
484 def _signal_func(self, message):
485 """D-Bus filter function. Handle signals by dispatching to Python
486 callbacks kept in the match-rule tree.
489 if not isinstance(message, SignalMessage):
490 return HANDLER_RESULT_NOT_YET_HANDLED
492 dbus_interface = message.get_interface()
493 path = message.get_path()
494 signal_name = message.get_member()
496 for match in self._iter_easy_matches(path, dbus_interface,
497 signal_name):
498 match.maybe_handle_message(message)
499 return HANDLER_RESULT_NOT_YET_HANDLED
501 def call_async(self, bus_name, object_path, dbus_interface, method,
502 signature, args, reply_handler, error_handler,
503 timeout=-1.0, utf8_strings=False, byte_arrays=False,
504 require_main_loop=True):
505 """Call the given method, asynchronously.
507 If the reply_handler is None, successful replies will be ignored.
508 If the error_handler is None, failures will be ignored. If both
509 are None, the implementation may request that no reply is sent.
511 :Returns: The dbus.lowlevel.PendingCall.
513 if object_path == LOCAL_PATH:
514 raise DBusException('Methods may not be called on the reserved '
515 'path %s' % LOCAL_PATH)
516 if dbus_interface == LOCAL_IFACE:
517 raise DBusException('Methods may not be called on the reserved '
518 'interface %s' % LOCAL_IFACE)
519 # no need to validate other args - MethodCallMessage ctor will do
521 get_args_opts = {'utf8_strings': utf8_strings,
522 'byte_arrays': byte_arrays}
524 message = MethodCallMessage(destination=bus_name,
525 path=object_path,
526 interface=dbus_interface,
527 method=method)
528 # Add the arguments to the function
529 try:
530 message.append(signature=signature, *args)
531 except Exception, e:
532 logging.basicConfig()
533 _logger.error('Unable to set arguments %r according to '
534 'signature %r: %s: %s',
535 args, signature, e.__class__, e)
536 raise
538 if reply_handler is None and error_handler is None:
539 # we don't care what happens, so just send it
540 self.send_message(message)
541 return
543 if reply_handler is None:
544 reply_handler = _noop
545 if error_handler is None:
546 error_handler = _noop
548 def msg_reply_handler(message):
549 if isinstance(message, MethodReturnMessage):
550 reply_handler(*message.get_args_list(**get_args_opts))
551 elif isinstance(message, ErrorMessage):
552 error_handler(DBusException(name=message.get_error_name(),
553 *message.get_args_list()))
554 else:
555 error_handler(TypeError('Unexpected type for reply '
556 'message: %r' % message))
557 return self.send_message_with_reply(message, msg_reply_handler,
558 timeout/1000.0,
559 require_main_loop=require_main_loop)
561 def call_blocking(self, bus_name, object_path, dbus_interface, method,
562 signature, args, timeout=-1.0, utf8_strings=False,
563 byte_arrays=False):
564 """Call the given method, synchronously.
566 if object_path == LOCAL_PATH:
567 raise DBusException('Methods may not be called on the reserved '
568 'path %s' % LOCAL_PATH)
569 if dbus_interface == LOCAL_IFACE:
570 raise DBusException('Methods may not be called on the reserved '
571 'interface %s' % LOCAL_IFACE)
572 # no need to validate other args - MethodCallMessage ctor will do
574 get_args_opts = {'utf8_strings': utf8_strings,
575 'byte_arrays': byte_arrays}
577 message = MethodCallMessage(destination=bus_name,
578 path=object_path,
579 interface=dbus_interface,
580 method=method)
581 # Add the arguments to the function
582 try:
583 message.append(signature=signature, *args)
584 except Exception, e:
585 logging.basicConfig()
586 _logger.error('Unable to set arguments %r according to '
587 'signature %r: %s: %s',
588 args, signature, e.__class__, e)
589 raise
591 # make a blocking call
592 reply_message = self.send_message_with_reply_and_block(
593 message, timeout)
594 args_list = reply_message.get_args_list(**get_args_opts)
595 if len(args_list) == 0:
596 return None
597 elif len(args_list) == 1:
598 return args_list[0]
599 else:
600 return tuple(args_list)