1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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):
51
52
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):
123
125 """SignalMatch objects are compared by identity."""
126 return hash(id(self))
127
129 """SignalMatch objects are compared by identity."""
130 return self is other
131
133 """SignalMatch objects are compared by identity."""
134 return self is not other
135
136 sender = property(lambda self: self._sender)
137
156
158 return ('<%s at %x "%s" on conn %r>'
159 % (self.__class__, id(self), self._rule, self._conn_weakref()))
160
163
164 - def matches_removal_spec(self, sender, object_path,
165 dbus_interface, member, handler, **kwargs):
179
237
246
247
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
258 super(Connection, self).__init__(*args, **kwargs)
259
260
261
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
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
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
520
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
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
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
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
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
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
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
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