Package dbus :: Module connection
[hide private]
[frames] | no frames]

Source Code for Module dbus.connection

  1  # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/> 
  2  # 
  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: 
 10  # 
 11  # The above copyright notice and this permission notice shall be 
 12  # included in all copies or substantial portions of the Software. 
 13  # 
 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. 
 22   
 23  __all__ = ('Connection', 'SignalMatch') 
 24  __docformat__ = 'reStructuredText' 
 25   
 26  import logging 
 27  try: 
 28      import thread 
 29  except ImportError: 
 30      import dummy_thread as thread 
 31  import weakref 
 32   
 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, \ 
 38                             UTF8String 
 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 
 43   
 44   
 45  _logger = logging.getLogger('dbus.connection') 
 46   
 47   
48 -def _noop(*args, **kwargs):
49 pass
50 51
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') 59
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, 65 **kwargs):
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) 74 75 self._rule = None 76 self._conn_weakref = weakref.ref(conn) 77 self._sender = sender 78 self._interface = dbus_interface 79 self._member = member 80 self._path = object_path 81 self._handler = handler 82 83 # if the connection is actually a bus, it's responsible for changing 84 # this later 85 self._sender_name_owner = sender 86 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 95 96 self._args_match = kwargs 97 if not kwargs: 98 self._int_args_match = None 99 else: 100 self._int_args_match = {} 101 for kwarg in kwargs: 102 if not kwarg.startswith('arg'): 103 raise TypeError('SignalMatch: unknown keyword argument %s' 104 % kwarg) 105 try: 106 index = int(kwarg[3:]) 107 except ValueError: 108 raise TypeError('SignalMatch: unknown keyword argument %s' 109 % kwarg) 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]
114
115 - def __hash__(self):
116 """SignalMatch objects are compared by identity.""" 117 return hash(id(self))
118
119 - def __eq__(self, other):
120 """SignalMatch objects are compared by identity.""" 121 return self is other
122
123 - def __ne__(self, other):
124 """SignalMatch objects are compared by identity.""" 125 return self is not other
126 127 sender = property(lambda self: self._sender) 128
129 - def __str__(self):
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)) 143 144 self._rule = ','.join(rule) 145 146 return self._rule
147
148 - def __repr__(self):
149 return ('<%s at %x "%s" on conn %r>' 150 % (self.__class__, id(self), self._rule, self._conn_weakref()))
151
152 - def set_sender_name_owner(self, new_name):
153 self._sender_name_owner = new_name
154
155 - def matches_removal_spec(self, sender, object_path, 156 dbus_interface, member, handler, **kwargs):
157 if handler not in (None, self._handler): 158 return False 159 if sender != self._sender: 160 return False 161 if object_path != self._path: 162 return False 163 if dbus_interface != self._interface: 164 return False 165 if member != self._member: 166 return False 167 if kwargs != self._args_match: 168 return False 169 return True
170
171 - def maybe_handle_message(self, message):
172 args = None 173 174 # these haven't been checked yet by the match tree 175 if self._sender_name_owner not in (None, message.get_sender()): 176 return False 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): 184 return False 185 186 # these have likely already been checked by the match tree 187 if self._member not in (None, message.get_member()): 188 return False 189 if self._interface not in (None, message.get_interface()): 190 return False 191 if self._path not in (None, message.get_path()): 192 return False 193 194 try: 195 # minor optimization: if we already extracted the args with the 196 # right calling convention to do the args match, don't bother 197 # doing so again 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) 201 kwargs = {} 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) 215 except: 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) 219 220 return True
221
222 - def remove(self):
223 conn = self._conn_weakref() 224 # do nothing if the connection has already vanished 225 if conn is not None: 226 conn.remove_signal_receiver(self, self._member, 227 self._interface, self._sender, 228 self._path, 229 **self._args_match)
230 231
232 -class Connection(_Connection):
233 """A connection to another application. In this base class there is 234 assumed to be no bus daemon. 235 236 :Since: 0.81.0 237 """ 238 239 ProxyObjectClass = ProxyObject 240
241 - def __init__(self, *args, **kwargs):
242 super(Connection, self).__init__(*args, **kwargs) 243 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 248 249 self.__call_on_disconnection = [] 250 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.""" 254 255 self._signals_lock = thread.allocate_lock() 256 """Lock used to protect signal data structures""" 257 258 self.add_message_filter(self.__class__._signal_func)
259
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. 263 264 If the name is already unique or this connection is not to a 265 bus daemon, just return it. 266 267 :Returns: a bus name. If the given `bus_name` exists, the returned 268 name identifies its current owner; otherwise the returned name 269 does not exist. 270 :Raises DBusException: if the implementation has failed 271 to activate the given bus name. 272 :Since: 0.81.0 273 """ 274 return bus_name
275
276 - def get_object(self, bus_name=None, object_path=None, introspect=True, 277 **kwargs):
278 """Return a local proxy for the given remote object. 279 280 Method calls on the proxy are translated into method calls on the 281 remote object. 282 283 :Parameters: 284 `bus_name` : str 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. 288 `object_path` : str 289 The object path of the desired object 290 `introspect` : bool 291 If true (default), attempt to introspect the remote 292 object to find out supported methods and their signatures 293 294 :Returns: a `dbus.proxies.ProxyObject` 295 """ 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 ' 300 'be specified') 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 306 if kwargs: 307 raise TypeError('get_object does not take these keyword ' 308 'arguments: %s' % ', '.join(kwargs.iterkeys())) 309 310 return self.ProxyObjectClass(self, bus_name, object_path, 311 introspect=introspect)
312
313 - def add_signal_receiver(self, handler_function, 314 signal_name=None, 315 dbus_interface=None, 316 bus_name=None, 317 path=None, 318 **keywords):
319 """Arrange for the given function to be called when a signal matching 320 the parameters is received. 321 322 :Parameters: 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. 328 `signal_name` : str 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 333 `bus_name` : str 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 336 any sender. 337 `path` : str 338 The object path of the object which must have emitted the 339 signal; None (the default) matches any object path 340 :Keywords: 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 346 unicode). 347 `byte_arrays` : bool 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: 352 a list of ints). 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. 367 `path_keyword` : str 368 If not None (the default), the handler function will receive 369 the object-path of the sending object as a keyword argument 370 with this name. 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 374 this name. 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`. 383 """ 384 self._require_main_loop() 385 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 ' 390 'specified') 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) 396 397 match = SignalMatch(self, bus_name, path, dbus_interface, 398 signal_name, handler_function, **keywords) 399 400 self._signals_lock.acquire() 401 try: 402 by_interface = self._signal_recipients_by_object_path.setdefault( 403 path, {}) 404 by_member = by_interface.setdefault(dbus_interface, {}) 405 matches = by_member.setdefault(signal_name, []) 406 407 matches.append(match) 408 finally: 409 self._signals_lock.release() 410 411 return match
412
413 - def _iter_easy_matches(self, path, dbus_interface, member):
414 if path is not None: 415 path_keys = (None, path) 416 else: 417 path_keys = (None,) 418 if dbus_interface is not None: 419 interface_keys = (None, dbus_interface) 420 else: 421 interface_keys = (None,) 422 if member is not None: 423 member_keys = (None, member) 424 else: 425 member_keys = (None,) 426 427 for path in path_keys: 428 by_interface = self._signal_recipients_by_object_path.get(path, 429 None) 430 if by_interface is None: 431 continue 432 for dbus_interface in interface_keys: 433 by_member = by_interface.get(dbus_interface, None) 434 if by_member is None: 435 continue 436 for member in member_keys: 437 matches = by_member.get(member, None) 438 if matches is None: 439 continue 440 for m in matches: 441 yield m
442
443 - def remove_signal_receiver(self, handler_or_match, 444 signal_name=None, 445 dbus_interface=None, 446 bus_name=None, 447 path=None, 448 **keywords):
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 ' 453 'specified') 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) 460 461 new = [] 462 deletions = [] 463 self._signals_lock.acquire() 464 try: 465 by_interface = self._signal_recipients_by_object_path.get(path, 466 None) 467 if by_interface is None: 468 return 469 by_member = by_interface.get(dbus_interface, None) 470 if by_member is None: 471 return 472 matches = by_member.get(signal_name, None) 473 if matches is None: 474 return 475 476 for match in matches: 477 if (handler_or_match is match 478 or match.matches_removal_spec(bus_name, 479 path, 480 dbus_interface, 481 signal_name, 482 handler_or_match, 483 **keywords)): 484 deletions.append(match) 485 else: 486 new.append(match) 487 by_member[signal_name] = new 488 finally: 489 self._signals_lock.release() 490 491 for match in deletions: 492 self._clean_up_signal_match(match)
493
494 - def _clean_up_signal_match(self, match):
495 # Now called without the signals lock held (it was held in <= 0.81.0) 496 pass
497
498 - def _signal_func(self, message):
499 """D-Bus filter function. Handle signals by dispatching to Python 500 callbacks kept in the match-rule tree. 501 """ 502 503 if not isinstance(message, SignalMessage): 504 return HANDLER_RESULT_NOT_YET_HANDLED 505 506 dbus_interface = message.get_interface() 507 path = message.get_path() 508 signal_name = message.get_member() 509 510 for match in self._iter_easy_matches(path, dbus_interface, 511 signal_name): 512 match.maybe_handle_message(message) 513 514 if (dbus_interface == LOCAL_IFACE and 515 path == LOCAL_PATH and 516 signal_name == 'Disconnected'): 517 for cb in self.__call_on_disconnection: 518 try: 519 cb(self) 520 except Exception, e: 521 # basicConfig is a no-op if logging is already configured 522 logging.basicConfig() 523 _logger.error('Exception in handler for Disconnected ' 524 'signal:', exc_info=1) 525 526 return HANDLER_RESULT_NOT_YET_HANDLED
527
528 - def call_async(self, bus_name, object_path, dbus_interface, method, 529 signature, args, reply_handler, error_handler, 530 timeout=-1.0, utf8_strings=False, byte_arrays=False, 531 require_main_loop=True):
532 """Call the given method, asynchronously. 533 534 If the reply_handler is None, successful replies will be ignored. 535 If the error_handler is None, failures will be ignored. If both 536 are None, the implementation may request that no reply is sent. 537 538 :Returns: The dbus.lowlevel.PendingCall. 539 :Since: 0.81.0 540 """ 541 if object_path == LOCAL_PATH: 542 raise DBusException('Methods may not be called on the reserved ' 543 'path %s' % LOCAL_PATH) 544 if dbus_interface == LOCAL_IFACE: 545 raise DBusException('Methods may not be called on the reserved ' 546 'interface %s' % LOCAL_IFACE) 547 # no need to validate other args - MethodCallMessage ctor will do 548 549 get_args_opts = {'utf8_strings': utf8_strings, 550 'byte_arrays': byte_arrays} 551 552 message = MethodCallMessage(destination=bus_name, 553 path=object_path, 554 interface=dbus_interface, 555 method=method) 556 # Add the arguments to the function 557 try: 558 message.append(signature=signature, *args) 559 except Exception, e: 560 logging.basicConfig() 561 _logger.error('Unable to set arguments %r according to ' 562 'signature %r: %s: %s', 563 args, signature, e.__class__, e) 564 raise 565 566 if reply_handler is None and error_handler is None: 567 # we don't care what happens, so just send it 568 self.send_message(message) 569 return 570 571 if reply_handler is None: 572 reply_handler = _noop 573 if error_handler is None: 574 error_handler = _noop 575 576 def msg_reply_handler(message): 577 if isinstance(message, MethodReturnMessage): 578 reply_handler(*message.get_args_list(**get_args_opts)) 579 elif isinstance(message, ErrorMessage): 580 error_handler(DBusException(name=message.get_error_name(), 581 *message.get_args_list())) 582 else: 583 error_handler(TypeError('Unexpected type for reply ' 584 'message: %r' % message))
585 return self.send_message_with_reply(message, msg_reply_handler, 586 timeout, 587 require_main_loop=require_main_loop)
588
589 - def call_blocking(self, bus_name, object_path, dbus_interface, method, 590 signature, args, timeout=-1.0, utf8_strings=False, 591 byte_arrays=False):
592 """Call the given method, synchronously. 593 :Since: 0.81.0 594 """ 595 if object_path == LOCAL_PATH: 596 raise DBusException('Methods may not be called on the reserved ' 597 'path %s' % LOCAL_PATH) 598 if dbus_interface == LOCAL_IFACE: 599 raise DBusException('Methods may not be called on the reserved ' 600 'interface %s' % LOCAL_IFACE) 601 # no need to validate other args - MethodCallMessage ctor will do 602 603 get_args_opts = {'utf8_strings': utf8_strings, 604 'byte_arrays': byte_arrays} 605 606 message = MethodCallMessage(destination=bus_name, 607 path=object_path, 608 interface=dbus_interface, 609 method=method) 610 # Add the arguments to the function 611 try: 612 message.append(signature=signature, *args) 613 except Exception, e: 614 logging.basicConfig() 615 _logger.error('Unable to set arguments %r according to ' 616 'signature %r: %s: %s', 617 args, signature, e.__class__, e) 618 raise 619 620 # make a blocking call 621 reply_message = self.send_message_with_reply_and_block( 622 message, timeout) 623 args_list = reply_message.get_args_list(**get_args_opts) 624 if len(args_list) == 0: 625 return None 626 elif len(args_list) == 1: 627 return args_list[0] 628 else: 629 return tuple(args_list)
630
631 - def call_on_disconnection(self, callable):
632 """Arrange for `callable` to be called with one argument (this 633 Connection object) when the Connection becomes 634 disconnected. 635 636 :Since: 0.83.0 637 """ 638 self.__call_on_disconnection.append(callable)
639