Blame dbus/service.py

Packit 130fc8
# Copyright (C) 2003-2006 Red Hat Inc. <http://www.redhat.com/>
Packit 130fc8
# Copyright (C) 2003 David Zeuthen
Packit 130fc8
# Copyright (C) 2004 Rob Taylor
Packit 130fc8
# Copyright (C) 2005-2006 Collabora Ltd. <http://www.collabora.co.uk/>
Packit 130fc8
#
Packit 130fc8
# Permission is hereby granted, free of charge, to any person
Packit 130fc8
# obtaining a copy of this software and associated documentation
Packit 130fc8
# files (the "Software"), to deal in the Software without
Packit 130fc8
# restriction, including without limitation the rights to use, copy,
Packit 130fc8
# modify, merge, publish, distribute, sublicense, and/or sell copies
Packit 130fc8
# of the Software, and to permit persons to whom the Software is
Packit 130fc8
# furnished to do so, subject to the following conditions:
Packit 130fc8
#
Packit 130fc8
# The above copyright notice and this permission notice shall be
Packit 130fc8
# included in all copies or substantial portions of the Software.
Packit 130fc8
#
Packit 130fc8
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
Packit 130fc8
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
Packit 130fc8
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
Packit 130fc8
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
Packit 130fc8
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
Packit 130fc8
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
Packit 130fc8
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
Packit 130fc8
# DEALINGS IN THE SOFTWARE.
Packit 130fc8
Packit 130fc8
__all__ = ('BusName', 'Object', 'FallbackObject', 'method', 'signal')
Packit 130fc8
__docformat__ = 'restructuredtext'
Packit 130fc8
Packit 130fc8
import sys
Packit 130fc8
import logging
Packit 130fc8
import threading
Packit 130fc8
import traceback
Packit 130fc8
from collections import Sequence
Packit 130fc8
Packit 130fc8
import _dbus_bindings
Packit 130fc8
from dbus import (
Packit 130fc8
    INTROSPECTABLE_IFACE, ObjectPath, SessionBus, Signature, Struct,
Packit 130fc8
    validate_bus_name, validate_object_path)
Packit 130fc8
from dbus.decorators import method, signal
Packit 130fc8
from dbus.exceptions import (
Packit 130fc8
    DBusException, NameExistsException, UnknownMethodException)
Packit 130fc8
from dbus.lowlevel import ErrorMessage, MethodReturnMessage, MethodCallMessage
Packit 130fc8
from dbus.proxies import LOCAL_PATH
Packit 130fc8
from dbus._compat import is_py2
Packit 130fc8
Packit 130fc8
Packit 130fc8
_logger = logging.getLogger('dbus.service')
Packit 130fc8
Packit 130fc8
Packit 130fc8
class _VariantSignature(object):
Packit 130fc8
    """A fake method signature which, when iterated, yields an endless stream
Packit 130fc8
    of 'v' characters representing variants (handy with zip()).
Packit 130fc8
Packit 130fc8
    It has no string representation.
Packit 130fc8
    """
Packit 130fc8
    def __iter__(self):
Packit 130fc8
        """Return self."""
Packit 130fc8
        return self
Packit 130fc8
Packit 130fc8
    def __next__(self):
Packit 130fc8
        """Return 'v' whenever called."""
Packit 130fc8
        return 'v'
Packit 130fc8
Packit 130fc8
    if is_py2:
Packit 130fc8
        next = __next__
Packit 130fc8
Packit 130fc8
Packit 130fc8
class BusName(object):
Packit 130fc8
    """A base class for exporting your own Named Services across the Bus.
Packit 130fc8
Packit 130fc8
    When instantiated, objects of this class attempt to claim the given
Packit 130fc8
    well-known name on the given bus for the current process. The name is
Packit 130fc8
    released when the BusName object becomes unreferenced.
Packit 130fc8
Packit 130fc8
    If a well-known name is requested multiple times, multiple references
Packit 130fc8
    to the same BusName object will be returned.
Packit 130fc8
Packit 130fc8
    Caveats
Packit 130fc8
    -------
Packit 130fc8
    - Assumes that named services are only ever requested using this class -
Packit 130fc8
      if you request names from the bus directly, confusion may occur.
Packit 130fc8
    - Does not handle queueing.
Packit 130fc8
    """
Packit 130fc8
    def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
Packit 130fc8
        """Constructor, which may either return an existing cached object
Packit 130fc8
        or a new object.
Packit 130fc8
Packit 130fc8
        :Parameters:
Packit 130fc8
            `name` : str
Packit 130fc8
                The well-known name to be advertised
Packit 130fc8
            `bus` : dbus.Bus
Packit 130fc8
                A Bus on which this service will be advertised.
Packit 130fc8
Packit 130fc8
                Omitting this parameter or setting it to None has been
Packit 130fc8
                deprecated since version 0.82.1. For backwards compatibility,
Packit 130fc8
                if this is done, the global shared connection to the session
Packit 130fc8
                bus will be used.
Packit 130fc8
Packit 130fc8
            `allow_replacement` : bool
Packit 130fc8
                If True, other processes trying to claim the same well-known
Packit 130fc8
                name will take precedence over this one.
Packit 130fc8
            `replace_existing` : bool
Packit 130fc8
                If True, this process can take over the well-known name
Packit 130fc8
                from other processes already holding it.
Packit 130fc8
            `do_not_queue` : bool
Packit 130fc8
                If True, this service will not be placed in the queue of
Packit 130fc8
                services waiting for the requested name if another service
Packit 130fc8
                already holds it.
Packit 130fc8
        """
