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 488 if new: 489 by_member[signal_name] = new 490 else: 491 del by_member[signal_name] 492 if not by_member: 493 del by_interface[dbus_interface] 494 if not by_interface: 495 del self._signal_recipients_by_object_path[path] 496 finally: 497 self._signals_lock.release() 498 499 for match in deletions: 500 self._clean_up_signal_match(match)
501
502 - def _clean_up_signal_match(self, match):
503 # Now called without the signals lock held (it was held in <= 0.81.0) 504 pass
505
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. 509 """ 510 511 if not isinstance(message, SignalMessage): 512 return HANDLER_RESULT_NOT_YET_HANDLED 513 514 dbus_interface = message.get_interface() 515 path = message.get_path() 516 signal_name = message.get_member() 517 518 for match in self._iter_easy_matches(path, dbus_interface, 519 signal_name): 520 match.maybe_handle_message(message) 521 522 if (dbus_interface == LOCAL_IFACE and 523 path == LOCAL_PATH and 524 signal_name == 'Disconnected'): 525 for cb in self.__call_on_disconnection: 526 try: 527 cb(self) 528 except Exception, e: 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) 533 534 return HANDLER_RESULT_NOT_YET_HANDLED
535
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. 541 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. 545 546 :Returns: The dbus.lowlevel.PendingCall. 547 :Since: 0.81.0 548 """ 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 556 557 get_args_opts = {'utf8_strings': utf8_strings, 558 'byte_arrays': byte_arrays} 559 560 message = MethodCallMessage(destination=bus_name, 561 path=object_path, 562 interface=dbus_interface, 563 method=method) 564 # Add the arguments to the function 565 try: 566 message.append(signature=signature, *args) 567 except Exception, e: 568 logging.basicConfig() 569 _logger.error('Unable to set arguments %r according to ' 570 'signature %r: %s: %s', 571 args, signature, e.__class__, e) 572 raise 573 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) 577 return 578 579 if reply_handler is None: 580 reply_handler = _noop 581 if error_handler is None: 582 error_handler = _noop 583 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())) 590 else: 591 error_handler(TypeError('Unexpected type for reply ' 592 'message: %r' % message))
593 return self.send_message_with_reply(message, msg_reply_handler, 594 timeout, 595 require_main_loop=require_main_loop)
596
597 - def call_blocking(self, bus_name, object_path, dbus_interface, method, 598 signature, args, timeout=-1.0, utf8_strings=False, 599 byte_arrays=False):
600 """Call the given method, synchronously. 601 :Since: 0.81.0 602 """ 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 610 611 get_args_opts = {'utf8_strings': utf8_strings, 612 'byte_arrays': byte_arrays} 613 614 message = MethodCallMessage(destination=bus_name, 615 path=object_path, 616 interface=dbus_interface, 617 method=method) 618 # Add the arguments to the function 619 try: 620 message.append(signature=signature, *args) 621 except Exception, e: 622 logging.basicConfig() 623 _logger.error('Unable to set arguments %r according to ' 624 'signature %r: %s: %s', 625 args, signature, e.__class__, e) 626 raise 627 628 # make a blocking call 629 reply_message = self.send_message_with_reply_and_block( 630 message, timeout) 631 args_list = reply_message.get_args_list(**get_args_opts) 632 if len(args_list) == 0: 633 return None 634 elif len(args_list) == 1: 635 return args_list[0] 636 else: 637 return tuple(args_list)
638
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 642 disconnected. 643 644 :Since: 0.83.0 645 """ 646 self.__call_on_disconnection.append(callable)
647