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  import threading 
 28  import weakref 
 29   
 30  from _dbus_bindings import ( 
 31      Connection as _Connection, LOCAL_IFACE, LOCAL_PATH, validate_bus_name, 
 32      validate_interface_name, validate_member_name, validate_object_path) 
 33  from dbus.exceptions import DBusException 
 34  from dbus.lowlevel import ( 
 35      ErrorMessage, HANDLER_RESULT_NOT_YET_HANDLED, MethodCallMessage, 
 36      MethodReturnMessage, SignalMessage) 
 37  from dbus.proxies import ProxyObject 
 38  from dbus._compat import is_py2, is_py3 
 39   
 40  if is_py3: 
 41      from _dbus_bindings import String 
 42  else: 
 43      from _dbus_bindings import UTF8String 
 44   
 45   
 46  _logger = logging.getLogger('dbus.connection') 
 47   
 48   
49 -def _noop(*args, **kwargs):
50 pass
51 52
53 -class SignalMatch(object):
54 _slots = ['_sender_name_owner', '_member', '_interface', '_sender', 55 '_path', '_handler', '_args_match', '_rule', 56 '_byte_arrays', '_conn_weakref', 57 '_destination_keyword', '_interface_keyword', 58 '_message_keyword', '_member_keyword', 59 '_sender_keyword', '_path_keyword', '_int_args_match'] 60 if is_py2: 61 _slots.append('_utf8_strings') 62 63 __slots__ = tuple(_slots) 64
65 - def __init__(self, conn, sender, object_path, dbus_interface, 66 member, handler, byte_arrays=False, 67 sender_keyword=None, path_keyword=None, 68 interface_keyword=None, member_keyword=None, 69 message_keyword=None, destination_keyword=None, 70 **kwargs):
71 if member is not None: 72 validate_member_name(member) 73 if dbus_interface is not None: 74 validate_interface_name(dbus_interface) 75 if sender is not None: 76 validate_bus_name(sender) 77 if object_path is not None: 78 validate_object_path(object_path) 79 80 self._rule = None 81 self._conn_weakref = weakref.ref(conn) 82 self._sender = sender 83 self._interface = dbus_interface 84 self._member = member 85 self._path = object_path 86 self._handler = handler 87 88 # if the connection is actually a bus, it's responsible for changing 89 # this later 90 self._sender_name_owner = sender 91 92 if is_py2: 93 self._utf8_strings = kwargs.pop('utf8_strings', False) 94 elif 'utf8_strings' in kwargs: 95 raise TypeError("unexpected keyword argument 'utf8_strings'") 96 97 self._byte_arrays = byte_arrays 98 self._sender_keyword = sender_keyword 99 self._path_keyword = path_keyword 100 self._member_keyword = member_keyword 101 self._interface_keyword = interface_keyword 102 self._message_keyword = message_keyword 103 self._destination_keyword = destination_keyword 104 105 self._args_match = kwargs 106 if not kwargs: 107 self._int_args_match = None 108 else: 109 self._int_args_match = {} 110 for kwarg in kwargs: 111 if not kwarg.startswith('arg'): 112 raise TypeError('SignalMatch: unknown keyword argument %s' 113 % kwarg) 114 try: 115 index = int(kwarg[3:]) 116 except ValueError: 117 raise TypeError('SignalMatch: unknown keyword argument %s' 118 % kwarg) 119 if index < 0 or index > 63: 120 raise TypeError('SignalMatch: arg match index must be in ' 121 'range(64), not %d' % index) 122 self._int_args_match[index] = kwargs[kwarg]
123
124 - def __hash__(self):
125 """SignalMatch objects are compared by identity.""" 126 return hash(id(self))
127
128 - def __eq__(self, other):
129 """SignalMatch objects are compared by identity.""" 130 return self is other
131
132 - def __ne__(self, other):
133 """SignalMatch objects are compared by identity.""" 134 return self is not other
135 136 sender = property(lambda self: self._sender) 137
138 - def __str__(self):
139 if self._rule is None: 140 rule = ["type='signal'"] 141 if self._sender is not None: 142 rule.append("sender='%s'" % self._sender) 143 if self._path is not None: 144 rule.append("path='%s'" % self._path) 145 if self._interface is not None: 146 rule.append("interface='%s'" % self._interface) 147 if self._member is not None: 148 rule.append("member='%s'" % self._member) 149 if self._int_args_match is not None: 150 for index, value in self._int_args_match.items(): 151 rule.append("arg%d='%s'" % (index, value)) 152 153 self._rule = ','.join(rule) 154 155 return self._rule
156
157 - def __repr__(self):
158 return ('<%s at %x "%s" on conn %r>' 159 % (self.__class__, id(self), self._rule, self._conn_weakref()))
160
161 - def set_sender_name_owner(self, new_name):
162 self._sender_name_owner = new_name
163
164 - def matches_removal_spec(self, sender, object_path, 165 dbus_interface, member, handler, **kwargs):
166 if handler not in (None, self._handler): 167 return False 168 if sender != self._sender: 169 return False 170 if object_path != self._path: 171 return False 172 if dbus_interface != self._interface: 173 return False 174 if member != self._member: 175 return False 176 if kwargs != self._args_match: 177 return False 178 return True
179
180 - def maybe_handle_message(self, message):
181 args = None 182 183 # these haven't been checked yet by the match tree 184 if self._sender_name_owner not in (None, message.get_sender()): 185 return False 186 if self._int_args_match is not None: 187 # extracting args with utf8_strings and byte_arrays is less work 188 kwargs = dict(byte_arrays=True) 189 arg_type = (String if is_py3 else UTF8String) 190 if is_py2: 191 kwargs['utf8_strings'] = True 192 args = message.get_args_list(**kwargs) 193 for index, value in self._int_args_match.items(): 194 if (index >= len(args) 195 or not isinstance(args[index], arg_type) 196 or args[index] != value): 197 return False 198 199 # these have likely already been checked by the match tree 200 if self._member not in (None, message.get_member()): 201 return False 202 if self._interface not in (None, message.get_interface()): 203 return False 204 if self._path not in (None, message.get_path()): 205 return False 206 207 try: 208 # minor optimization: if we already extracted the args with the 209 # right calling convention to do the args match, don't bother 210 # doing so again 211 utf8_strings = (is_py2 and self._utf8_strings) 212 if args is None or not utf8_strings or not self._byte_arrays: 213 kwargs = dict(byte_arrays=self._byte_arrays) 214 if is_py2: 215 kwargs['utf8_strings'] = self._utf8_strings 216 args = message.get_args_list(**kwargs) 217 kwargs = {} 218 if self._sender_keyword is not None: 219 kwargs[self._sender_keyword] = message.get_sender() 220 if self._destination_keyword is not None: 221 kwargs[self._destination_keyword] = message.get_destination() 222 if self._path_keyword is not None: 223 kwargs[self._path_keyword] = message.get_path() 224 if self._member_keyword is not None: 225 kwargs[self._member_keyword] = message.get_member() 226 if self._interface_keyword is not None: 227 kwargs[self._interface_keyword] = message.get_interface() 228 if self._message_keyword is not None: 229 kwargs[self._message_keyword] = message 230 self._handler(*args, **kwargs) 231 except: 232 # basicConfig is a no-op if logging is already configured 233 logging.basicConfig() 234 _logger.error('Exception in handler for D-Bus signal:', exc_info=1) 235 236 return True
237
238 - def remove(self):
239 conn = self._conn_weakref() 240 # do nothing if the connection has already vanished 241 if conn is not None: 242 conn.remove_signal_receiver(self, self._member, 243 self._interface, self._sender, 244 self._path, 245 **self._args_match)
246 247
248 -class Connection(_Connection):
249 """A connection to another application. In this base class there is 250 assumed to be no bus daemon. 251 252 :Since: 0.81.0 253 """ 254 255 ProxyObjectClass = ProxyObject 256
257 - def __init__(self, *args, **kwargs):
258 super(Connection, self).__init__(*args, **kwargs) 259 260 # this if-block is needed because shared bus connections can be 261 # __init__'ed more than once 262 if not hasattr(self, '_dbus_Connection_initialized'): 263 self._dbus_Connection_initialized = 1 264 265 self.__call_on_disconnection = [] 266 267 self._signal_recipients_by_object_path = {} 268 """Map from object path to dict mapping dbus_interface to dict 269 mapping member to list of SignalMatch objects.""" 270 271 self._signals_lock = threading.Lock() 272 """Lock used to protect signal data structures""" 273 274 self.add_message_filter(self.__class__._signal_func)
275
276 - def activate_name_owner(self, bus_name):
277 """Return the unique name for the given bus name, activating it 278 if necessary and possible. 279 280 If the name is already unique or this connection is not to a 281 bus daemon, just return it. 282 283 :Returns: a bus name. If the given `bus_name` exists, the returned 284 name identifies its current owner; otherwise the returned name 285 does not exist. 286 :Raises DBusException: if the implementation has failed 287 to activate the given bus name. 288 :Since: 0.81.0 289 """ 290 return bus_name
291
292 - def get_object(self, bus_name=None, object_path=None, introspect=True, 293 **kwargs):
294 """Return a local proxy for the given remote object. 295 296 Method calls on the proxy are translated into method calls on the 297 remote object. 298 299 :Parameters: 300 `bus_name` : str 301 A bus name (either the unique name or a well-known name) 302 of the application owning the object. The keyword argument 303 named_service is a deprecated alias for this. 304 `object_path` : str 305 The object path of the desired object 306 `introspect` : bool 307 If true (default), attempt to introspect the remote 308 object to find out supported methods and their signatures 309 310 :Returns: a `dbus.proxies.ProxyObject` 311 """ 312 named_service = kwargs.pop('named_service', None) 313 if named_service is not None: 314 if bus_name is not None: 315 raise TypeError('bus_name and named_service cannot both ' 316 'be specified') 317 from warnings import warn 318 warn('Passing the named_service parameter to get_object by name ' 319 'is deprecated: please use positional parameters', 320 DeprecationWarning, stacklevel=2) 321 bus_name = named_service 322 if kwargs: 323 raise TypeError('get_object does not take these keyword ' 324 'arguments: %s' % ', '.join(kwargs.keys())) 325 326 return self.ProxyObjectClass(self, bus_name, object_path, 327 introspect=introspect)
328
329 - def add_signal_receiver(self, handler_function, 330 signal_name=None, 331 dbus_interface=None, 332 bus_name=None, 333 path=None, 334 **keywords):
335 """Arrange for the given function to be called when a signal matching 336 the parameters is received. 337 338 :Parameters: 339 `handler_function` : callable 340 The function to be called. Its positional arguments will 341 be the arguments of the signal. By default it will receive 342 no keyword arguments, but see the description of 343 the optional keyword arguments below. 344 `signal_name` : str 345 The signal name; None (the default) matches all names 346 `dbus_interface` : str 347 The D-Bus interface name with which to qualify the signal; 348 None (the default) matches all interface names 349 `bus_name` : str 350 A bus name for the sender, which will be resolved to a 351 unique name if it is not already; None (the default) matches 352 any sender. 353 `path` : str 354 The object path of the object which must have emitted the 355 signal; None (the default) matches any object path 356 :Keywords: 357 `utf8_strings` : bool 358 If True, the handler function will receive any string 359 arguments as dbus.UTF8String objects (a subclass of str 360 guaranteed to be UTF-8). If False (default) it will receive 361 any string arguments as dbus.String objects (a subclass of 362 unicode). 363 `byte_arrays` : bool 364 If True, the handler function will receive any byte-array 365 arguments as dbus.ByteArray objects (a subclass of str). 366 If False (default) it will receive any byte-array 367 arguments as a dbus.Array of dbus.Byte (subclasses of: 368 a list of ints). 369 `sender_keyword` : str 370 If not None (the default), the handler function will receive 371 the unique name of the sending endpoint as a keyword 372 argument with this name. 373 `destination_keyword` : str 374 If not None (the default), the handler function will receive 375 the bus name of the destination (or None if the signal is a 376 broadcast, as is usual) as a keyword argument with this name. 377 `interface_keyword` : str 378 If not None (the default), the handler function will receive 379 the signal interface as a keyword argument with this name. 380 `member_keyword` : str 381 If not None (the default), the handler function will receive 382 the signal name as a keyword argument with this name. 383 `path_keyword` : str 384 If not None (the default), the handler function will receive 385 the object-path of the sending object as a keyword argument 386 with this name. 387 `message_keyword` : str 388 If not None (the default), the handler function will receive 389 the `dbus.lowlevel.SignalMessage` as a keyword argument with 390 this name. 391 `arg...` : unicode or UTF-8 str 392 If there are additional keyword parameters of the form 393 ``arg``\ *n*, match only signals where the *n*\ th argument 394 is the value given for that keyword parameter. As of this 395 time only string arguments can be matched (in particular, 396 object paths and signatures can't). 397 `named_service` : str 398 A deprecated alias for `bus_name`. 399 """ 400 self._require_main_loop() 401 402 named_service = keywords.pop('named_service', None) 403 if named_service is not None: 404 if bus_name is not None: 405 raise TypeError('bus_name and named_service cannot both be ' 406 'specified') 407 bus_name = named_service 408 from warnings import warn 409 warn('Passing the named_service parameter to add_signal_receiver ' 410 'by name is deprecated: please use positional parameters', 411 DeprecationWarning, stacklevel=2) 412 413 match = SignalMatch(self, bus_name, path, dbus_interface, 414 signal_name, handler_function, **keywords) 415 416 self._signals_lock.acquire() 417 try: 418 by_interface = self._signal_recipients_by_object_path.setdefault( 419 path, {}) 420 by_member = by_interface.setdefault(dbus_interface, {}) 421 matches = by_member.setdefault(signal_name, []) 422 423 matches.append(match) 424 finally: 425 self._signals_lock.release() 426 427 return match
428
429 - def _iter_easy_matches(self, path, dbus_interface, member):
430 if path is not None: 431 path_keys = (None, path) 432 else: 433 path_keys = (None,) 434 if dbus_interface is not None: 435 interface_keys = (None, dbus_interface) 436 else: 437 interface_keys = (None,) 438 if member is not None: 439 member_keys = (None, member) 440 else: 441 member_keys = (None,) 442 443 for path in path_keys: 444 by_interface = self._signal_recipients_by_object_path.get(path) 445 if by_interface is None: 446 continue 447 for dbus_interface in interface_keys: 448 by_member = by_interface.get(dbus_interface, None) 449 if by_member is None: 450 continue 451 for member in member_keys: 452 matches = by_member.get(member, None) 453 if matches is None: 454 continue 455 for m in matches: 456 yield m
457
458 - def remove_signal_receiver(self, handler_or_match, 459 signal_name=None, 460 dbus_interface=None, 461 bus_name=None, 462 path=None, 463 **keywords):
464 named_service = keywords.pop('named_service', None) 465 if named_service is not None: 466 if bus_name is not None: 467 raise TypeError('bus_name and named_service cannot both be ' 468 'specified') 469 bus_name = named_service 470 from warnings import warn 471 warn('Passing the named_service parameter to ' 472 'remove_signal_receiver by name is deprecated: please use ' 473 'positional parameters', 474 DeprecationWarning, stacklevel=2) 475 476 new = [] 477 deletions = [] 478 self._signals_lock.acquire() 479 try: 480 by_interface = self._signal_recipients_by_object_path.get(path, 481 None) 482 if by_interface is None: 483 return 484 by_member = by_interface.get(dbus_interface, None) 485 if by_member is None: 486 return 487 matches = by_member.get(signal_name, None) 488 if matches is None: 489 return 490 491 for match in matches: 492 if (handler_or_match is match 493 or match.matches_removal_spec(bus_name, 494 path, 495 dbus_interface, 496 signal_name, 497 handler_or_match, 498 **keywords)): 499 deletions.append(match) 500 else: 501 new.append(match) 502 503 if new: 504 by_member[signal_name] = new 505 else: 506 del by_member[signal_name] 507 if not by_member: 508 del by_interface[dbus_interface] 509 if not by_interface: 510 del self._signal_recipients_by_object_path[path] 511 finally: 512 self._signals_lock.release() 513 514 for match in deletions: 515 self._clean_up_signal_match(match)
516
517 - def _clean_up_signal_match(self, match):
518 # Now called without the signals lock held (it was held in <= 0.81.0) 519 pass
520
521 - def _signal_func(self, message):
522 """D-Bus filter function. Handle signals by dispatching to Python 523 callbacks kept in the match-rule tree. 524 """ 525 526 if not isinstance(message, SignalMessage): 527 return HANDLER_RESULT_NOT_YET_HANDLED 528 529 dbus_interface = message.get_interface() 530 path = message.get_path() 531 signal_name = message.get_member() 532 533 for match in self._iter_easy_matches(path, dbus_interface, 534 signal_name): 535 match.maybe_handle_message(message) 536 537 if (dbus_interface == LOCAL_IFACE and 538 path == LOCAL_PATH and 539 signal_name == 'Disconnected'): 540 for cb in self.__call_on_disconnection: 541 try: 542 cb(self) 543 except Exception: 544 # basicConfig is a no-op if logging is already configured 545 logging.basicConfig() 546 _logger.error('Exception in handler for Disconnected ' 547 'signal:', exc_info=1) 548 549 return HANDLER_RESULT_NOT_YET_HANDLED
550
551 - def call_async(self, bus_name, object_path, dbus_interface, method, 552 signature, args, reply_handler, error_handler, 553 timeout=-1.0, byte_arrays=False, 554 require_main_loop=True, **kwargs):
555 """Call the given method, asynchronously. 556 557 If the reply_handler is None, successful replies will be ignored. 558 If the error_handler is None, failures will be ignored. If both 559 are None, the implementation may request that no reply is sent. 560 561 :Returns: The dbus.lowlevel.PendingCall. 562 :Since: 0.81.0 563 """ 564 if object_path == LOCAL_PATH: 565 raise DBusException('Methods may not be called on the reserved ' 566 'path %s' % LOCAL_PATH) 567 if dbus_interface == LOCAL_IFACE: 568 raise DBusException('Methods may not be called on the reserved ' 569 'interface %s' % LOCAL_IFACE) 570 # no need to validate other args - MethodCallMessage ctor will do 571 572 get_args_opts = dict(byte_arrays=byte_arrays) 573 if is_py2: 574 get_args_opts['utf8_strings'] = kwargs.get('utf8_strings', False) 575 elif 'utf8_strings' in kwargs: 576 raise TypeError("unexpected keyword argument 'utf8_strings'") 577 578 message = MethodCallMessage(destination=bus_name, 579 path=object_path, 580 interface=dbus_interface, 581 method=method) 582 # Add the arguments to the function 583 try: 584 message.append(signature=signature, *args) 585 except Exception as e: 586 logging.basicConfig() 587 _logger.error('Unable to set arguments %r according to ' 588 'signature %r: %s: %s', 589 args, signature, e.__class__, e) 590 raise 591 592 if reply_handler is None and error_handler is None: 593 # we don't care what happens, so just send it 594 self.send_message(message) 595 return 596 597 if reply_handler is None: 598 reply_handler = _noop 599 if error_handler is None: 600 error_handler = _noop 601 602 def msg_reply_handler(message): 603 if isinstance(message, MethodReturnMessage): 604 reply_handler(*message.get_args_list(**get_args_opts)) 605 elif isinstance(message, ErrorMessage): 606 error_handler(DBusException(name=message.get_error_name(), 607 *message.get_args_list())) 608 else: 609 error_handler(TypeError('Unexpected type for reply ' 610 'message: %r' % message))
611 return self.send_message_with_reply(message, msg_reply_handler, 612 timeout, 613 require_main_loop=require_main_loop)
614
615 - def call_blocking(self, bus_name, object_path, dbus_interface, method, 616 signature, args, timeout=-1.0, 617 byte_arrays=False, **kwargs):
618 """Call the given method, synchronously. 619 :Since: 0.81.0 620 """ 621 if object_path == LOCAL_PATH: 622 raise DBusException('Methods may not be called on the reserved ' 623 'path %s' % LOCAL_PATH) 624 if dbus_interface == LOCAL_IFACE: 625 raise DBusException('Methods may not be called on the reserved ' 626 'interface %s' % LOCAL_IFACE) 627 # no need to validate other args - MethodCallMessage ctor will do 628 629 get_args_opts = dict(byte_arrays=byte_arrays) 630 if is_py2: 631 get_args_opts['utf8_strings'] = kwargs.get('utf8_strings', False) 632 elif 'utf8_strings' in kwargs: 633 raise TypeError("unexpected keyword argument 'utf8_strings'") 634 635 message = MethodCallMessage(destination=bus_name, 636 path=object_path, 637 interface=dbus_interface, 638 method=method) 639 # Add the arguments to the function 640 try: 641 message.append(signature=signature, *args) 642 except Exception as e: 643 logging.basicConfig() 644 _logger.error('Unable to set arguments %r according to ' 645 'signature %r: %s: %s', 646 args, signature, e.__class__, e) 647 raise 648 649 # make a blocking call 650 reply_message = self.send_message_with_reply_and_block( 651 message, timeout) 652 args_list = reply_message.get_args_list(**get_args_opts) 653 if len(args_list) == 0: 654 return None 655 elif len(args_list) == 1: 656 return args_list[0] 657 else: 658 return tuple(args_list)
659
660 - def call_on_disconnection(self, callable):
661 """Arrange for `callable` to be called with one argument (this 662 Connection object) when the Connection becomes 663 disconnected. 664 665 :Since: 0.83.0 666 """ 667 self.__call_on_disconnection.append(callable)
668