Packit 130fc8
        validate_bus_name(name, allow_well_known=True, allow_unique=False)
Packit 130fc8
Packit 130fc8
        # if necessary, get default bus (deprecated)
Packit 130fc8
        if bus is None:
Packit 130fc8
            import warnings
Packit 130fc8
            warnings.warn('Omitting the "bus" parameter to '
Packit 130fc8
                          'dbus.service.BusName.__init__ is deprecated',
Packit 130fc8
                          DeprecationWarning, stacklevel=2)
Packit 130fc8
            bus = SessionBus()
Packit 130fc8
Packit 130fc8
        # see if this name is already defined, return it if so
Packit 130fc8
        # FIXME: accessing internals of Bus
Packit 130fc8
        if name in bus._bus_names:
Packit 130fc8
            return bus._bus_names[name]
Packit 130fc8
Packit 130fc8
        # otherwise register the name
Packit 130fc8
        name_flags = (
Packit 130fc8
            (allow_replacement and _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT or 0) |
Packit 130fc8
            (replace_existing and _dbus_bindings.NAME_FLAG_REPLACE_EXISTING or 0) |
Packit 130fc8
            (do_not_queue and _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE or 0))
Packit 130fc8
Packit 130fc8
        retval = bus.request_name(name, name_flags)
Packit 130fc8
Packit 130fc8
        # TODO: more intelligent tracking of bus name states?
Packit 130fc8
        if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
Packit 130fc8
            pass
Packit 130fc8
        elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
Packit 130fc8
            # queueing can happen by default, maybe we should
Packit 130fc8
            # track this better or let the user know if they're
Packit 130fc8
            # queued or not?
Packit 130fc8
            pass
Packit 130fc8
        elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
Packit 130fc8
            raise NameExistsException(name)
Packit 130fc8
        elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
Packit 130fc8
            # if this is a shared bus which is being used by someone
Packit 130fc8
            # else in this process, this can happen legitimately
Packit 130fc8
            pass
Packit 130fc8
        else:
Packit 130fc8
            raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
Packit 130fc8
Packit 130fc8
        # and create the object
Packit 130fc8
        bus_name = object.__new__(cls)
Packit 130fc8
        bus_name._bus = bus
Packit 130fc8
        bus_name._name = name
Packit 130fc8
Packit 130fc8
        # cache instance (weak ref only)
Packit 130fc8
        # FIXME: accessing Bus internals again
Packit 130fc8
        bus._bus_names[name] = bus_name
Packit 130fc8
Packit 130fc8
        return bus_name
Packit 130fc8
Packit 130fc8
    # do nothing because this is called whether or not the bus name
Packit 130fc8
    # object was retrieved from the cache or created new
Packit 130fc8
    def __init__(self, *args, **keywords):
Packit 130fc8
        pass
Packit 130fc8
Packit 130fc8
    # we can delete the low-level name here because these objects
Packit 130fc8
    # are guaranteed to exist only once for each bus name
Packit 130fc8
    def __del__(self):
Packit 130fc8
        self._bus.release_name(self._name)
Packit 130fc8
        pass
Packit 130fc8
Packit 130fc8
    def get_bus(self):
Packit 130fc8
        """Get the Bus this Service is on"""
Packit 130fc8
        return self._bus
Packit 130fc8
Packit 130fc8
    def get_name(self):
Packit 130fc8
        """Get the name of this service"""
Packit 130fc8
        return self._name
Packit 130fc8
Packit 130fc8
    def __repr__(self):
Packit 130fc8
        return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
Packit 130fc8
    __str__ = __repr__
Packit 130fc8
Packit 130fc8
Packit 130fc8
def _method_lookup(self, method_name, dbus_interface):
Packit 130fc8
    """Walks the Python MRO of the given class to find the method to invoke.
Packit 130fc8
Packit 130fc8
    Returns two methods, the one to call, and the one it inherits from which
Packit 130fc8
    defines its D-Bus interface name, signature, and attributes.
Packit 130fc8
    """
Packit 130fc8
    parent_method = None
Packit 130fc8
    candidate_class = None
Packit 130fc8
    successful = False
Packit 130fc8
Packit 130fc8
    # split up the cases when we do and don't have an interface because the
Packit 130fc8
    # latter is much simpler
Packit 130fc8
    if dbus_interface:
Packit 130fc8
        # search through the class hierarchy in python MRO order
Packit 130fc8
        for cls in self.__class__.__mro__:
Packit 130fc8
            # if we haven't got a candidate class yet, and we find a class with a
Packit 130fc8
            # suitably named member, save this as a candidate class
Packit 130fc8
            if (not candidate_class and method_name in cls.__dict__):
Packit 130fc8
                if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
Packit 130fc8
                    and "_dbus_interface" in cls.__dict__[method_name].__dict__):
Packit 130fc8
                    # however if it is annotated for a different interface
Packit 130fc8
                    # than we are looking for, it cannot be a candidate
Packit 130fc8
                    if cls.__dict__[method_name]._dbus_interface == dbus_interface:
Packit 130fc8
                        candidate_class = cls
Packit 130fc8
                        parent_method = cls.__dict__[method_name]
Packit 130fc8
                        successful = True
Packit 130fc8
                        break
Packit 130fc8
                    else:
Packit 130fc8
                        pass
Packit 130fc8
                else:
Packit 130fc8
                    candidate_class = cls
Packit 130fc8
Packit 130fc8
            # if we have a candidate class, carry on checking this and all
Packit 130fc8
            # superclasses for a method annoated as a dbus method
Packit 130fc8
            # on the correct interface
