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 # this if-block is needed because shared bus connections can be 243 # __init__'ed more than once 244 if not hasattr(self, '_dbus_Connection_initialized'): 245 self._dbus_Connection_initialized = 1 246 247 self.__call_on_disconnection = [] 248 249 self._signal_recipients_by_object_path = {} 250 """Map from object path to dict mapping dbus_interface to dict 251 mapping member to list of SignalMatch objects.""" 252 253 self._signals_lock = thread.allocate_lock() 254 """Lock used to protect signal data structures""" 255 256 self.add_message_filter(self.__class__._signal_func)
257
258 - def activate_name_owner(self, bus_name):
259 """Return the unique name for the given bus name, activating it 260 if necessary and possible. 261 262 If the name is already unique or this connection is not to a 263 bus daemon, just return it. 264 265 :Returns: a bus name. If the given `bus_name` exists, the returned 266 name identifies its current owner; otherwise the returned name 267 does not exist. 268 :Raises DBusException: if the implementation has failed 269 to activate the given bus name. 270 :Since: 0.81.0 271 """ 272 return bus_name
273
274 - def get_object(self, bus_name=None, object_path=None, introspect=True, 275 **kwargs):
276 """Return a local proxy for the given remote object. 277 278 Method calls on the proxy are translated into method calls on the 279 remote object. 280 281 :Parameters: 282 `bus_name` : str 283 A bus name (either the unique name or a well-known name) 284 of the application owning the object. The keyword argument 285 named_service is a deprecated alias for this. 286 `object_path` : str 287 The object path of the desired object 288 `introspect` : bool 289 If true (default), attempt to introspect the remote 290 object to find out supported methods and their signatures 291 292 :Returns: a `dbus.proxies.ProxyObject` 293 """ 294 named_service = kwargs.pop('named_service', None) 295 if named_service is not None: 296 if bus_name is not None: 297 raise TypeError('bus_name and named_service cannot both ' 298 'be specified') 299 from warnings import warn 300 warn('Passing the named_service parameter to get_object by name ' 301 'is deprecated: please use positional parameters', 302 DeprecationWarning, stacklevel=2) 303 bus_name = named_service 304 if kwargs: 305 raise TypeError('get_object does not take these keyword ' 306 'arguments: %s' % ', '.join(kwargs.iterkeys())) 307 308 return self.ProxyObjectClass(self, bus_name, object_path, 309 introspect=introspect)
310
311 - def add_signal_receiver(self, handler_function, 312 signal_name=None, 313 dbus_interface=None, 314 bus_name=None, 315 path=None, 316 **keywords):
317 """Arrange for the given function to be called when a signal matching 318 the parameters is received. 319 320 :Parameters: 321 `handler_function` : callable 322 The function to be called. Its positional arguments will 323 be the arguments of the signal. By default it will receive 324 no keyword arguments, but see the description of 325 the optional keyword arguments below. 326 `signal_name` : str 327 The signal name; None (the default) matches all names 328 `dbus_interface` : str 329 The D-Bus interface name with which to qualify the signal; 330 None (the default) matches all interface names 331 `bus_name` : str 332 A bus name for the sender, which will be resolved to a 333 unique name if it is not already; None (the default) matches 334 any sender. 335 `path` : str 336 The object path of the object which must have emitted the 337 signal; None (the default) matches any object path 338 :Keywords: 339 `utf8_strings` : bool 340 If True, the handler function will receive any string 341 arguments as dbus.UTF8String objects (a subclass of str 342 guaranteed to be UTF-8). If False (default) it will receive 343 any string arguments as dbus.String objects (a subclass of 344 unicode). 345 `byte_arrays` : bool 346 If True, the handler function will receive any byte-array 347 arguments as dbus.ByteArray objects (a subclass of str). 348 If False (default) it will receive any byte-array 349 arguments as a dbus.Array of dbus.Byte (subclasses of: 350 a list of ints). 351 `sender_keyword` : str 352 If not None (the default), the handler function will receive 353 the unique name of the sending endpoint as a keyword 354 argument with this name. 355 `destination_keyword` : str 356 If not None (the default), the handler function will receive 357 the bus name of the destination (or None if the signal is a 358 broadcast, as is usual) as a keyword argument with this name. 359 `interface_keyword` : str 360 If not None (the default), the handler function will receive 361 the signal interface as a keyword argument with this name. 362 `member_keyword` : str 363 If not None (the default), the handler function will receive 364 the signal name as a keyword argument with this name. 365 `path_keyword` : str 366 If not None (the default), the handler function will receive 367 the object-path of the sending object as a keyword argument 368 with this name. 369 `message_keyword` : str 370 If not None (the default), the handler function will receive 371 the `dbus.lowlevel.SignalMessage` as a keyword argument with 372 this name. 373 `arg...` : unicode or UTF-8 str 374 If there are additional keyword parameters of the form 375 ``arg``\ *n*, match only signals where the *n*\ th argument 376 is the value given for that keyword parameter. As of this 377 time only string arguments can be matched (in particular, 378 object paths and signatures can't). 379 `named_service` : str 380 A deprecated alias for `bus_name`. 381 """ 382 self._require_main_loop() 383 384 named_service = keywords.pop('named_service', None) 385 if named_service is not None: 386 if bus_name is not None: 387 raise TypeError('bus_name and named_service cannot both be ' 388 'specified') 389 bus_name = named_service 390 from warnings import warn 391 warn('Passing the named_service parameter to add_signal_receiver ' 392 'by name is deprecated: please use positional parameters', 393 DeprecationWarning, stacklevel=2) 394 395 match = SignalMatch(self, bus_name, path, dbus_interface, 396 signal_name, handler_function, **keywords) 397 398 self._signals_lock.acquire() 399 try: 400 by_interface = self._signal_recipients_by_object_path.setdefault( 401 path, {}) 402 by_member = by_interface.setdefault(dbus_interface, {}) 403 matches = by_member.setdefault(signal_name, []) 404 405 matches.append(match) 406 finally: 407 self._signals_lock.release() 408 409 return match
410
411 - def _iter_easy_matches(self, path, dbus_interface, member):
412 if path is not None: 413 path_keys = (None, path) 414 else: 415 path_keys = (None,) 416 if dbus_interface is not None: 417 interface_keys = (None, dbus_interface) 418 else: 419 interface_keys = (None,) 420 if member is not None: 421 member_keys = (None, member) 422 else: 423 member_keys = (None,) 424 425 for path in path_keys: 426 by_interface = self._signal_recipients_by_object_path.get(path, 427 None) 428 if by_interface is None: 429 continue 430 for dbus_interface in interface_keys: 431 by_member = by_interface.get(dbus_interface, None) 432 if by_member is None: 433 continue 434 for member in member_keys: 435 matches = by_member.get(member, None) 436 if matches is None: 437 continue 438 for m in matches: 439 yield m
440
441 - def remove_signal_receiver(self, handler_or_match, 442 signal_name=None, 443 dbus_interface=None, 444 bus_name=None, 445 path=None, 446 **keywords):
447 named_service = keywords.pop('named_service', None) 448 if named_service is not None: 449 if bus_name is not None: 450 raise TypeError('bus_name and named_service cannot both be ' 451 'specified') 452 bus_name = named_service 453 from warnings import warn 454 warn('Passing the named_service parameter to ' 455 'remove_signal_receiver by name is deprecated: please use ' 456 'positional parameters', 457 DeprecationWarning, stacklevel=2) 458 459 new = [] 460 deletions = [] 461 self._signals_lock.acquire() 462 try: 463 by_interface = self._signal_recipients_by_object_path.get(path, 464 None) 465 if by_interface is None: 466 return 467 by_member = by_interface.get(dbus_interface, None) 468 if by_member is None: 469 return 470 matches = by_member.get(signal_name, None) 471 if matches is None: 472 return 473 474 for match in matches: 475 if (handler_or_match is match 476 or match.matches_removal_spec(bus_name, 477 path, 478 dbus_interface, 479 signal_name, 480 handler_or_match, 481 **keywords)): 482 deletions.append(match) 483 else: 484 new.append(match) 485 by_member[signal_name] = new 486 finally: 487 self._signals_lock.release() 488 489 for match in deletions: 490 self._clean_up_signal_match(match)
491
492 - def _clean_up_signal_match(self, match):
493 # Now called without the signals lock held (it was held in <= 0.81.0) 494 pass
495
496 - def _signal_func(self, message):
497 """D-Bus filter function. Handle signals by dispatching to Python 498 callbacks kept in the match-rule tree. 499 """ 500 501 if not isinstance(message, SignalMessage): 502 return HANDLER_RESULT_NOT_YET_HANDLED 503 504 dbus_interface = message.get_interface() 505 path = message.get_path() 506 signal_name = message.get_member() 507 508 for match in self._iter_easy_matches(path, dbus_interface, 509 signal_name): 510 match.maybe_handle_message(message) 511 512 if (dbus_interface == LOCAL_IFACE and 513 path == LOCAL_PATH and 514 signal_name == 'Disconnected'): 515 for cb in self.__call_on_disconnection: 516 try: 517 cb(self) 518 except Exception, e: 519 # basicConfig is a no-op if logging is already configured 520 logging.basicConfig() 521 _logger.error('Exception in handler for Disconnected ' 522 'signal:', exc_info=1) 523 524 return HANDLER_RESULT_NOT_YET_HANDLED
525
526 - def call_async(self, bus_name, object_path, dbus_interface, method, 527 signature, args, reply_handler, error_handler, 528 timeout=-1.0, utf8_strings=False, byte_arrays=False, 529 require_main_loop=True):
530 """Call the given method, asynchronously. 531 532 If the reply_handler is None, successful replies will be ignored. 533 If the error_handler is None, failures will be ignored. If both 534 are None, the implementation may request that no reply is sent. 535 536 :Returns: The dbus.lowlevel.PendingCall. 537 :Since: 0.81.0 538 """ 539 if object_path == LOCAL_PATH: 540 raise DBusException('Methods may not be called on the reserved ' 541 'path %s' % LOCAL_PATH) 542 if dbus_interface == LOCAL_IFACE: 543 raise DBusException('Methods may not be called on the reserved ' 544 'interface %s' % LOCAL_IFACE) 545 # no need to validate other args - MethodCallMessage ctor will do 546 547 get_args_opts = {'utf8_strings': utf8_strings, 548 'byte_arrays': byte_arrays} 549 550 message = MethodCallMessage(destination=bus_name, 551 path=object_path, 552 interface=dbus_interface, 553 method=method) 554 # Add the arguments to the function 555 try: 556 message.append(signature=signature, *args) 557 except Exception, e: 558 logging.basicConfig() 559 _logger.error('Unable to set arguments %r according to ' 560 'signature %r: %s: %s', 561 args, signature, e.__class__, e) 562 raise 563 564 if reply_handler is None and error_handler is None: 565 # we don't care what happens, so just send it 566 self.send_message(message) 567 return 568 569 if reply_handler is None: 570 reply_handler = _noop 571 if error_handler is None: 572 error_handler = _noop 573 574 def msg_reply_handler(message): 575 if isinstance(message, MethodReturnMessage): 576 reply_handler(*message.get_args_list(**get_args_opts)) 577 elif isinstance(message, ErrorMessage): 578 error_handler(DBusException(name=message.get_error_name(), 579 *message.get_args_list())) 580 else: 581 error_handler(TypeError('Unexpected type for reply ' 582 'message: %r' % message))
583 return self.send_message_with_reply(message, msg_reply_handler, 584 timeout, 585 require_main_loop=require_main_loop)
586
587 - def call_blocking(self, bus_name, object_path, dbus_interface, method, 588 signature, args, timeout=-1.0, utf8_strings=False, 589 byte_arrays=False):
590 """Call the given method, synchronously. 591 :Since: 0.81.0 592 """ 593 if object_path == LOCAL_PATH: 594 raise DBusException('Methods may not be called on the reserved ' 595 'path %s' % LOCAL_PATH) 596 if dbus_interface == LOCAL_IFACE: 597 raise DBusException('Methods may not be called on the reserved ' 598 'interface %s' % LOCAL_IFACE) 599 # no need to validate other args - MethodCallMessage ctor will do 600 601 get_args_opts = {'utf8_strings': utf8_strings, 602 'byte_arrays': byte_arrays} 603 604 message = MethodCallMessage(destination=bus_name, 605 path=object_path, 606 interface=dbus_interface, 607 method=method) 608 # Add the arguments to the function 609 try: 610 message.append(signature=signature, *args) 611 except Exception, e: 612 logging.basicConfig() 613 _logger.error('Unable to set arguments %r according to ' 614 'signature %r: %s: %s', 615 args, signature, e.__class__, e) 616 raise 617 618 # make a blocking call 619 reply_message = self.send_message_with_reply_and_block( 620 message, timeout) 621 args_list = reply_message.get_args_list(**get_args_opts) 622 if len(args_list) == 0: 623 return None 624 elif len(args_list) == 1: 625 return args_list[0] 626 else: 627 return tuple(args_list)
628
629 - def call_on_disconnection(self, callable):
630 """Arrange for `callable` to be called with one argument (this 631 Connection object) when the Connection becomes 632 disconnected. 633 634 :Since: 0.83.0 635 """ 636 self.__call_on_disconnection.append(callable)
637