1 # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
3 # Permission is hereby granted, free of charge, to any person
4 # obtaining a copy of this software and associated documentation
5 # files (the "Software"), to deal in the Software without
6 # restriction, including without limitation the rights to use, copy,
7 # modify, merge, publish, distribute, sublicense, and/or sell copies
8 # of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
11 # The above copyright notice and this permission notice shall be
12 # included in all copies or substantial portions of the Software.
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 # DEALINGS IN THE SOFTWARE.
23 __all__ = ('Connection', 'SignalMatch')
24 __docformat__ = 'reStructuredText'
30 import dummy_thread as thread
33 from _dbus_bindings import Connection as _Connection, \
34 LOCAL_PATH, LOCAL_IFACE, \
35 validate_interface_name, validate_member_name,\
36 validate_bus_name, validate_object_path,\
37 validate_error_name, \
39 from dbus.exceptions import DBusException
40 from dbus.lowlevel import ErrorMessage, MethodCallMessage, SignalMessage, \
41 MethodReturnMessage, HANDLER_RESULT_NOT_YET_HANDLED
42 from dbus.proxies import ProxyObject
45 _logger = logging.getLogger('dbus.connection')
48 def _noop(*args, **kwargs):
52 class SignalMatch(object):
53 __slots__ = ('_sender_name_owner', '_member', '_interface', '_sender',
54 '_path', '_handler', '_args_match', '_rule',
55 '_utf8_strings', '_byte_arrays', '_conn_weakref',
56 '_destination_keyword', '_interface_keyword',
57 '_message_keyword', '_member_keyword',
58 '_sender_keyword', '_path_keyword', '_int_args_match')
60 def __init__(self, conn, sender, object_path, dbus_interface,
61 member, handler, utf8_strings=False, byte_arrays=False,
62 sender_keyword=None, path_keyword=None,
63 interface_keyword=None, member_keyword=None,
64 message_keyword=None, destination_keyword=None,
66 if member is not None:
67 validate_member_name(member)
68 if dbus_interface is not None:
69 validate_interface_name(dbus_interface)
70 if sender is not None:
71 validate_bus_name(sender)
72 if object_path is not None:
73 validate_object_path(object_path)
76 self._conn_weakref = weakref.ref(conn)
78 self._interface = dbus_interface
80 self._path = object_path
81 self._handler = handler
83 # if the connection is actually a bus, it's responsible for changing
85 self._sender_name_owner = sender
87 self._utf8_strings = utf8_strings
88 self._byte_arrays = byte_arrays
89 self._sender_keyword = sender_keyword
90 self._path_keyword = path_keyword
91 self._member_keyword = member_keyword
92 self._interface_keyword = interface_keyword
93 self._message_keyword = message_keyword
94 self._destination_keyword = destination_keyword
96 self._args_match = kwargs
98 self._int_args_match = None
100 self._int_args_match = {}
102 if not kwarg.startswith('arg'):
103 raise TypeError('SignalMatch: unknown keyword argument %s'
106 index = int(kwarg[3:])
108 raise TypeError('SignalMatch: unknown keyword argument %s'
110 if index < 0 or index > 63:
111 raise TypeError('SignalMatch: arg match index must be in '
112 'range(64), not %d' % index)
113 self._int_args_match[index] = kwargs[kwarg]
116 """SignalMatch objects are compared by identity."""
117 return hash(id(self))
119 def __eq__(self, other):
120 """SignalMatch objects are compared by identity."""
123 def __ne__(self, other):
124 """SignalMatch objects are compared by identity."""
125 return self is not other
127 sender = property(lambda self: self._sender)
130 if self._rule is None:
131 rule = ["type='signal'"]
132 if self._sender is not None:
133 rule.append("sender='%s'" % self._sender)
134 if self._path is not None:
135 rule.append("path='%s'" % self._path)
136 if self._interface is not None:
137 rule.append("interface='%s'" % self._interface)
138 if self._member is not None:
139 rule.append("member='%s'" % self._member)
140 if self._int_args_match is not None:
141 for index, value in self._int_args_match.iteritems():
142 rule.append("arg%d='%s'" % (index, value))
144 self._rule = ','.join(rule)
149 return ('<%s at %x "%s" on conn %r>'
150 % (self.__class__, id(self), self._rule, self._conn_weakref()))
152 def set_sender_name_owner(self, new_name):
153 self._sender_name_owner = new_name
155 def matches_removal_spec(self, sender, object_path,
156 dbus_interface, member, handler, **kwargs):
157 if handler not in (None, self._handler):
159 if sender != self._sender:
161 if object_path != self._path:
163 if dbus_interface != self._interface:
165 if member != self._member:
167 if kwargs != self._args_match:
171 def maybe_handle_message(self, message):
174 # these haven't been checked yet by the match tree
175 if self._sender_name_owner not in (None, message.get_sender()):
177 if self._int_args_match is not None:
178 # extracting args with utf8_strings and byte_arrays is less work
179 args = message.get_args_list(utf8_strings=True, byte_arrays=True)
180 for index, value in self._int_args_match.iteritems():
181 if (index >= len(args)
182 or not isinstance(args[index], UTF8String)
183 or args[index] != value):
186 # these have likely already been checked by the match tree
187 if self._member not in (None, message.get_member()):
189 if self._interface not in (None, message.get_interface()):
191 if self._path not in (None, message.get_path()):
195 # minor optimization: if we already extracted the args with the
196 # right calling convention to do the args match, don't bother
198 if args is None or not self._utf8_strings or not self._byte_arrays:
199 args = message.get_args_list(utf8_strings=self._utf8_strings,
200 byte_arrays=self._byte_arrays)
202 if self._sender_keyword is not None:
203 kwargs[self._sender_keyword] = message.get_sender()
204 if self._destination_keyword is not None:
205 kwargs[self._destination_keyword] = message.get_destination()
206 if self._path_keyword is not None:
207 kwargs[self._path_keyword] = message.get_path()
208 if self._member_keyword is not None:
209 kwargs[self._member_keyword] = message.get_member()
210 if self._interface_keyword is not None:
211 kwargs[self._interface_keyword] = message.get_interface()
212 if self._message_keyword is not None:
213 kwargs[self._message_keyword] = message
214 self._handler(*args, **kwargs)
216 # basicConfig is a no-op if logging is already configured
217 logging.basicConfig()
218 _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
223 conn = self._conn_weakref()
224 # do nothing if the connection has already vanished
226 conn.remove_signal_receiver(self, self._member,
227 self._interface, self._sender,
232 class Connection(_Connection):
233 """A connection to another application. In this base class there is
234 assumed to be no bus daemon.
239 ProxyObjectClass = ProxyObject
241 def __init__(self, *args, **kwargs):
242 super(Connection, self).__init__(*args, **kwargs)
244 # this if-block is needed because shared bus connections can be
245 # __init__'ed more than once
246 if not hasattr(self, '_dbus_Connection_initialized'):
247 self._dbus_Connection_initialized = 1
249 self.__call_on_disconnection = []
251 self._signal_recipients_by_object_path = {}
252 """Map from object path to dict mapping dbus_interface to dict
253 mapping member to list of SignalMatch objects."""
255 self._signals_lock = thread.allocate_lock()
256 """Lock used to protect signal data structures"""
258 self.add_message_filter(self.__class__._signal_func)
260 def activate_name_owner(self, bus_name):
261 """Return the unique name for the given bus name, activating it
262 if necessary and possible.
264 If the name is already unique or this connection is not to a
265 bus daemon, just return it.
267 :Returns: a bus name. If the given `bus_name` exists, the returned
268 name identifies its current owner; otherwise the returned name
270 :Raises DBusException: if the implementation has failed
271 to activate the given bus name.
276 def get_object(self, bus_name=None, object_path=None, introspect=True,
278 """Return a local proxy for the given remote object.
280 Method calls on the proxy are translated into method calls on the
285 A bus name (either the unique name or a well-known name)
286 of the application owning the object. The keyword argument
287 named_service is a deprecated alias for this.
289 The object path of the desired object
291 If true (default), attempt to introspect the remote
292 object to find out supported methods and their signatures
294 :Returns: a `dbus.proxies.ProxyObject`
296 named_service = kwargs.pop('named_service', None)
297 if named_service is not None:
298 if bus_name is not None:
299 raise TypeError('bus_name and named_service cannot both '
301 from warnings import warn
302 warn('Passing the named_service parameter to get_object by name '
303 'is deprecated: please use positional parameters',
304 DeprecationWarning, stacklevel=2)
305 bus_name = named_service
307 raise TypeError('get_object does not take these keyword '
308 'arguments: %s' % ', '.join(kwargs.iterkeys()))
310 return self.ProxyObjectClass(self, bus_name, object_path,
311 introspect=introspect)
313 def add_signal_receiver(self, handler_function,
319 """Arrange for the given function to be called when a signal matching
320 the parameters is received.
323 `handler_function` : callable
324 The function to be called. Its positional arguments will
325 be the arguments of the signal. By default it will receive
326 no keyword arguments, but see the description of
327 the optional keyword arguments below.
329 The signal name; None (the default) matches all names
330 `dbus_interface` : str
331 The D-Bus interface name with which to qualify the signal;
332 None (the default) matches all interface names
334 A bus name for the sender, which will be resolved to a
335 unique name if it is not already; None (the default) matches
338 The object path of the object which must have emitted the
339 signal; None (the default) matches any object path
341 `utf8_strings` : bool
342 If True, the handler function will receive any string
343 arguments as dbus.UTF8String objects (a subclass of str
344 guaranteed to be UTF-8). If False (default) it will receive
345 any string arguments as dbus.String objects (a subclass of
348 If True, the handler function will receive any byte-array
349 arguments as dbus.ByteArray objects (a subclass of str).
350 If False (default) it will receive any byte-array
351 arguments as a dbus.Array of dbus.Byte (subclasses of:
353 `sender_keyword` : str
354 If not None (the default), the handler function will receive
355 the unique name of the sending endpoint as a keyword
356 argument with this name.
357 `destination_keyword` : str
358 If not None (the default), the handler function will receive
359 the bus name of the destination (or None if the signal is a
360 broadcast, as is usual) as a keyword argument with this name.
361 `interface_keyword` : str
362 If not None (the default), the handler function will receive
363 the signal interface as a keyword argument with this name.
364 `member_keyword` : str
365 If not None (the default), the handler function will receive
366 the signal name as a keyword argument with this name.
368 If not None (the default), the handler function will receive
369 the object-path of the sending object as a keyword argument
371 `message_keyword` : str
372 If not None (the default), the handler function will receive
373 the `dbus.lowlevel.SignalMessage` as a keyword argument with
375 `arg...` : unicode or UTF-8 str
376 If there are additional keyword parameters of the form
377 ``arg``\ *n*, match only signals where the *n*\ th argument
378 is the value given for that keyword parameter. As of this
379 time only string arguments can be matched (in particular,
380 object paths and signatures can't).
381 `named_service` : str
382 A deprecated alias for `bus_name`.
384 self._require_main_loop()
386 named_service = keywords.pop('named_service', None)
387 if named_service is not None:
388 if bus_name is not None:
389 raise TypeError('bus_name and named_service cannot both be '
391 bus_name = named_service
392 from warnings import warn
393 warn('Passing the named_service parameter to add_signal_receiver '
394 'by name is deprecated: please use positional parameters',
395 DeprecationWarning, stacklevel=2)
397 match = SignalMatch(self, bus_name, path, dbus_interface,
398 signal_name, handler_function, **keywords)
400 self._signals_lock.acquire()
402 by_interface = self._signal_recipients_by_object_path.setdefault(
404 by_member = by_interface.setdefault(dbus_interface, {})
405 matches = by_member.setdefault(signal_name, [])
407 matches.append(match)
409 self._signals_lock.release()
413 def _iter_easy_matches(self, path, dbus_interface, member):
415 path_keys = (None, path)
418 if dbus_interface is not None:
419 interface_keys = (None, dbus_interface)
421 interface_keys = (None,)
422 if member is not None:
423 member_keys = (None, member)
425 member_keys = (None,)
427 for path in path_keys:
428 by_interface = self._signal_recipients_by_object_path.get(path,
430 if by_interface is None:
432 for dbus_interface in interface_keys:
433 by_member = by_interface.get(dbus_interface, None)
434 if by_member is None:
436 for member in member_keys:
437 matches = by_member.get(member, None)
443 def remove_signal_receiver(self, handler_or_match,
449 named_service = keywords.pop('named_service', None)
450 if named_service is not None:
451 if bus_name is not None:
452 raise TypeError('bus_name and named_service cannot both be '
454 bus_name = named_service
455 from warnings import warn
456 warn('Passing the named_service parameter to '
457 'remove_signal_receiver by name is deprecated: please use '
458 'positional parameters',
459 DeprecationWarning, stacklevel=2)
463 self._signals_lock.acquire()
465 by_interface = self._signal_recipients_by_object_path.get(path,
467 if by_interface is None:
469 by_member = by_interface.get(dbus_interface, None)
470 if by_member is None:
472 matches = by_member.get(signal_name, None)
476 for match in matches:
477 if (handler_or_match is match
478 or match.matches_removal_spec(bus_name,
484 deletions.append(match)
489 by_member[signal_name] = new
491 del by_member[signal_name]
493 del by_interface[dbus_interface]
495 del self._signal_recipients_by_object_path[path]
497 self._signals_lock.release()
499 for match in deletions:
500 self._clean_up_signal_match(match)
502 def _clean_up_signal_match(self, match):
503 # Now called without the signals lock held (it was held in <= 0.81.0)
506 def _signal_func(self, message):
507 """D-Bus filter function. Handle signals by dispatching to Python
508 callbacks kept in the match-rule tree.
511 if not isinstance(message, SignalMessage):
512 return HANDLER_RESULT_NOT_YET_HANDLED
514 dbus_interface = message.get_interface()
515 path = message.get_path()
516 signal_name = message.get_member()
518 for match in self._iter_easy_matches(path, dbus_interface,
520 match.maybe_handle_message(message)
522 if (dbus_interface == LOCAL_IFACE and
523 path == LOCAL_PATH and
524 signal_name == 'Disconnected'):
525 for cb in self.__call_on_disconnection:
529 # basicConfig is a no-op if logging is already configured
530 logging.basicConfig()
531 _logger.error('Exception in handler for Disconnected '
532 'signal:', exc_info=1)
534 return HANDLER_RESULT_NOT_YET_HANDLED
536 def call_async(self, bus_name, object_path, dbus_interface, method,
537 signature, args, reply_handler, error_handler,
538 timeout=-1.0, utf8_strings=False, byte_arrays=False,
539 require_main_loop=True):
540 """Call the given method, asynchronously.
542 If the reply_handler is None, successful replies will be ignored.
543 If the error_handler is None, failures will be ignored. If both
544 are None, the implementation may request that no reply is sent.
546 :Returns: The dbus.lowlevel.PendingCall.
549 if object_path == LOCAL_PATH:
550 raise DBusException('Methods may not be called on the reserved '
551 'path %s' % LOCAL_PATH)
552 if dbus_interface == LOCAL_IFACE:
553 raise DBusException('Methods may not be called on the reserved '
554 'interface %s' % LOCAL_IFACE)
555 # no need to validate other args - MethodCallMessage ctor will do
557 get_args_opts = {'utf8_strings': utf8_strings,
558 'byte_arrays': byte_arrays}
560 message = MethodCallMessage(destination=bus_name,
562 interface=dbus_interface,
564 # Add the arguments to the function
566 message.append(signature=signature, *args)
568 logging.basicConfig()
569 _logger.error('Unable to set arguments %r according to '
570 'signature %r: %s: %s',
571 args, signature, e.__class__, e)
574 if reply_handler is None and error_handler is None:
575 # we don't care what happens, so just send it
576 self.send_message(message)
579 if reply_handler is None:
580 reply_handler = _noop
581 if error_handler is None:
582 error_handler = _noop
584 def msg_reply_handler(message):
585 if isinstance(message, MethodReturnMessage):
586 reply_handler(*message.get_args_list(**get_args_opts))
587 elif isinstance(message, ErrorMessage):
588 error_handler(DBusException(name=message.get_error_name(),
589 *message.get_args_list()))
591 error_handler(TypeError('Unexpected type for reply '
592 'message: %r' % message))
593 return self.send_message_with_reply(message, msg_reply_handler,
595 require_main_loop=require_main_loop)
597 def call_blocking(self, bus_name, object_path, dbus_interface, method,
598 signature, args, timeout=-1.0, utf8_strings=False,
600 """Call the given method, synchronously.
603 if object_path == LOCAL_PATH:
604 raise DBusException('Methods may not be called on the reserved '
605 'path %s' % LOCAL_PATH)
606 if dbus_interface == LOCAL_IFACE:
607 raise DBusException('Methods may not be called on the reserved '
608 'interface %s' % LOCAL_IFACE)
609 # no need to validate other args - MethodCallMessage ctor will do
611 get_args_opts = {'utf8_strings': utf8_strings,
612 'byte_arrays': byte_arrays}
614 message = MethodCallMessage(destination=bus_name,
616 interface=dbus_interface,
618 # Add the arguments to the function
620 message.append(signature=signature, *args)
622 logging.basicConfig()
623 _logger.error('Unable to set arguments %r according to '
624 'signature %r: %s: %s',
625 args, signature, e.__class__, e)
628 # make a blocking call
629 reply_message = self.send_message_with_reply_and_block(
631 args_list = reply_message.get_args_list(**get_args_opts)
632 if len(args_list) == 0:
634 elif len(args_list) == 1:
637 return tuple(args_list)
639 def call_on_disconnection(self, callable):
640 """Arrange for `callable` to be called with one argument (this
641 Connection object) when the Connection becomes
646 self.__call_on_disconnection.append(callable)