Packit 130fc8
            if (candidate_class and method_name in cls.__dict__
Packit 130fc8
                and "_dbus_is_method" in cls.__dict__[method_name].__dict__
Packit 130fc8
                and "_dbus_interface" in cls.__dict__[method_name].__dict__
Packit 130fc8
                and cls.__dict__[method_name]._dbus_interface == dbus_interface):
Packit 130fc8
                # the candidate class has a dbus method on the correct interface,
Packit 130fc8
                # or overrides a method that is, success!
Packit 130fc8
                parent_method = cls.__dict__[method_name]
Packit 130fc8
                successful = True
Packit 130fc8
                break
Packit 130fc8
Packit 130fc8
    else:
Packit 130fc8
        # simpler version of above
Packit 130fc8
        for cls in self.__class__.__mro__:
Packit 130fc8
            if (not candidate_class and method_name in cls.__dict__):
Packit 130fc8
                candidate_class = cls
Packit 130fc8
Packit 130fc8
            if (candidate_class and method_name in cls.__dict__
Packit 130fc8
                and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
Packit 130fc8
                parent_method = cls.__dict__[method_name]
Packit 130fc8
                successful = True
Packit 130fc8
                break
Packit 130fc8
Packit 130fc8
    if successful:
Packit 130fc8
        return (candidate_class.__dict__[method_name], parent_method)
Packit 130fc8
    else:
Packit 130fc8
        if dbus_interface:
Packit 130fc8
            raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
Packit 130fc8
        else:
Packit 130fc8
            raise UnknownMethodException('%s is not a valid method' % method_name)
Packit 130fc8
Packit 130fc8
Packit 130fc8
def _method_reply_return(connection, message, method_name, signature, *retval):
Packit 130fc8
    reply = MethodReturnMessage(message)
Packit 130fc8
    try:
Packit 130fc8
        reply.append(signature=signature, *retval)
Packit 130fc8
    except Exception as e:
Packit 130fc8
        logging.basicConfig()
Packit 130fc8
        if signature is None:
Packit 130fc8
            try:
Packit 130fc8
                signature = reply.guess_signature(retval) + ' (guessed)'
Packit 130fc8
            except Exception as e:
Packit 130fc8
                _logger.error('Unable to guess signature for arguments %r: '
Packit 130fc8
                              '%s: %s', retval, e.__class__, e)
Packit 130fc8
                raise
Packit 130fc8
        _logger.error('Unable to append %r to message with signature %s: '
Packit 130fc8
                      '%s: %s', retval, signature, e.__class__, e)
Packit 130fc8
        raise
Packit 130fc8
Packit 130fc8
    connection.send_message(reply)
Packit 130fc8
Packit 130fc8
Packit 130fc8
def _method_reply_error(connection, message, exception):
Packit 130fc8
    name = getattr(exception, '_dbus_error_name', None)
Packit 130fc8
Packit 130fc8
    if name is not None:
Packit 130fc8
        pass
Packit 130fc8
    elif getattr(exception, '__module__', '') in ('', '__main__'):
Packit 130fc8
        name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
Packit 130fc8
    else:
Packit 130fc8
        name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
Packit 130fc8
Packit 130fc8
    et, ev, etb = sys.exc_info()
Packit 130fc8
    if isinstance(exception, DBusException) and not exception.include_traceback:
Packit 130fc8
        # We don't actually want the traceback anyway
Packit 130fc8
        contents = exception.get_dbus_message()
Packit 130fc8
    elif ev is exception:
Packit 130fc8
        # The exception was actually thrown, so we can get a traceback
Packit 130fc8
        contents = ''.join(traceback.format_exception(et, ev, etb))
Packit 130fc8
    else:
Packit 130fc8
        # We don't have any traceback for it, e.g.
Packit 130fc8
        #   async_err_cb(MyException('Failed to badger the mushroom'))
Packit 130fc8
        # see also https://bugs.freedesktop.org/show_bug.cgi?id=12403
Packit 130fc8
        contents = ''.join(traceback.format_exception_only(exception.__class__,
Packit 130fc8
            exception))
Packit 130fc8
    reply = ErrorMessage(message, name, contents)
Packit 130fc8
Packit 130fc8
    connection.send_message(reply)
Packit 130fc8
Packit 130fc8
Packit 130fc8
class InterfaceType(type):
Packit 130fc8
    def __init__(cls, name, bases, dct):
Packit 130fc8
        # these attributes are shared between all instances of the Interface
Packit 130fc8
        # object, so this has to be a dictionary that maps class names to
Packit 130fc8
        # the per-class introspection/interface data
Packit 130fc8
        class_table = getattr(cls, '_dbus_class_table', {})
Packit 130fc8
        cls._dbus_class_table = class_table
Packit 130fc8
        interface_table = class_table[cls.__module__ + '.' + name] = {}
Packit 130fc8
Packit 130fc8
        # merge all the name -> method tables for all the interfaces
Packit 130fc8
        # implemented by our base classes into our own
Packit 130fc8
        for b in bases:
Packit 130fc8
            base_name = b.__module__ + '.' + b.__name__
Packit 130fc8
            if getattr(b, '_dbus_class_table', False):
Packit 130fc8
                for (interface, method_table) in class_table[base_name].items():
Packit 130fc8
                    our_method_table = interface_table.setdefault(interface, {})
Packit 130fc8
                    our_method_table.update(method_table)
Packit 130fc8
Packit 130fc8
        # add in all the name -> method entries for our own methods/signals
Packit 130fc8
        for func in dct.values():
Packit 130fc8
            if getattr(func, '_dbus_interface', False):
Packit 130fc8
                method_table = interface_table.setdefault(func._dbus_interface, {})
Packit 130fc8
                method_table[func.__name__] = func
Packit 130fc8
Packit 130fc8
        super(InterfaceType, cls).__init__(name, bases, dct)
Packit 130fc8
Packit 130fc8
    # methods are different to signals, so we have two functions... :)
Packit 130fc8
    def _reflect_on_method(cls, func):
Packit 130fc8
        args = func._dbus_args
Packit 130fc8
Packit 130fc8
        if func._dbus_in_signature:
Packit 130fc8
            # convert signature into a tuple so length refers to number of
Packit 130fc8
            # types, not number of characters. the length is checked by
Packit 130fc8
            # the decorator to make sure it matches the length of args.
Packit 130fc8
            in_sig = tuple(Signature(func._dbus_in_signature))
Packit 130fc8
        else:
Packit 130fc8
            # magic iterator which returns as many v's as we need
Packit 130fc8
            in_sig = _VariantSignature()
Packit 130fc8
Packit 130fc8
        if func._dbus_out_signature:
Packit 130fc8
            out_sig = Signature(func._dbus_out_signature)
Packit 130fc8
        else:
Packit 130fc8
            # its tempting to default to Signature('v'), but
Packit 130fc8
            # for methods that return nothing, providing incorrect
Packit 130fc8
            # introspection data is worse than providing none at all
Packit 130fc8
            out_sig = []
Packit 130fc8
Packit 130fc8
        reflection_data = '    <method name="%s">\n' % (func.__name__)
Packit 130fc8
        for pair in zip(in_sig, args):
Packit 130fc8
            reflection_data += '      <arg direction="in"  type="%s" name="%s" />\n' % pair
Packit 130fc8
        for type in out_sig:
Packit 130fc8
            reflection_data += '      <arg direction="out" type="%s" />\n' % type
Packit 130fc8
        reflection_data += '    </method>\n'
Packit 130fc8
Packit 130fc8
        return reflection_data
Packit 130fc8
Packit 130fc8
    def _reflect_on_signal(cls, func):
Packit 130fc8
        args = func._dbus_args
Packit 130fc8
Packit 130fc8
        if func._dbus_signature:
Packit 130fc8
            # convert signature into a tuple so length refers to number of
Packit 130fc8
            # types, not number of characters
Packit 130fc8
            sig = tuple(Signature(func._dbus_signature))
Packit 130fc8
        else:
Packit 130fc8
            # magic iterator which returns as many v's as we need
Packit 130fc8
            sig = _VariantSignature()
Packit 130fc8
Packit 130fc8
        reflection_data = '    <signal name="%s">\n' % (func.__name__)
Packit 130fc8
        for pair in zip(sig, args):
Packit 130fc8
            reflection_data = reflection_data + '      <arg type="%s" name="%s" />\n' % pair
Packit 130fc8
        reflection_data = reflection_data + '    </signal>\n'
Packit 130fc8
Packit 130fc8
        return reflection_data
Packit 130fc8
Packit 130fc8
Packit 130fc8
# Define Interface as an instance of the metaclass InterfaceType, in a way
Packit 130fc8
# that is compatible across both Python 2 and Python 3.
Packit 130fc8
Interface = InterfaceType('Interface', (object,), {})
Packit 130fc8
Packit 130fc8
Packit 130fc8
#: A unique object used as the value of Object._object_path and
Packit 130fc8
#: Object._connection if it's actually in more than one place
Packit 130fc8
_MANY = object()
Packit 130fc8
Packit 130fc8
class Object(Interface):
Packit 130fc8
    r"""A base class for exporting your own Objects across the Bus.
Packit 130fc8
Packit 130fc8
    Just inherit from Object and mark exported methods with the
Packit 130fc8
    @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
Packit 130fc8
Packit 130fc8
    Example::
Packit 130fc8
Packit 130fc8
        class Example(dbus.service.object):
Packit 130fc8
            def __init__(self, object_path):
Packit 130fc8
                dbus.service.Object.__init__(self, dbus.SessionBus(), path)
Packit 130fc8
                self._last_input = None
Packit 130fc8
Packit 130fc8
            @dbus.service.method(interface='com.example.Sample',
Packit 130fc8
                                 in_signature='v', out_signature='s')
Packit 130fc8
            def StringifyVariant(self, var):
Packit 130fc8
                self.LastInputChanged(var)      # emits the signal
Packit 130fc8
                return str(var)
Packit 130fc8
Packit 130fc8
            @dbus.service.signal(interface='com.example.Sample',
Packit 130fc8
                                 signature='v')
Packit 130fc8
            def LastInputChanged(self, var):
Packit 130fc8
                # run just before the signal is actually emitted
Packit 130fc8
                # just put "pass" if nothing should happen
Packit 130fc8
                self._last_input = var
Packit 130fc8
Packit 130fc8
            @dbus.service.method(interface='com.example.Sample',
Packit 130fc8
                                 in_signature='', out_signature='v')
Packit 130fc8
            def GetLastInput(self):
Packit 130fc8
                return self._last_input
Packit 130fc8
    """
Packit 130fc8
Packit 130fc8
    #: If True, this object can be made available at more than one object path.
Packit 130fc8
    #: If True but `SUPPORTS_MULTIPLE_CONNECTIONS` is False, the object may
Packit 130fc8
    #: handle more than one object path, but they must all be on the same
Packit 130fc8
    #: connection.
Packit 130fc8
    SUPPORTS_MULTIPLE_OBJECT_PATHS = False
Packit 130fc8
Packit 130fc8
    #: If True, this object can be made available on more than one connection.
Packit 130fc8
    #: If True but `SUPPORTS_MULTIPLE_OBJECT_PATHS` is False, the object must
Packit 130fc8
    #: have the same object path on all its connections.
Packit 130fc8
    SUPPORTS_MULTIPLE_CONNECTIONS = False
Packit 130fc8
Packit 130fc8
    def __init__(self, conn=None, object_path=None, bus_name=None):
Packit 130fc8
        """Constructor. Either conn or bus_name is required; object_path
Packit 130fc8
        is also required.
Packit 130fc8
Packit 130fc8
        :Parameters:
Packit 130fc8
            `conn` : dbus.connection.Connection or None
Packit 130fc8
                The connection on which to export this object.
Packit 130fc8
Packit 130fc8
                If None, use the Bus associated with the given ``bus_name``.
Packit 130fc8
                If there is no ``bus_name`` either, the object is not
Packit 130fc8
                initially available on any Connection.
Packit 130fc8
Packit 130fc8
                For backwards compatibility, if an instance of
Packit 130fc8
                dbus.service.BusName is passed as the first parameter,
Packit 130fc8
                this is equivalent to passing its associated Bus as
Packit 130fc8
                ``conn``, and passing the BusName itself as ``bus_name``.
Packit 130fc8
Packit 130fc8
            `object_path` : str or None
Packit 130fc8
                A D-Bus object path at which to make this Object available
Packit 130fc8
                immediately. If this is not None, a `conn` or `bus_name` must
Packit 130fc8
                also be provided.
Packit 130fc8
Packit 130fc8
            `bus_name` : dbus.service.BusName or None
Packit 130fc8
                Represents a well-known name claimed by this process. A
Packit 130fc8
                reference to the BusName object will be held by this
Packit 130fc8
                Object, preventing the name from being released during this
Packit 130fc8
                Object's lifetime (unless it's released manually).
Packit 130fc8
        """
Packit 130fc8
        if object_path is not None:
Packit 130fc8
            validate_object_path(object_path)
Packit 130fc8
Packit 130fc8
        if isinstance(conn, BusName):
Packit 130fc8
            # someone's using the old API; don't gratuitously break them
Packit 130fc8
            bus_name = conn
Packit 130fc8
            conn = bus_name.get_bus()
Packit 130fc8
        elif conn is None:
Packit 130fc8
            if bus_name is not None:
Packit 130fc8
                # someone's using the old API but naming arguments, probably
Packit 130fc8
                conn = bus_name.get_bus()
Packit 130fc8
Packit 130fc8
        #: Either an object path, None or _MANY
Packit 130fc8
        self._object_path = None
Packit 130fc8
        #: Either a dbus.connection.Connection, None or _MANY
Packit 130fc8
        self._connection = None
Packit 130fc8
        #: A list of tuples (Connection, object path, False) where the False
Packit 130fc8
        #: is for future expansion (to support fallback paths)
Packit 130fc8
        self._locations = []
Packit 130fc8
        #: Lock protecting `_locations`, `_connection` and `_object_path`
Packit 130fc8
        self._locations_lock = threading.Lock()
Packit 130fc8
Packit 130fc8
        #: True if this is a fallback object handling a whole subtree.
Packit 130fc8
        self._fallback = False
Packit 130fc8
Packit 130fc8
        self._name = bus_name
Packit 130fc8
Packit 130fc8
        if conn is None and object_path is not None:
Packit 130fc8
            raise TypeError('If object_path is given, either conn or bus_name '
Packit 130fc8
                            'is required')
Packit 130fc8
        if conn is not None and object_path is not None:
Packit 130fc8
            self.add_to_connection(conn, object_path)
Packit 130fc8
Packit 130fc8
    @property
Packit 130fc8
    def __dbus_object_path__(self):
Packit 130fc8
        """The object-path at which this object is available.
Packit 130fc8
        Access raises AttributeError if there is no object path, or more than
Packit 130fc8
        one object path.
Packit 130fc8
Packit 130fc8
        Changed in 0.82.0: AttributeError can be raised.
Packit 130fc8
        """
Packit 130fc8
        if self._object_path is _MANY:
Packit 130fc8
            raise AttributeError('Object %r has more than one object path: '
Packit 130fc8
                                 'use Object.locations instead' % self)
Packit 130fc8
        elif self._object_path is None:
Packit 130fc8
            raise AttributeError('Object %r has no object path yet' % self)
Packit 130fc8
        else:
Packit 130fc8
            return self._object_path
Packit 130fc8
Packit 130fc8
    @property
Packit 130fc8
    def connection(self):
Packit 130fc8
        """The Connection on which this object is available.
Packit 130fc8
        Access raises AttributeError if there is no Connection, or more than
Packit 130fc8
        one Connection.
Packit 130fc8
Packit 130fc8
        Changed in 0.82.0: AttributeError can be raised.
Packit 130fc8
        """
Packit 130fc8
        if self._connection is _MANY:
Packit 130fc8
            raise AttributeError('Object %r is on more than one Connection: '
Packit 130fc8
                                 'use Object.locations instead' % self)
Packit 130fc8
        elif self._connection is None:
Packit 130fc8
            raise AttributeError('Object %r has no Connection yet' % self)
Packit 130fc8
        else:
Packit 130fc8
            return self._connection
Packit 130fc8
Packit 130fc8
    @property
Packit 130fc8
    def locations(self):
Packit 130fc8
        """An iterable over tuples representing locations at which this
Packit 130fc8
        object is available.
Packit 130fc8
Packit 130fc8
        Each tuple has at least two items, but may have more in future
Packit 130fc8
        versions of dbus-python, so do not rely on their exact length.
Packit 130fc8
        The first two items are the dbus.connection.Connection and the object
Packit 130fc8
        path.
Packit 130fc8
Packit 130fc8
        :Since: 0.82.0
Packit 130fc8
        """
Packit 130fc8
        return iter(self._locations)
Packit 130fc8
Packit 130fc8
    def add_to_connection(self, connection, path):
Packit 130fc8
        """Make this object accessible via the given D-Bus connection and
Packit 130fc8
        object path.
Packit 130fc8
Packit 130fc8
        :Parameters:
Packit 130fc8
            `connection` : dbus.connection.Connection
Packit 130fc8
                Export the object on this connection. If the class attribute
Packit 130fc8
                SUPPORTS_MULTIPLE_CONNECTIONS is False (default), this object
Packit 130fc8
                can only be made available on one connection; if the class
Packit 130fc8
                attribute is set True by a subclass, the object can be made
Packit 130fc8
                available on more than one connection.
Packit 130fc8
Packit 130fc8
            `path` : dbus.ObjectPath or other str
Packit 130fc8
                Place the object at this object path. If the class attribute
Packit 130fc8
                SUPPORTS_MULTIPLE_OBJECT_PATHS is False (default), this object
Packit 130fc8
                can only be made available at one object path; if the class
Packit 130fc8
                attribute is set True by a subclass, the object can be made
Packit 130fc8
                available with more than one object path.
Packit 130fc8
Packit 130fc8
        :Raises ValueError: if the object's class attributes do not allow the
Packit 130fc8
            object to be exported in the desired way.
Packit 130fc8
        :Since: 0.82.0
Packit 130fc8
        """
Packit 130fc8
        if path == LOCAL_PATH:
Packit 130fc8
            raise ValueError('Objects may not be exported on the reserved '
Packit 130fc8
                             'path %s' % LOCAL_PATH)
Packit 130fc8
Packit 130fc8
        self._locations_lock.acquire()
Packit 130fc8
        try:
Packit 130fc8
            if (self._connection is not None and
Packit 130fc8
                self._connection is not connection and
Packit 130fc8
                not self.SUPPORTS_MULTIPLE_CONNECTIONS):
Packit 130fc8
                raise ValueError('%r is already exported on '
Packit 130fc8
                                 'connection %r' % (self, self._connection))
Packit 130fc8
Packit 130fc8
            if (self._object_path is not None and
Packit 130fc8
                not self.SUPPORTS_MULTIPLE_OBJECT_PATHS and
Packit 130fc8
                self._object_path != path):
Packit 130fc8
                raise ValueError('%r is already exported at object '
Packit 130fc8
                                 'path %s' % (self, self._object_path))
Packit 130fc8
Packit 130fc8
            connection._register_object_path(path, self._message_cb,
Packit 130fc8
                                             self._unregister_cb,
Packit 130fc8
                                             self._fallback)
Packit 130fc8
Packit 130fc8
            if self._connection is None:
Packit 130fc8
                self._connection = connection
Packit 130fc8
            elif self._connection is not connection:
Packit 130fc8
                self._connection = _MANY
Packit 130fc8
Packit 130fc8
            if self._object_path is None:
Packit 130fc8
                self._object_path = path
Packit 130fc8
            elif self._object_path != path:
Packit 130fc8
                self._object_path = _MANY
Packit 130fc8
Packit 130fc8
            self._locations.append((connection, path, self._fallback))
Packit 130fc8
        finally:
Packit 130fc8
            self._locations_lock.release()
Packit 130fc8
Packit 130fc8
    def remove_from_connection(self, connection=None, path=None):
Packit 130fc8
        """Make this object inaccessible via the given D-Bus connection
Packit 130fc8
        and object path. If no connection or path is specified,
Packit 130fc8
        the object ceases to be accessible via any connection or path.
Packit 130fc8
Packit 130fc8
        :Parameters:
Packit 130fc8
            `connection` : dbus.connection.Connection or None
Packit 130fc8
                Only remove the object from this Connection. If None,
Packit 130fc8
                remove from all Connections on which it's exported.
Packit 130fc8
            `path` : dbus.ObjectPath or other str, or None
Packit 130fc8
                Only remove the object from this object path. If None,
Packit 130fc8
                remove from all object paths.
Packit 130fc8
        :Raises LookupError:
Packit 130fc8
            if the object was not exported on the requested connection
Packit 130fc8
            or path, or (if both are None) was not exported at all.
Packit 130fc8
        :Since: 0.81.1
Packit 130fc8
        """
Packit 130fc8
        self._locations_lock.acquire()
Packit 130fc8
        try:
Packit 130fc8
            if self._object_path is None or self._connection is None:
Packit 130fc8
                raise LookupError('%r is not exported' % self)
Packit 130fc8
Packit 130fc8
            if connection is not None or path is not None:
Packit 130fc8
                dropped = []
Packit 130fc8
                for location in self._locations:
Packit 130fc8
                    if ((connection is None or location[0] is connection) and
Packit 130fc8
                        (path is None or location[1] == path)):
Packit 130fc8
                        dropped.append(location)
Packit 130fc8
            else:
Packit 130fc8
                dropped = self._locations
Packit 130fc8
                self._locations = []
Packit 130fc8
Packit 130fc8
            if not dropped:
Packit 130fc8
                raise LookupError('%r is not exported at a location matching '
Packit 130fc8
                                  '(%r,%r)' % (self, connection, path))
Packit 130fc8
Packit 130fc8
            for location in dropped:
Packit 130fc8
                try:
Packit 130fc8
                    location[0]._unregister_object_path(location[1])
Packit 130fc8
                except LookupError:
Packit 130fc8
                    pass
Packit 130fc8
                if self._locations:
Packit 130fc8
                    try:
Packit 130fc8
                        self._locations.remove(location)
Packit 130fc8
                    except ValueError:
Packit 130fc8
                        pass
Packit 130fc8
        finally:
Packit 130fc8
            self._locations_lock.release()
Packit 130fc8
Packit 130fc8
    def _unregister_cb(self, connection):
Packit 130fc8
        # there's not really enough information to do anything useful here
Packit 130fc8
        _logger.info('Unregistering exported object %r from some path '
Packit 130fc8
                     'on %r', self, connection)
Packit 130fc8
Packit 130fc8
    def _message_cb(self, connection, message):
Packit 130fc8
        if not isinstance(message, MethodCallMessage):
Packit 130fc8
            return
Packit 130fc8
Packit 130fc8
        try:
Packit 130fc8
            # lookup candidate method and parent method
Packit 130fc8
            method_name = message.get_member()
Packit 130fc8
            interface_name = message.get_interface()
Packit 130fc8
            (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
Packit 130fc8
Packit 130fc8
            # set up method call parameters
Packit 130fc8
            args = message.get_args_list(**parent_method._dbus_get_args_options)
Packit 130fc8
            keywords = {}
Packit 130fc8
Packit 130fc8
            if parent_method._dbus_out_signature is not None:
Packit 130fc8
                signature = Signature(parent_method._dbus_out_signature)
Packit 130fc8
            else:
Packit 130fc8
                signature = None
Packit 130fc8
Packit 130fc8
            # set up async callback functions
Packit 130fc8
            if parent_method._dbus_async_callbacks:
Packit 130fc8
                (return_callback, error_callback) = parent_method._dbus_async_callbacks
Packit 130fc8
                keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
Packit 130fc8
                keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
Packit 130fc8
Packit 130fc8
            # include the sender etc. if desired
Packit 130fc8
            if parent_method._dbus_sender_keyword:
Packit 130fc8
                keywords[parent_method._dbus_sender_keyword] = message.get_sender()
Packit 130fc8
            if parent_method._dbus_path_keyword:
Packit 130fc8
                keywords[parent_method._dbus_path_keyword] = message.get_path()
Packit 130fc8
            if parent_method._dbus_rel_path_keyword:
Packit 130fc8
                path = message.get_path()
Packit 130fc8
                rel_path = path
Packit 130fc8
                for exp in self._locations:
Packit 130fc8
                    # pathological case: if we're exported in two places,
Packit 130fc8
                    # one of which is a subtree of the other, then pick the
Packit 130fc8
                    # subtree by preference (i.e. minimize the length of
Packit 130fc8
                    # rel_path)
Packit 130fc8
                    if exp[0] is connection:
Packit 130fc8
                        if path == exp[1]:
Packit 130fc8
                            rel_path = '/'
Packit 130fc8
                            break
Packit 130fc8
                        if exp[1] == '/':
Packit 130fc8
                            # we already have rel_path == path at the beginning
Packit 130fc8
                            continue
Packit 130fc8
                        if path.startswith(exp[1] + '/'):
Packit 130fc8
                            # yes we're in this exported subtree
Packit 130fc8
                            suffix = path[len(exp[1]):]
Packit 130fc8
                            if len(suffix) < len(rel_path):
Packit 130fc8
                                rel_path = suffix
Packit 130fc8
                rel_path = ObjectPath(rel_path)
Packit 130fc8
                keywords[parent_method._dbus_rel_path_keyword] = rel_path
Packit 130fc8
Packit 130fc8
            if parent_method._dbus_destination_keyword:
Packit 130fc8
                keywords[parent_method._dbus_destination_keyword] = message.get_destination()
Packit 130fc8
            if parent_method._dbus_message_keyword:
Packit 130fc8
                keywords[parent_method._dbus_message_keyword] = message
Packit 130fc8
            if parent_method._dbus_connection_keyword:
Packit 130fc8
                keywords[parent_method._dbus_connection_keyword] = connection
Packit 130fc8
Packit 130fc8
            # call method
Packit 130fc8
            retval = candidate_method(self, *args, **keywords)
Packit 130fc8
Packit 130fc8
            # we're done - the method has got callback functions to reply with
Packit 130fc8
            if parent_method._dbus_async_callbacks:
Packit 130fc8
                return
Packit 130fc8
Packit 130fc8
            # otherwise we send the return values in a reply. if we have a
Packit 130fc8
            # signature, use it to turn the return value into a tuple as
Packit 130fc8
            # appropriate
Packit 130fc8
            if signature is not None:
Packit 130fc8
                signature_tuple = tuple(signature)
Packit 130fc8
                # if we have zero or one return values we want make a tuple
Packit 130fc8
                # for the _method_reply_return function, otherwise we need
Packit 130fc8
                # to check we're passing it a sequence
Packit 130fc8
                if len(signature_tuple) == 0:
Packit 130fc8
                    if retval == None:
Packit 130fc8
                        retval = ()
Packit 130fc8
                    else:
Packit 130fc8
                        raise TypeError('%s has an empty output signature but did not return None' %
Packit 130fc8
                            method_name)
Packit 130fc8
                elif len(signature_tuple) == 1:
Packit 130fc8
                    retval = (retval,)
Packit 130fc8
                else:
Packit 130fc8
                    if isinstance(retval, Sequence):
Packit 130fc8
                        # multi-value signature, multi-value return... proceed
Packit 130fc8
                        # unchanged
Packit 130fc8
                        pass
Packit 130fc8
                    else:
Packit 130fc8
                        raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
Packit 130fc8
                            (method_name, signature))
Packit 130fc8
Packit 130fc8
            # no signature, so just turn the return into a tuple and send it as normal
Packit 130fc8
            else:
Packit 130fc8
                if retval is None:
Packit 130fc8
                    retval = ()
Packit 130fc8
                elif (isinstance(retval, tuple)
Packit 130fc8
                      and not isinstance(retval, Struct)):
Packit 130fc8
                # If the return is a tuple that is not a Struct, we use it
Packit 130fc8
                # as-is on the assumption that there are multiple return
Packit 130fc8
                # values - this is the usual Python idiom. (fd.o #10174)
Packit 130fc8
                    pass
Packit 130fc8
                else:
Packit 130fc8
                    retval = (retval,)
Packit 130fc8
Packit 130fc8
            _method_reply_return(connection, message, method_name, signature, *retval)
Packit 130fc8
        except Exception as exception:
Packit 130fc8
            # send error reply
Packit 130fc8
            _method_reply_error(connection, message, exception)
Packit 130fc8
Packit 130fc8
    @method(INTROSPECTABLE_IFACE, in_signature='', out_signature='s',
Packit 130fc8
            path_keyword='object_path', connection_keyword='connection')
Packit 130fc8
    def Introspect(self, object_path, connection):
Packit 130fc8
        """Return a string of XML encoding this object's supported interfaces,
Packit 130fc8
        methods and signals.
Packit 130fc8
        """
Packit 130fc8
        reflection_data = _dbus_bindings.DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
Packit 130fc8
        reflection_data += '<node name="%s">\n' % object_path
Packit 130fc8
Packit 130fc8
        interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
Packit 130fc8
        for (name, funcs) in interfaces.items():
Packit 130fc8
            reflection_data += '  <interface name="%s">\n' % (name)
Packit 130fc8
Packit 130fc8
            for func in funcs.values():
Packit 130fc8
                if getattr(func, '_dbus_is_method', False):
Packit 130fc8
                    reflection_data += self.__class__._reflect_on_method(func)
Packit 130fc8
                elif getattr(func, '_dbus_is_signal', False):
Packit 130fc8
                    reflection_data += self.__class__._reflect_on_signal(func)
Packit 130fc8
Packit 130fc8
            reflection_data += '  </interface>\n'
Packit 130fc8
Packit 130fc8
        for name in connection.list_exported_child_objects(object_path):
Packit 130fc8
            reflection_data += '  <node name="%s"/>\n' % name
Packit 130fc8
Packit 130fc8
        reflection_data += '</node>\n'
Packit 130fc8
Packit 130fc8
        return reflection_data
Packit 130fc8
Packit 130fc8
    def __repr__(self):
Packit 130fc8
        where = ''
Packit 130fc8
        if (self._object_path is not _MANY
Packit 130fc8
            and self._object_path is not None):
Packit 130fc8
            where = ' at %s' % self._object_path
Packit 130fc8
        return '<%s.%s%s at %#x>' % (self.__class__.__module__,
Packit 130fc8
                                   self.__class__.__name__, where,
Packit 130fc8
                                   id(self))
Packit 130fc8
    __str__ = __repr__
Packit 130fc8
Packit 130fc8
class FallbackObject(Object):
Packit 130fc8
    """An object that implements an entire subtree of the object-path
Packit 130fc8
    tree.
Packit 130fc8
Packit 130fc8
    :Since: 0.82.0
Packit 130fc8
    """
Packit 130fc8
Packit 130fc8
    SUPPORTS_MULTIPLE_OBJECT_PATHS = True
Packit 130fc8
Packit 130fc8
    def __init__(self, conn=None, object_path=None):
Packit 130fc8
        """Constructor.
Packit 130fc8
Packit 130fc8
        Note that the superclass' ``bus_name`` __init__ argument is not
Packit 130fc8
        supported here.
Packit 130fc8
Packit 130fc8
        :Parameters:
Packit 130fc8
            `conn` : dbus.connection.Connection or None
Packit 130fc8
                The connection on which to export this object. If this is not
Packit 130fc8
                None, an `object_path` must also be provided.
Packit 130fc8
Packit 130fc8
                If None, the object is not initially available on any
Packit 130fc8
                Connection.
Packit 130fc8
Packit 130fc8
            `object_path` : str or None
Packit 130fc8
                A D-Bus object path at which to make this Object available
Packit 130fc8
                immediately. If this is not None, a `conn` must also be
Packit 130fc8
                provided.
Packit 130fc8
Packit 130fc8
                This object will implements all object-paths in the subtree
Packit 130fc8
                starting at this object-path, except where a more specific
Packit 130fc8
                object has been added.
Packit 130fc8
        """
Packit 130fc8
        super(FallbackObject, self).__init__()
Packit 130fc8
        self._fallback = True
Packit 130fc8
Packit 130fc8
        if conn is None:
Packit 130fc8
            if object_path is not None:
Packit 130fc8
                raise TypeError('If object_path is given, conn is required')
Packit 130fc8
        elif object_path is None:
Packit 130fc8
            raise TypeError('If conn is given, object_path is required')
Packit 130fc8
        else:
Packit 130fc8
            self.add_to_connection(conn, object_path)