summaryrefslogtreecommitdiff
path: root/dbus
diff options
context:
space:
mode:
Diffstat (limited to 'dbus')
-rw-r--r--dbus/__init__.py105
-rw-r--r--dbus/_dbus.py256
-rw-r--r--dbus/_expat_introspect_parser.py85
-rw-r--r--dbus/_version.py3
-rw-r--r--dbus/_version.py.in3
-rw-r--r--dbus/bus.py439
-rw-r--r--dbus/connection.py646
-rw-r--r--dbus/dbus_bindings.py37
-rw-r--r--dbus/decorators.py340
-rw-r--r--dbus/exceptions.py106
-rw-r--r--dbus/glib.py41
-rw-r--r--dbus/gobject_service.py71
-rw-r--r--dbus/lowlevel.py40
-rw-r--r--dbus/mainloop/__init__.py62
-rw-r--r--dbus/mainloop/glib.py41
-rw-r--r--dbus/proxies.py557
-rw-r--r--dbus/server.py117
-rw-r--r--dbus/service.py829
-rw-r--r--dbus/types.py9
19 files changed, 3787 insertions, 0 deletions
diff --git a/dbus/__init__.py b/dbus/__init__.py
new file mode 100644
index 0000000..e990ec3
--- /dev/null
+++ b/dbus/__init__.py
@@ -0,0 +1,105 @@
+"""\
+Implements the public API for a D-Bus client. See the dbus.service module
+to export objects or claim well-known names.
+
+..
+ for epydoc's benefit
+
+:NewField SupportedUsage: Supported usage
+:NewField Constructor: Constructor
+"""
+
+# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import os
+
+__all__ = (
+ # from _dbus
+ 'Bus', 'SystemBus', 'SessionBus', 'StarterBus',
+
+ # from proxies
+ 'Interface',
+
+ # from _dbus_bindings
+ 'get_default_main_loop', 'set_default_main_loop',
+
+ 'validate_interface_name', 'validate_member_name',
+ 'validate_bus_name', 'validate_object_path',
+ 'validate_error_name',
+
+ 'BUS_DAEMON_NAME', 'BUS_DAEMON_PATH', 'BUS_DAEMON_IFACE',
+ 'LOCAL_PATH', 'LOCAL_IFACE', 'PEER_IFACE',
+ 'INTROSPECTABLE_IFACE', 'PROPERTIES_IFACE',
+
+ 'ObjectPath', 'ByteArray', 'Signature', 'Byte', 'Boolean',
+ 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64',
+ 'Double', 'String', 'Array', 'Struct', 'Dictionary', 'UTF8String',
+
+ # from exceptions
+ 'DBusException',
+ 'MissingErrorHandlerException', 'MissingReplyHandlerException',
+ 'ValidationException', 'IntrospectionParserException',
+ 'UnknownMethodException', 'NameExistsException',
+
+ # submodules
+ 'service', 'mainloop', 'lowlevel'
+ )
+__docformat__ = 'restructuredtext'
+
+try:
+ from dbus._version import version, __version__
+except ImportError:
+ pass
+
+# OLPC Sugar compatibility
+import dbus.exceptions as exceptions
+import dbus.types as types
+
+from _dbus_bindings import get_default_main_loop, set_default_main_loop,\
+ validate_interface_name, validate_member_name,\
+ validate_bus_name, validate_object_path,\
+ validate_error_name
+from _dbus_bindings import BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
+ LOCAL_PATH, LOCAL_IFACE, PEER_IFACE,\
+ INTROSPECTABLE_IFACE, PROPERTIES_IFACE
+
+from dbus.exceptions import MissingErrorHandlerException, \
+ MissingReplyHandlerException, \
+ ValidationException, \
+ IntrospectionParserException, \
+ UnknownMethodException, \
+ NameExistsException, \
+ DBusException
+from _dbus_bindings import ObjectPath, ByteArray, Signature, Byte, Boolean,\
+ Int16, UInt16, Int32, UInt32, Int64, UInt64,\
+ Double, String, Array, Struct, Dictionary, \
+ UTF8String
+from dbus._dbus import Bus, SystemBus, SessionBus, StarterBus
+from dbus.proxies import Interface
+
+
+if 'DBUS_PYTHON_NO_DEPRECATED' not in os.environ:
+ from dbus._dbus import dbus_bindings # for backwards compat
diff --git a/dbus/_dbus.py b/dbus/_dbus.py
new file mode 100644
index 0000000..60e7933
--- /dev/null
+++ b/dbus/_dbus.py
@@ -0,0 +1,256 @@
+"""Implementation for dbus.Bus. Not to be imported directly."""
+
+# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from __future__ import generators
+
+__all__ = ('Bus', 'SystemBus', 'SessionBus', 'StarterBus')
+__docformat__ = 'reStructuredText'
+
+import os
+import sys
+import weakref
+from traceback import print_exc
+
+from dbus.exceptions import DBusException
+from _dbus_bindings import BUS_DAEMON_NAME, BUS_DAEMON_PATH,\
+ BUS_DAEMON_IFACE, UTF8String,\
+ validate_member_name, validate_interface_name,\
+ validate_bus_name, validate_object_path,\
+ BUS_SESSION, BUS_SYSTEM, BUS_STARTER,\
+ DBUS_START_REPLY_SUCCESS, \
+ DBUS_START_REPLY_ALREADY_RUNNING
+from dbus.bus import BusConnection
+from dbus.lowlevel import SignalMessage
+
+try:
+ import thread
+except ImportError:
+ import dummy_thread as thread
+
+
+class Bus(BusConnection):
+ """A connection to one of three possible standard buses, the SESSION,
+ SYSTEM, or STARTER bus. This class manages shared connections to those
+ buses.
+
+ If you're trying to subclass `Bus`, you may be better off subclassing
+ `BusConnection`, which doesn't have all this magic.
+ """
+
+ _shared_instances = {}
+
+ def __new__(cls, bus_type=BusConnection.TYPE_SESSION, private=False,
+ mainloop=None):
+ """Constructor, returning an existing instance where appropriate.
+
+ The returned instance is actually always an instance of `SessionBus`,
+ `SystemBus` or `StarterBus`.
+
+ :Parameters:
+ `bus_type` : cls.TYPE_SESSION, cls.TYPE_SYSTEM or cls.TYPE_STARTER
+ Connect to the appropriate bus
+ `private` : bool
+ If true, never return an existing shared instance, but instead
+ return a private connection.
+
+ :Deprecated: since 0.82.3. Use dbus.bus.BusConnection for
+ private connections.
+
+ `mainloop` : dbus.mainloop.NativeMainLoop
+ The main loop to use. The default is to use the default
+ main loop if one has been set up, or raise an exception
+ if none has been.
+ :Changed: in dbus-python 0.80:
+ converted from a wrapper around a Connection to a Connection
+ subclass.
+ """
+ if (not private and bus_type in cls._shared_instances):
+ return cls._shared_instances[bus_type]
+
+ # this is a bit odd, but we create instances of the subtypes
+ # so we can return the shared instances if someone tries to
+ # construct one of them (otherwise we'd eg try and return an
+ # instance of Bus from __new__ in SessionBus). why are there
+ # three ways to construct this class? we just don't know.
+ if bus_type == BUS_SESSION:
+ subclass = SessionBus
+ elif bus_type == BUS_SYSTEM:
+ subclass = SystemBus
+ elif bus_type == BUS_STARTER:
+ subclass = StarterBus
+ else:
+ raise ValueError('invalid bus_type %s' % bus_type)
+
+ bus = BusConnection.__new__(subclass, bus_type, mainloop=mainloop)
+
+ bus._bus_type = bus_type
+
+ if not private:
+ cls._shared_instances[bus_type] = bus
+
+ return bus
+
+ def close(self):
+ t = self._bus_type
+ if self.__class__._shared_instances.get(t) is self:
+ del self.__class__._shared_instances[t]
+ super(Bus, self).close()
+
+ def get_connection(self):
+ """Return self, for backwards compatibility with earlier dbus-python
+ versions where Bus was not a subclass of Connection.
+
+ :Deprecated: since 0.80.0
+ """
+ return self
+ _connection = property(get_connection, None, None,
+ """self._connection == self, for backwards
+ compatibility with earlier dbus-python versions
+ where Bus was not a subclass of Connection.""")
+
+ def get_session(private=False):
+ """Static method that returns a connection to the session bus.
+
+ :Parameters:
+ `private` : bool
+ If true, do not return a shared connection.
+ """
+ return SessionBus(private=private)
+
+ get_session = staticmethod(get_session)
+
+ def get_system(private=False):
+ """Static method that returns a connection to the system bus.
+
+ :Parameters:
+ `private` : bool
+ If true, do not return a shared connection.
+ """
+ return SystemBus(private=private)
+
+ get_system = staticmethod(get_system)
+
+
+ def get_starter(private=False):
+ """Static method that returns a connection to the starter bus.
+
+ :Parameters:
+ `private` : bool
+ If true, do not return a shared connection.
+ """
+ return StarterBus(private=private)
+
+ get_starter = staticmethod(get_starter)
+
+ def __repr__(self):
+ if self._bus_type == BUS_SESSION:
+ name = 'session'
+ elif self._bus_type == BUS_SYSTEM:
+ name = 'system'
+ elif self._bus_type == BUS_STARTER:
+ name = 'starter'
+ else:
+ name = 'unknown bus type'
+
+ return '<%s.%s (%s) at %#x>' % (self.__class__.__module__,
+ self.__class__.__name__,
+ name, id(self))
+ __str__ = __repr__
+
+
+# FIXME: Drop the subclasses here? I can't think why we'd ever want
+# polymorphism
+class SystemBus(Bus):
+ """The system-wide message bus."""
+ def __new__(cls, private=False, mainloop=None):
+ """Return a connection to the system bus.
+
+ :Parameters:
+ `private` : bool
+ If true, never return an existing shared instance, but instead
+ return a private connection.
+ `mainloop` : dbus.mainloop.NativeMainLoop
+ The main loop to use. The default is to use the default
+ main loop if one has been set up, or raise an exception
+ if none has been.
+ """
+ return Bus.__new__(cls, Bus.TYPE_SYSTEM, mainloop=mainloop,
+ private=private)
+
+class SessionBus(Bus):
+ """The session (current login) message bus."""
+ def __new__(cls, private=False, mainloop=None):
+ """Return a connection to the session bus.
+
+ :Parameters:
+ `private` : bool
+ If true, never return an existing shared instance, but instead
+ return a private connection.
+ `mainloop` : dbus.mainloop.NativeMainLoop
+ The main loop to use. The default is to use the default
+ main loop if one has been set up, or raise an exception
+ if none has been.
+ """
+ return Bus.__new__(cls, Bus.TYPE_SESSION, private=private,
+ mainloop=mainloop)
+
+class StarterBus(Bus):
+ """The bus that activated this process (only valid if
+ this process was launched by DBus activation).
+ """
+ def __new__(cls, private=False, mainloop=None):
+ """Return a connection to the bus that activated this process.
+
+ :Parameters:
+ `private` : bool
+ If true, never return an existing shared instance, but instead
+ return a private connection.
+ `mainloop` : dbus.mainloop.NativeMainLoop
+ The main loop to use. The default is to use the default
+ main loop if one has been set up, or raise an exception
+ if none has been.
+ """
+ return Bus.__new__(cls, Bus.TYPE_STARTER, private=private,
+ mainloop=mainloop)
+
+
+if 'DBUS_PYTHON_NO_DEPRECATED' not in os.environ:
+
+ class _DBusBindingsEmulation:
+ """A partial emulation of the dbus_bindings module."""
+ def __str__(self):
+ return '_DBusBindingsEmulation()'
+ def __repr__(self):
+ return '_DBusBindingsEmulation()'
+ def __getattr__(self, attr):
+ global dbus_bindings
+ import dbus.dbus_bindings as m
+ dbus_bindings = m
+ return getattr(m, attr)
+
+ dbus_bindings = _DBusBindingsEmulation()
+ """Deprecated, don't use."""
diff --git a/dbus/_expat_introspect_parser.py b/dbus/_expat_introspect_parser.py
new file mode 100644
index 0000000..96b27ad
--- /dev/null
+++ b/dbus/_expat_introspect_parser.py
@@ -0,0 +1,85 @@
+# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from xml.parsers.expat import ExpatError, ParserCreate
+from dbus.exceptions import IntrospectionParserException
+
+class _Parser(object):
+ __slots__ = ('map', 'in_iface', 'in_method', 'sig')
+ def __init__(self):
+ self.map = {}
+ self.in_iface = ''
+ self.in_method = ''
+ self.sig = ''
+
+ def parse(self, data):
+ parser = ParserCreate('UTF-8', ' ')
+ parser.buffer_text = True
+ parser.StartElementHandler = self.StartElementHandler
+ parser.EndElementHandler = self.EndElementHandler
+ parser.Parse(data)
+ return self.map
+
+ def StartElementHandler(self, name, attributes):
+ if not self.in_iface:
+ if (not self.in_method and name == 'interface'):
+ self.in_iface = attributes['name']
+ else:
+ if (not self.in_method and name == 'method'):
+ self.in_method = attributes['name']
+ elif (self.in_method and name == 'arg'):
+ if attributes.get('direction', 'in') == 'in':
+ self.sig += attributes['type']
+
+ def EndElementHandler(self, name):
+ if self.in_iface:
+ if (not self.in_method and name == 'interface'):
+ self.in_iface = ''
+ elif (self.in_method and name == 'method'):
+ self.map[self.in_iface + '.' + self.in_method] = self.sig
+ self.in_method = ''
+ self.sig = ''
+
+def process_introspection_data(data):
+ """Return a dict mapping ``interface.method`` strings to the
+ concatenation of all their 'in' parameters, and mapping
+ ``interface.signal`` strings to the concatenation of all their
+ parameters.
+
+ Example output::
+
+ {
+ 'com.example.SignalEmitter.OneString': 's',
+ 'com.example.MethodImplementor.OneInt32Argument': 'i',
+ }
+
+ :Parameters:
+ `data` : str
+ The introspection XML. Must be an 8-bit string of UTF-8.
+ """
+ try:
+ return _Parser().parse(data)
+ except Exception, e:
+ raise IntrospectionParserException('%s: %s' % (e.__class__, e))
diff --git a/dbus/_version.py b/dbus/_version.py
new file mode 100644
index 0000000..a2dca5f
--- /dev/null
+++ b/dbus/_version.py
@@ -0,0 +1,3 @@
+# dbus/_version.py. Generated from _version.py.in by configure.
+version = (0, 83, 1)
+__version__ = "0.83.1"
diff --git a/dbus/_version.py.in b/dbus/_version.py.in
new file mode 100644
index 0000000..42c9dc7
--- /dev/null
+++ b/dbus/_version.py.in
@@ -0,0 +1,3 @@
+# @configure_input@
+version = (@DBUS_PYTHON_MAJOR_VERSION@, @DBUS_PYTHON_MINOR_VERSION@, @DBUS_PYTHON_MICRO_VERSION@)
+__version__ = "@DBUS_PYTHON_MAJOR_VERSION@.@DBUS_PYTHON_MINOR_VERSION@.@DBUS_PYTHON_MICRO_VERSION@"
diff --git a/dbus/bus.py b/dbus/bus.py
new file mode 100644
index 0000000..edd5ef7
--- /dev/null
+++ b/dbus/bus.py
@@ -0,0 +1,439 @@
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('BusConnection',)
+__docformat__ = 'reStructuredText'
+
+import logging
+import weakref
+
+from _dbus_bindings import validate_interface_name, validate_member_name,\
+ validate_bus_name, validate_object_path,\
+ validate_error_name,\
+ BUS_SESSION, BUS_STARTER, BUS_SYSTEM, \
+ DBUS_START_REPLY_SUCCESS, \
+ DBUS_START_REPLY_ALREADY_RUNNING, \
+ BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
+ NAME_FLAG_ALLOW_REPLACEMENT, \
+ NAME_FLAG_DO_NOT_QUEUE, \
+ NAME_FLAG_REPLACE_EXISTING, \
+ RELEASE_NAME_REPLY_NON_EXISTENT, \
+ RELEASE_NAME_REPLY_NOT_OWNER, \
+ RELEASE_NAME_REPLY_RELEASED, \
+ REQUEST_NAME_REPLY_ALREADY_OWNER, \
+ REQUEST_NAME_REPLY_EXISTS, \
+ REQUEST_NAME_REPLY_IN_QUEUE, \
+ REQUEST_NAME_REPLY_PRIMARY_OWNER
+from dbus.connection import Connection
+from dbus.exceptions import DBusException
+from dbus.lowlevel import HANDLER_RESULT_NOT_YET_HANDLED
+
+
+_NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
+ "interface='%s',member='NameOwnerChanged',"
+ "path='%s',arg0='%%s'"
+ % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
+ BUS_DAEMON_PATH))
+"""(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
+messages"""
+
+_NAME_HAS_NO_OWNER = 'org.freedesktop.DBus.Error.NameHasNoOwner'
+
+_logger = logging.getLogger('dbus.bus')
+
+
+class NameOwnerWatch(object):
+ __slots__ = ('_match', '_pending_call')
+
+ def __init__(self, bus_conn, bus_name, callback):
+ validate_bus_name(bus_name)
+
+ def signal_cb(owned, old_owner, new_owner):
+ callback(new_owner)
+
+ def error_cb(e):
+ if e.get_dbus_name() == _NAME_HAS_NO_OWNER:
+ callback('')
+ else:
+ logging.basicConfig()
+ _logger.debug('GetNameOwner(%s) failed:', bus_name,
+ exc_info=(e.__class__, e, None))
+
+ self._match = bus_conn.add_signal_receiver(signal_cb,
+ 'NameOwnerChanged',
+ BUS_DAEMON_IFACE,
+ BUS_DAEMON_NAME,
+ BUS_DAEMON_PATH,
+ arg0=bus_name)
+ self._pending_call = bus_conn.call_async(BUS_DAEMON_NAME,
+ BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE,
+ 'GetNameOwner',
+ 's', (bus_name,),
+ callback, error_cb,
+ utf8_strings=True)
+
+ def cancel(self):
+ if self._match is not None:
+ self._match.remove()
+ if self._pending_call is not None:
+ self._pending_call.cancel()
+ self._match = None
+ self._pending_call = None
+
+
+class BusConnection(Connection):
+ """A connection to a D-Bus daemon that implements the
+ ``org.freedesktop.DBus`` pseudo-service.
+
+ :Since: 0.81.0
+ """
+
+ TYPE_SESSION = BUS_SESSION
+ """Represents a session bus (same as the global dbus.BUS_SESSION)"""
+
+ TYPE_SYSTEM = BUS_SYSTEM
+ """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
+
+ TYPE_STARTER = BUS_STARTER
+ """Represents the bus that started this service by activation (same as
+ the global dbus.BUS_STARTER)"""
+
+ START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
+ START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
+
+ def __new__(cls, address_or_type=TYPE_SESSION, mainloop=None):
+ bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
+
+ # _bus_names is used by dbus.service.BusName!
+ bus._bus_names = weakref.WeakValueDictionary()
+
+ bus._signal_sender_matches = {}
+ """Map from SignalMatch to NameOwnerWatch."""
+
+ return bus
+
+ def add_signal_receiver(self, handler_function, signal_name=None,
+ dbus_interface=None, bus_name=None,
+ path=None, **keywords):
+ named_service = keywords.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both be '
+ 'specified')
+ bus_name = named_service
+ from warnings import warn
+ warn('Passing the named_service parameter to add_signal_receiver '
+ 'by name is deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+
+ match = super(BusConnection, self).add_signal_receiver(
+ handler_function, signal_name, dbus_interface, bus_name,
+ path, **keywords)
+
+ if (bus_name is not None and bus_name != BUS_DAEMON_NAME):
+ if bus_name[:1] == ':':
+ def callback(new_owner):
+ if new_owner == '':
+ match.remove()
+ else:
+ callback = match.set_sender_name_owner
+ watch = self.watch_name_owner(bus_name, callback)
+ self._signal_sender_matches[match] = watch
+
+ self.add_match_string(str(match))
+
+ return match
+
+ def _clean_up_signal_match(self, match):
+ # The signals lock is no longer held here (it was in <= 0.81.0)
+ self.remove_match_string_non_blocking(str(match))
+ watch = self._signal_sender_matches.pop(match, None)
+ if watch is not None:
+ watch.cancel()
+
+ def activate_name_owner(self, bus_name):
+ if (bus_name is not None and bus_name[:1] != ':'
+ and bus_name != BUS_DAEMON_NAME):
+ try:
+ return self.get_name_owner(bus_name)
+ except DBusException, e:
+ if e.get_dbus_name() != _NAME_HAS_NO_OWNER:
+ raise
+ # else it doesn't exist: try to start it
+ self.start_service_by_name(bus_name)
+ return self.get_name_owner(bus_name)
+ else:
+ # already unique
+ return bus_name
+
+ def get_object(self, bus_name, object_path, introspect=True,
+ follow_name_owner_changes=False, **kwargs):
+ """Return a local proxy for the given remote object.
+
+ Method calls on the proxy are translated into method calls on the
+ remote object.
+
+ :Parameters:
+ `bus_name` : str
+ A bus name (either the unique name or a well-known name)
+ of the application owning the object. The keyword argument
+ named_service is a deprecated alias for this.
+ `object_path` : str
+ The object path of the desired object
+ `introspect` : bool
+ If true (default), attempt to introspect the remote
+ object to find out supported methods and their signatures
+ `follow_name_owner_changes` : bool
+ If the object path is a well-known name and this parameter
+ is false (default), resolve the well-known name to the unique
+ name of its current owner and bind to that instead; if the
+ ownership of the well-known name changes in future,
+ keep communicating with the original owner.
+ This is necessary if the D-Bus API used is stateful.
+
+ If the object path is a well-known name and this parameter
+ is true, whenever the well-known name changes ownership in
+ future, bind to the new owner, if any.
+
+ If the given object path is a unique name, this parameter
+ has no effect.
+
+ :Returns: a `dbus.proxies.ProxyObject`
+ :Raises `DBusException`: if resolving the well-known name to a
+ unique name fails
+ """
+ if follow_name_owner_changes:
+ self._require_main_loop() # we don't get the signals otherwise
+
+ named_service = kwargs.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both '
+ 'be specified')
+ from warnings import warn
+ warn('Passing the named_service parameter to get_object by name '
+ 'is deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+ bus_name = named_service
+ if kwargs:
+ raise TypeError('get_object does not take these keyword '
+ 'arguments: %s' % ', '.join(kwargs.iterkeys()))
+
+ return self.ProxyObjectClass(self, bus_name, object_path,
+ introspect=introspect,
+ follow_name_owner_changes=follow_name_owner_changes)
+
+ def get_unix_user(self, bus_name):
+ """Get the numeric uid of the process owning the given bus name.
+
+ :Parameters:
+ `bus_name` : str
+ A bus name, either unique or well-known
+ :Returns: a `dbus.UInt32`
+ :Since: 0.80.0
+ """
+ validate_bus_name(bus_name)
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'GetConnectionUnixUser',
+ 's', (bus_name,))
+
+ def start_service_by_name(self, bus_name, flags=0):
+ """Start a service which will implement the given bus name on this Bus.
+
+ :Parameters:
+ `bus_name` : str
+ The well-known bus name to be activated.
+ `flags` : dbus.UInt32
+ Flags to pass to StartServiceByName (currently none are
+ defined)
+
+ :Returns: A tuple of 2 elements. The first is always True, the
+ second is either START_REPLY_SUCCESS or
+ START_REPLY_ALREADY_RUNNING.
+
+ :Raises `DBusException`: if the service could not be started.
+ :Since: 0.80.0
+ """
+ validate_bus_name(bus_name)
+ return (True, self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE,
+ 'StartServiceByName',
+ 'su', (bus_name, flags)))
+
+ # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
+ # but this would not be backwards-compatible
+ def request_name(self, name, flags=0):
+ """Request a bus name.
+
+ :Parameters:
+ `name` : str
+ The well-known name to be requested
+ `flags` : dbus.UInt32
+ A bitwise-OR of 0 or more of the flags
+ `NAME_FLAG_ALLOW_REPLACEMENT`,
+ `NAME_FLAG_REPLACE_EXISTING`
+ and `NAME_FLAG_DO_NOT_QUEUE`
+ :Returns: `REQUEST_NAME_REPLY_PRIMARY_OWNER`,
+ `REQUEST_NAME_REPLY_IN_QUEUE`,
+ `REQUEST_NAME_REPLY_EXISTS` or
+ `REQUEST_NAME_REPLY_ALREADY_OWNER`
+ :Raises `DBusException`: if the bus daemon cannot be contacted or
+ returns an error.
+ """
+ validate_bus_name(name, allow_unique=False)
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'RequestName',
+ 'su', (name, flags))
+
+ def release_name(self, name):
+ """Release a bus name.
+
+ :Parameters:
+ `name` : str
+ The well-known name to be released
+ :Returns: `RELEASE_NAME_REPLY_RELEASED`,
+ `RELEASE_NAME_REPLY_NON_EXISTENT`
+ or `RELEASE_NAME_REPLY_NOT_OWNER`
+ :Raises `DBusException`: if the bus daemon cannot be contacted or
+ returns an error.
+ """
+ validate_bus_name(name, allow_unique=False)
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'ReleaseName',
+ 's', (name,))
+
+ def list_names(self):
+ """Return a list of all currently-owned names on the bus.
+
+ :Returns: a dbus.Array of dbus.UTF8String
+ :Since: 0.81.0
+ """
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'ListNames',
+ '', (), utf8_strings=True)
+
+ def list_activatable_names(self):
+ """Return a list of all names that can be activated on the bus.
+
+ :Returns: a dbus.Array of dbus.UTF8String
+ :Since: 0.81.0
+ """
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'ListNames',
+ '', (), utf8_strings=True)
+
+ def get_name_owner(self, bus_name):
+ """Return the unique connection name of the primary owner of the
+ given name.
+
+ :Raises `DBusException`: if the `bus_name` has no owner
+ :Since: 0.81.0
+ """
+ validate_bus_name(bus_name, allow_unique=False)
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'GetNameOwner',
+ 's', (bus_name,), utf8_strings=True)
+
+ def watch_name_owner(self, bus_name, callback):
+ """Watch the unique connection name of the primary owner of the
+ given name.
+
+ `callback` will be called with one argument, which is either the
+ unique connection name, or the empty string (meaning the name is
+ not owned).
+
+ :Since: 0.81.0
+ """
+ return NameOwnerWatch(self, bus_name, callback)
+
+ def name_has_owner(self, bus_name):
+ """Return True iff the given bus name has an owner on this bus.
+
+ :Parameters:
+ `bus_name` : str
+ The bus name to look up
+ :Returns: a `bool`
+ """
+ return bool(self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'NameHasOwner',
+ 's', (bus_name,)))
+
+ def add_match_string(self, rule):
+ """Arrange for this application to receive messages on the bus that
+ match the given rule. This version will block.
+
+ :Parameters:
+ `rule` : str
+ The match rule
+ :Raises `DBusException`: on error.
+ :Since: 0.80.0
+ """
+ self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,))
+
+ # FIXME: add an async success/error handler capability?
+ # (and the same for remove_...)
+ def add_match_string_non_blocking(self, rule):
+ """Arrange for this application to receive messages on the bus that
+ match the given rule. This version will not block, but any errors
+ will be ignored.
+
+
+ :Parameters:
+ `rule` : str
+ The match rule
+ :Raises `DBusException`: on error.
+ :Since: 0.80.0
+ """
+ self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,),
+ None, None)
+
+ def remove_match_string(self, rule):
+ """Arrange for this application to receive messages on the bus that
+ match the given rule. This version will block.
+
+ :Parameters:
+ `rule` : str
+ The match rule
+ :Raises `DBusException`: on error.
+ :Since: 0.80.0
+ """
+ self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,))
+
+ def remove_match_string_non_blocking(self, rule):
+ """Arrange for this application to receive messages on the bus that
+ match the given rule. This version will not block, but any errors
+ will be ignored.
+
+
+ :Parameters:
+ `rule` : str
+ The match rule
+ :Raises `DBusException`: on error.
+ :Since: 0.80.0
+ """
+ self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,),
+ None, None)
diff --git a/dbus/connection.py b/dbus/connection.py
new file mode 100644
index 0000000..d76aaf2
--- /dev/null
+++ b/dbus/connection.py
@@ -0,0 +1,646 @@
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('Connection', 'SignalMatch')
+__docformat__ = 'reStructuredText'
+
+import logging
+try:
+ import thread
+except ImportError:
+ import dummy_thread as thread
+import weakref
+
+from _dbus_bindings import Connection as _Connection, \
+ LOCAL_PATH, LOCAL_IFACE, \
+ validate_interface_name, validate_member_name,\
+ validate_bus_name, validate_object_path,\
+ validate_error_name, \
+ UTF8String
+from dbus.exceptions import DBusException
+from dbus.lowlevel import ErrorMessage, MethodCallMessage, SignalMessage, \
+ MethodReturnMessage, HANDLER_RESULT_NOT_YET_HANDLED
+from dbus.proxies import ProxyObject
+
+
+_logger = logging.getLogger('dbus.connection')
+
+
+def _noop(*args, **kwargs):
+ pass
+
+
+class SignalMatch(object):
+ __slots__ = ('_sender_name_owner', '_member', '_interface', '_sender',
+ '_path', '_handler', '_args_match', '_rule',
+ '_utf8_strings', '_byte_arrays', '_conn_weakref',
+ '_destination_keyword', '_interface_keyword',
+ '_message_keyword', '_member_keyword',
+ '_sender_keyword', '_path_keyword', '_int_args_match')
+
+ def __init__(self, conn, sender, object_path, dbus_interface,
+ member, handler, utf8_strings=False, byte_arrays=False,
+ sender_keyword=None, path_keyword=None,
+ interface_keyword=None, member_keyword=None,
+ message_keyword=None, destination_keyword=None,
+ **kwargs):
+ if member is not None:
+ validate_member_name(member)
+ if dbus_interface is not None:
+ validate_interface_name(dbus_interface)
+ if sender is not None:
+ validate_bus_name(sender)
+ if object_path is not None:
+ validate_object_path(object_path)
+
+ self._rule = None
+ self._conn_weakref = weakref.ref(conn)
+ self._sender = sender
+ self._interface = dbus_interface
+ self._member = member
+ self._path = object_path
+ self._handler = handler
+
+ # if the connection is actually a bus, it's responsible for changing
+ # this later
+ self._sender_name_owner = sender
+
+ self._utf8_strings = utf8_strings
+ self._byte_arrays = byte_arrays
+ self._sender_keyword = sender_keyword
+ self._path_keyword = path_keyword
+ self._member_keyword = member_keyword
+ self._interface_keyword = interface_keyword
+ self._message_keyword = message_keyword
+ self._destination_keyword = destination_keyword
+
+ self._args_match = kwargs
+ if not kwargs:
+ self._int_args_match = None
+ else:
+ self._int_args_match = {}
+ for kwarg in kwargs:
+ if not kwarg.startswith('arg'):
+ raise TypeError('SignalMatch: unknown keyword argument %s'
+ % kwarg)
+ try:
+ index = int(kwarg[3:])
+ except ValueError:
+ raise TypeError('SignalMatch: unknown keyword argument %s'
+ % kwarg)
+ if index < 0 or index > 63:
+ raise TypeError('SignalMatch: arg match index must be in '
+ 'range(64), not %d' % index)
+ self._int_args_match[index] = kwargs[kwarg]
+
+ def __hash__(self):
+ """SignalMatch objects are compared by identity."""
+ return hash(id(self))
+
+ def __eq__(self, other):
+ """SignalMatch objects are compared by identity."""
+ return self is other
+
+ def __ne__(self, other):
+ """SignalMatch objects are compared by identity."""
+ return self is not other
+
+ sender = property(lambda self: self._sender)
+
+ def __str__(self):
+ if self._rule is None:
+ rule = ["type='signal'"]
+ if self._sender is not None:
+ rule.append("sender='%s'" % self._sender)
+ if self._path is not None:
+ rule.append("path='%s'" % self._path)
+ if self._interface is not None:
+ rule.append("interface='%s'" % self._interface)
+ if self._member is not None:
+ rule.append("member='%s'" % self._member)
+ if self._int_args_match is not None:
+ for index, value in self._int_args_match.iteritems():
+ rule.append("arg%d='%s'" % (index, value))
+
+ self._rule = ','.join(rule)
+
+ return self._rule
+
+ def __repr__(self):
+ return ('<%s at %x "%s" on conn %r>'
+ % (self.__class__, id(self), self._rule, self._conn_weakref()))
+
+ def set_sender_name_owner(self, new_name):
+ self._sender_name_owner = new_name
+
+ def matches_removal_spec(self, sender, object_path,
+ dbus_interface, member, handler, **kwargs):
+ if handler not in (None, self._handler):
+ return False
+ if sender != self._sender:
+ return False
+ if object_path != self._path:
+ return False
+ if dbus_interface != self._interface:
+ return False
+ if member != self._member:
+ return False
+ if kwargs != self._args_match:
+ return False
+ return True
+
+ def maybe_handle_message(self, message):
+ args = None
+
+ # these haven't been checked yet by the match tree
+ if self._sender_name_owner not in (None, message.get_sender()):
+ return False
+ if self._int_args_match is not None:
+ # extracting args with utf8_strings and byte_arrays is less work
+ args = message.get_args_list(utf8_strings=True, byte_arrays=True)
+ for index, value in self._int_args_match.iteritems():
+ if (index >= len(args)
+ or not isinstance(args[index], UTF8String)
+ or args[index] != value):
+ return False
+
+ # these have likely already been checked by the match tree
+ if self._member not in (None, message.get_member()):
+ return False
+ if self._interface not in (None, message.get_interface()):
+ return False
+ if self._path not in (None, message.get_path()):
+ return False
+
+ try:
+ # minor optimization: if we already extracted the args with the
+ # right calling convention to do the args match, don't bother
+ # doing so again
+ if args is None or not self._utf8_strings or not self._byte_arrays:
+ args = message.get_args_list(utf8_strings=self._utf8_strings,
+ byte_arrays=self._byte_arrays)
+ kwargs = {}
+ if self._sender_keyword is not None:
+ kwargs[self._sender_keyword] = message.get_sender()
+ if self._destination_keyword is not None:
+ kwargs[self._destination_keyword] = message.get_destination()
+ if self._path_keyword is not None:
+ kwargs[self._path_keyword] = message.get_path()
+ if self._member_keyword is not None:
+ kwargs[self._member_keyword] = message.get_member()
+ if self._interface_keyword is not None:
+ kwargs[self._interface_keyword] = message.get_interface()
+ if self._message_keyword is not None:
+ kwargs[self._message_keyword] = message
+ self._handler(*args, **kwargs)
+ except:
+ # basicConfig is a no-op if logging is already configured
+ logging.basicConfig()
+ _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
+
+ return True
+
+ def remove(self):
+ conn = self._conn_weakref()
+ # do nothing if the connection has already vanished
+ if conn is not None:
+ conn.remove_signal_receiver(self, self._member,
+ self._interface, self._sender,
+ self._path,
+ **self._args_match)
+
+
+class Connection(_Connection):
+ """A connection to another application. In this base class there is
+ assumed to be no bus daemon.
+
+ :Since: 0.81.0
+ """
+
+ ProxyObjectClass = ProxyObject
+
+ def __init__(self, *args, **kwargs):
+ super(Connection, self).__init__(*args, **kwargs)
+
+ # this if-block is needed because shared bus connections can be
+ # __init__'ed more than once
+ if not hasattr(self, '_dbus_Connection_initialized'):
+ self._dbus_Connection_initialized = 1
+
+ self.__call_on_disconnection = []
+
+ self._signal_recipients_by_object_path = {}
+ """Map from object path to dict mapping dbus_interface to dict
+ mapping member to list of SignalMatch objects."""
+
+ self._signals_lock = thread.allocate_lock()
+ """Lock used to protect signal data structures"""
+
+ self.add_message_filter(self.__class__._signal_func)
+
+ def activate_name_owner(self, bus_name):
+ """Return the unique name for the given bus name, activating it
+ if necessary and possible.
+
+ If the name is already unique or this connection is not to a
+ bus daemon, just return it.
+
+ :Returns: a bus name. If the given `bus_name` exists, the returned
+ name identifies its current owner; otherwise the returned name
+ does not exist.
+ :Raises DBusException: if the implementation has failed
+ to activate the given bus name.
+ :Since: 0.81.0
+ """
+ return bus_name
+
+ def get_object(self, bus_name=None, object_path=None, introspect=True,
+ **kwargs):
+ """Return a local proxy for the given remote object.
+
+ Method calls on the proxy are translated into method calls on the
+ remote object.
+
+ :Parameters:
+ `bus_name` : str
+ A bus name (either the unique name or a well-known name)
+ of the application owning the object. The keyword argument
+ named_service is a deprecated alias for this.
+ `object_path` : str
+ The object path of the desired object
+ `introspect` : bool
+ If true (default), attempt to introspect the remote
+ object to find out supported methods and their signatures
+
+ :Returns: a `dbus.proxies.ProxyObject`
+ """
+ named_service = kwargs.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both '
+ 'be specified')
+ from warnings import warn
+ warn('Passing the named_service parameter to get_object by name '
+ 'is deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+ bus_name = named_service
+ if kwargs:
+ raise TypeError('get_object does not take these keyword '
+ 'arguments: %s' % ', '.join(kwargs.iterkeys()))
+
+ return self.ProxyObjectClass(self, bus_name, object_path,
+ introspect=introspect)
+
+ def add_signal_receiver(self, handler_function,
+ signal_name=None,
+ dbus_interface=None,
+ bus_name=None,
+ path=None,
+ **keywords):
+ """Arrange for the given function to be called when a signal matching
+ the parameters is received.
+
+ :Parameters:
+ `handler_function` : callable
+ The function to be called. Its positional arguments will
+ be the arguments of the signal. By default it will receive
+ no keyword arguments, but see the description of
+ the optional keyword arguments below.
+ `signal_name` : str
+ The signal name; None (the default) matches all names
+ `dbus_interface` : str
+ The D-Bus interface name with which to qualify the signal;
+ None (the default) matches all interface names
+ `bus_name` : str
+ A bus name for the sender, which will be resolved to a
+ unique name if it is not already; None (the default) matches
+ any sender.
+ `path` : str
+ The object path of the object which must have emitted the
+ signal; None (the default) matches any object path
+ :Keywords:
+ `utf8_strings` : bool
+ If True, the handler function will receive any string
+ arguments as dbus.UTF8String objects (a subclass of str
+ guaranteed to be UTF-8). If False (default) it will receive
+ any string arguments as dbus.String objects (a subclass of
+ unicode).
+ `byte_arrays` : bool
+ If True, the handler function will receive any byte-array
+ arguments as dbus.ByteArray objects (a subclass of str).
+ If False (default) it will receive any byte-array
+ arguments as a dbus.Array of dbus.Byte (subclasses of:
+ a list of ints).
+ `sender_keyword` : str
+ If not None (the default), the handler function will receive
+ the unique name of the sending endpoint as a keyword
+ argument with this name.
+ `destination_keyword` : str
+ If not None (the default), the handler function will receive
+ the bus name of the destination (or None if the signal is a
+ broadcast, as is usual) as a keyword argument with this name.
+ `interface_keyword` : str
+ If not None (the default), the handler function will receive
+ the signal interface as a keyword argument with this name.
+ `member_keyword` : str
+ If not None (the default), the handler function will receive
+ the signal name as a keyword argument with this name.
+ `path_keyword` : str
+ If not None (the default), the handler function will receive
+ the object-path of the sending object as a keyword argument
+ with this name.
+ `message_keyword` : str
+ If not None (the default), the handler function will receive
+ the `dbus.lowlevel.SignalMessage` as a keyword argument with
+ this name.
+ `arg...` : unicode or UTF-8 str
+ If there are additional keyword parameters of the form
+ ``arg``\ *n*, match only signals where the *n*\ th argument
+ is the value given for that keyword parameter. As of this
+ time only string arguments can be matched (in particular,
+ object paths and signatures can't).
+ `named_service` : str
+ A deprecated alias for `bus_name`.
+ """
+ self._require_main_loop()
+
+ named_service = keywords.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both be '
+ 'specified')
+ bus_name = named_service
+ from warnings import warn
+ warn('Passing the named_service parameter to add_signal_receiver '
+ 'by name is deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+
+ match = SignalMatch(self, bus_name, path, dbus_interface,
+ signal_name, handler_function, **keywords)
+
+ self._signals_lock.acquire()
+ try:
+ by_interface = self._signal_recipients_by_object_path.setdefault(
+ path, {})
+ by_member = by_interface.setdefault(dbus_interface, {})
+ matches = by_member.setdefault(signal_name, [])
+
+ matches.append(match)
+ finally:
+ self._signals_lock.release()
+
+ return match
+
+ def _iter_easy_matches(self, path, dbus_interface, member):
+ if path is not None:
+ path_keys = (None, path)
+ else:
+ path_keys = (None,)
+ if dbus_interface is not None:
+ interface_keys = (None, dbus_interface)
+ else:
+ interface_keys = (None,)
+ if member is not None:
+ member_keys = (None, member)
+ else:
+ member_keys = (None,)
+
+ for path in path_keys:
+ by_interface = self._signal_recipients_by_object_path.get(path,
+ None)
+ if by_interface is None:
+ continue
+ for dbus_interface in interface_keys:
+ by_member = by_interface.get(dbus_interface, None)
+ if by_member is None:
+ continue
+ for member in member_keys:
+ matches = by_member.get(member, None)
+ if matches is None:
+ continue
+ for m in matches:
+ yield m
+
+ def remove_signal_receiver(self, handler_or_match,
+ signal_name=None,
+ dbus_interface=None,
+ bus_name=None,
+ path=None,
+ **keywords):
+ named_service = keywords.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both be '
+ 'specified')
+ bus_name = named_service
+ from warnings import warn
+ warn('Passing the named_service parameter to '
+ 'remove_signal_receiver by name is deprecated: please use '
+ 'positional parameters',
+ DeprecationWarning, stacklevel=2)
+
+ new = []
+ deletions = []
+ self._signals_lock.acquire()
+ try:
+ by_interface = self._signal_recipients_by_object_path.get(path,
+ None)
+ if by_interface is None:
+ return
+ by_member = by_interface.get(dbus_interface, None)
+ if by_member is None:
+ return
+ matches = by_member.get(signal_name, None)
+ if matches is None:
+ return
+
+ for match in matches:
+ if (handler_or_match is match
+ or match.matches_removal_spec(bus_name,
+ path,
+ dbus_interface,
+ signal_name,
+ handler_or_match,
+ **keywords)):
+ deletions.append(match)
+ else:
+ new.append(match)
+
+ if new:
+ by_member[signal_name] = new
+ else:
+ del by_member[signal_name]
+ if not by_member:
+ del by_interface[dbus_interface]
+ if not by_interface:
+ del self._signal_recipients_by_object_path[path]
+ finally:
+ self._signals_lock.release()
+
+ for match in deletions:
+ self._clean_up_signal_match(match)
+
+ def _clean_up_signal_match(self, match):
+ # Now called without the signals lock held (it was held in <= 0.81.0)
+ pass
+
+ def _signal_func(self, message):
+ """D-Bus filter function. Handle signals by dispatching to Python
+ callbacks kept in the match-rule tree.
+ """
+
+ if not isinstance(message, SignalMessage):
+ return HANDLER_RESULT_NOT_YET_HANDLED
+
+ dbus_interface = message.get_interface()
+ path = message.get_path()
+ signal_name = message.get_member()
+
+ for match in self._iter_easy_matches(path, dbus_interface,
+ signal_name):
+ match.maybe_handle_message(message)
+
+ if (dbus_interface == LOCAL_IFACE and
+ path == LOCAL_PATH and
+ signal_name == 'Disconnected'):
+ for cb in self.__call_on_disconnection:
+ try:
+ cb(self)
+ except Exception, e:
+ # basicConfig is a no-op if logging is already configured
+ logging.basicConfig()
+ _logger.error('Exception in handler for Disconnected '
+ 'signal:', exc_info=1)
+
+ return HANDLER_RESULT_NOT_YET_HANDLED
+
+ def call_async(self, bus_name, object_path, dbus_interface, method,
+ signature, args, reply_handler, error_handler,
+ timeout=-1.0, utf8_strings=False, byte_arrays=False,
+ require_main_loop=True):
+ """Call the given method, asynchronously.
+
+ If the reply_handler is None, successful replies will be ignored.
+ If the error_handler is None, failures will be ignored. If both
+ are None, the implementation may request that no reply is sent.
+
+ :Returns: The dbus.lowlevel.PendingCall.
+ :Since: 0.81.0
+ """
+ if object_path == LOCAL_PATH:
+ raise DBusException('Methods may not be called on the reserved '
+ 'path %s' % LOCAL_PATH)
+ if dbus_interface == LOCAL_IFACE:
+ raise DBusException('Methods may not be called on the reserved '
+ 'interface %s' % LOCAL_IFACE)
+ # no need to validate other args - MethodCallMessage ctor will do
+
+ get_args_opts = {'utf8_strings': utf8_strings,
+ 'byte_arrays': byte_arrays}
+
+ message = MethodCallMessage(destination=bus_name,
+ path=object_path,
+ interface=dbus_interface,
+ method=method)
+ # Add the arguments to the function
+ try:
+ message.append(signature=signature, *args)
+ except Exception, e:
+ logging.basicConfig()
+ _logger.error('Unable to set arguments %r according to '
+ 'signature %r: %s: %s',
+ args, signature, e.__class__, e)
+ raise
+
+ if reply_handler is None and error_handler is None:
+ # we don't care what happens, so just send it
+ self.send_message(message)
+ return
+
+ if reply_handler is None:
+ reply_handler = _noop
+ if error_handler is None:
+ error_handler = _noop
+
+ def msg_reply_handler(message):
+ if isinstance(message, MethodReturnMessage):
+ reply_handler(*message.get_args_list(**get_args_opts))
+ elif isinstance(message, ErrorMessage):
+ error_handler(DBusException(name=message.get_error_name(),
+ *message.get_args_list()))
+ else:
+ error_handler(TypeError('Unexpected type for reply '
+ 'message: %r' % message))
+ return self.send_message_with_reply(message, msg_reply_handler,
+ timeout,
+ require_main_loop=require_main_loop)
+
+ def call_blocking(self, bus_name, object_path, dbus_interface, method,
+ signature, args, timeout=-1.0, utf8_strings=False,
+ byte_arrays=False):
+ """Call the given method, synchronously.
+ :Since: 0.81.0
+ """
+ if object_path == LOCAL_PATH:
+ raise DBusException('Methods may not be called on the reserved '
+ 'path %s' % LOCAL_PATH)
+ if dbus_interface == LOCAL_IFACE:
+ raise DBusException('Methods may not be called on the reserved '
+ 'interface %s' % LOCAL_IFACE)
+ # no need to validate other args - MethodCallMessage ctor will do
+
+ get_args_opts = {'utf8_strings': utf8_strings,
+ 'byte_arrays': byte_arrays}
+
+ message = MethodCallMessage(destination=bus_name,
+ path=object_path,
+ interface=dbus_interface,
+ method=method)
+ # Add the arguments to the function
+ try:
+ message.append(signature=signature, *args)
+ except Exception, e:
+ logging.basicConfig()
+ _logger.error('Unable to set arguments %r according to '
+ 'signature %r: %s: %s',
+ args, signature, e.__class__, e)
+ raise
+
+ # make a blocking call
+ reply_message = self.send_message_with_reply_and_block(
+ message, timeout)
+ args_list = reply_message.get_args_list(**get_args_opts)
+ if len(args_list) == 0:
+ return None
+ elif len(args_list) == 1:
+ return args_list[0]
+ else:
+ return tuple(args_list)
+
+ def call_on_disconnection(self, callable):
+ """Arrange for `callable` to be called with one argument (this
+ Connection object) when the Connection becomes
+ disconnected.
+
+ :Since: 0.83.0
+ """
+ self.__call_on_disconnection.append(callable)
diff --git a/dbus/dbus_bindings.py b/dbus/dbus_bindings.py
new file mode 100644
index 0000000..a45ca9f
--- /dev/null
+++ b/dbus/dbus_bindings.py
@@ -0,0 +1,37 @@
+# Backwards-compatibility with the old dbus_bindings.
+
+from warnings import warn as _warn
+
+_dbus_bindings_warning = DeprecationWarning("""\
+The dbus_bindings module is not public API and will go away soon.
+
+Most uses of dbus_bindings are applications catching the exception
+dbus.dbus_bindings.DBusException. You should use dbus.DBusException
+instead (this is compatible with all dbus-python versions since 0.40.2).
+
+If you need additional public API, please contact the maintainers via
+<dbus@lists.freedesktop.org>.
+""")
+
+_warn(_dbus_bindings_warning, DeprecationWarning, stacklevel=2)
+
+# Exceptions
+from dbus.exceptions import DBusException
+class ConnectionError(Exception): pass
+
+# Types
+from dbus.types import *
+
+# Messages
+from _dbus_bindings import Message, SignalMessage as Signal,\
+ MethodCallMessage as MethodCall,\
+ MethodReturnMessage as MethodReturn,\
+ ErrorMessage as Error
+# MessageIter has gone away, thankfully
+
+# Connection
+from _dbus_bindings import Connection
+
+from dbus import Bus
+bus_request_name = Bus.request_name
+bus_release_name = Bus.release_name
diff --git a/dbus/decorators.py b/dbus/decorators.py
new file mode 100644
index 0000000..a5ca281
--- /dev/null
+++ b/dbus/decorators.py
@@ -0,0 +1,340 @@
+"""Service-side D-Bus decorators."""
+
+# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('method', 'signal')
+__docformat__ = 'restructuredtext'
+
+import inspect
+
+from dbus import validate_interface_name, Signature, validate_member_name
+from dbus.lowlevel import SignalMessage
+from dbus.exceptions import DBusException
+
+
+def method(dbus_interface, in_signature=None, out_signature=None,
+ async_callbacks=None,
+ sender_keyword=None, path_keyword=None, destination_keyword=None,
+ message_keyword=None, connection_keyword=None,
+ utf8_strings=False, byte_arrays=False,
+ rel_path_keyword=None):
+ """Factory for decorators used to mark methods of a `dbus.service.Object`
+ to be exported on the D-Bus.
+
+ The decorated method will be exported over D-Bus as the method of the
+ same name on the given D-Bus interface.
+
+ :Parameters:
+ `dbus_interface` : str
+ Name of a D-Bus interface
+ `in_signature` : str or None
+ If not None, the signature of the method parameters in the usual
+ D-Bus notation
+ `out_signature` : str or None
+ If not None, the signature of the return value in the usual
+ D-Bus notation
+ `async_callbacks` : tuple containing (str,str), or None
+ If None (default) the decorated method is expected to return
+ values matching the `out_signature` as usual, or raise
+ an exception on error. If not None, the following applies:
+
+ `async_callbacks` contains the names of two keyword arguments to
+ the decorated function, which will be used to provide a success
+ callback and an error callback (in that order).
+
+ When the decorated method is called via the D-Bus, its normal
+ return value will be ignored; instead, a pair of callbacks are
+ passed as keyword arguments, and the decorated method is
+ expected to arrange for one of them to be called.
+
+ On success the success callback must be called, passing the
+ results of this method as positional parameters in the format
+ given by the `out_signature`.
+
+ On error the decorated method may either raise an exception
+ before it returns, or arrange for the error callback to be
+ called with an Exception instance as parameter.
+
+ `sender_keyword` : str or None
+ If not None, contains the name of a keyword argument to the
+ decorated function, conventionally ``'sender'``. When the
+ method is called, the sender's unique name will be passed as
+ this keyword argument.
+
+ `path_keyword` : str or None
+ If not None (the default), the decorated method will receive
+ the destination object path as a keyword argument with this
+ name. Normally you already know the object path, but in the
+ case of "fallback paths" you'll usually want to use the object
+ path in the method's implementation.
+
+ For fallback objects, `rel_path_keyword` (new in 0.82.2) is
+ likely to be more useful.
+
+ :Since: 0.80.0?
+
+ `rel_path_keyword` : str or None
+ If not None (the default), the decorated method will receive
+ the destination object path, relative to the path at which the
+ object was exported, as a keyword argument with this
+ name. For non-fallback objects the relative path will always be
+ '/'.
+
+ :Since: 0.82.2
+
+ `destination_keyword` : str or None
+ If not None (the default), the decorated method will receive
+ the destination bus name as a keyword argument with this name.
+ Included for completeness - you shouldn't need this.
+
+ :Since: 0.80.0?
+
+ `message_keyword` : str or None
+ If not None (the default), the decorated method will receive
+ the `dbus.lowlevel.MethodCallMessage` as a keyword argument
+ with this name.
+
+ :Since: 0.80.0?
+
+ `connection_keyword` : str or None
+ If not None (the default), the decorated method will receive
+ the `dbus.connection.Connection` as a keyword argument
+ with this name. This is generally only useful for objects
+ that are available on more than one connection.
+
+ :Since: 0.82.0
+
+ `utf8_strings` : bool
+ If False (default), D-Bus strings are passed to the decorated
+ method as objects of class dbus.String, a unicode subclass.
+
+ If True, D-Bus strings are passed to the decorated method
+ as objects of class dbus.UTF8String, a str subclass guaranteed
+ to be encoded in UTF-8.
+
+ This option does not affect object-paths and signatures, which
+ are always 8-bit strings (str subclass) encoded in ASCII.
+
+ :Since: 0.80.0
+
+ `byte_arrays` : bool
+ If False (default), a byte array will be passed to the decorated
+ method as an `Array` (a list subclass) of `Byte` objects.
+
+ If True, a byte array will be passed to the decorated method as
+ a `ByteArray`, a str subclass. This is usually what you want,
+ but is switched off by default to keep dbus-python's API
+ consistent.
+
+ :Since: 0.80.0
+ """
+ validate_interface_name(dbus_interface)
+
+ def decorator(func):
+ args = inspect.getargspec(func)[0]
+ args.pop(0)
+
+ if async_callbacks:
+ if type(async_callbacks) != tuple:
+ raise TypeError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
+ if len(async_callbacks) != 2:
+ raise ValueError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
+ args.remove(async_callbacks[0])
+ args.remove(async_callbacks[1])
+
+ if sender_keyword:
+ args.remove(sender_keyword)
+ if rel_path_keyword:
+ args.remove(rel_path_keyword)
+ if path_keyword:
+ args.remove(path_keyword)
+ if destination_keyword:
+ args.remove(destination_keyword)
+ if message_keyword:
+ args.remove(message_keyword)
+ if connection_keyword:
+ args.remove(connection_keyword)
+
+ if in_signature:
+ in_sig = tuple(Signature(in_signature))
+
+ if len(in_sig) > len(args):
+ raise ValueError, 'input signature is longer than the number of arguments taken'
+ elif len(in_sig) < len(args):
+ raise ValueError, 'input signature is shorter than the number of arguments taken'
+
+ func._dbus_is_method = True
+ func._dbus_async_callbacks = async_callbacks
+ func._dbus_interface = dbus_interface
+ func._dbus_in_signature = in_signature
+ func._dbus_out_signature = out_signature
+ func._dbus_sender_keyword = sender_keyword
+ func._dbus_path_keyword = path_keyword
+ func._dbus_rel_path_keyword = rel_path_keyword
+ func._dbus_destination_keyword = destination_keyword
+ func._dbus_message_keyword = message_keyword
+ func._dbus_connection_keyword = connection_keyword
+ func._dbus_args = args
+ func._dbus_get_args_options = {'byte_arrays': byte_arrays,
+ 'utf8_strings': utf8_strings}
+ return func
+
+ return decorator
+
+
+def signal(dbus_interface, signature=None, path_keyword=None,
+ rel_path_keyword=None):
+ """Factory for decorators used to mark methods of a `dbus.service.Object`
+ to emit signals on the D-Bus.
+
+ Whenever the decorated method is called in Python, after the method
+ body is executed, a signal with the same name as the decorated method,
+ with the given D-Bus interface, will be emitted from this object.
+
+ :Parameters:
+ `dbus_interface` : str
+ The D-Bus interface whose signal is emitted
+ `signature` : str
+ The signature of the signal in the usual D-Bus notation
+
+ `path_keyword` : str or None
+ A keyword argument to the decorated method. If not None,
+ that argument will not be emitted as an argument of
+ the signal, and when the signal is emitted, it will appear
+ to come from the object path given by the keyword argument.
+
+ Note that when calling the decorated method, you must always
+ pass in the object path as a keyword argument, not as a
+ positional argument.
+
+ This keyword argument cannot be used on objects where
+ the class attribute ``SUPPORTS_MULTIPLE_OBJECT_PATHS`` is true.
+
+ :Deprecated: since 0.82.0. Use `rel_path_keyword` instead.
+
+ `rel_path_keyword` : str or None
+ A keyword argument to the decorated method. If not None,
+ that argument will not be emitted as an argument of
+ the signal.
+
+ When the signal is emitted, if the named keyword argument is given,
+ the signal will appear to come from the object path obtained by
+ appending the keyword argument to the object's object path.
+ This is useful to implement "fallback objects" (objects which
+ own an entire subtree of the object-path tree).
+
+ If the object is available at more than one object-path on the
+ same or different connections, the signal will be emitted at
+ an appropriate object-path on each connection - for instance,
+ if the object is exported at /abc on connection 1 and at
+ /def and /x/y/z on connection 2, and the keyword argument is
+ /foo, then signals will be emitted from /abc/foo and /def/foo
+ on connection 1, and /x/y/z/foo on connection 2.
+
+ :Since: 0.82.0
+ """
+ validate_interface_name(dbus_interface)
+
+ if path_keyword is not None:
+ from warnings import warn
+ warn(DeprecationWarning('dbus.service.signal::path_keyword has been '
+ 'deprecated since dbus-python 0.82.0, and '
+ 'will not work on objects that support '
+ 'multiple object paths'),
+ DeprecationWarning, stacklevel=2)
+ if rel_path_keyword is not None:
+ raise TypeError('dbus.service.signal::path_keyword and '
+ 'rel_path_keyword cannot both be used')
+
+ def decorator(func):
+ member_name = func.__name__
+ validate_member_name(member_name)
+
+ def emit_signal(self, *args, **keywords):
+ abs_path = None
+ if path_keyword is not None:
+ if self.SUPPORTS_MULTIPLE_OBJECT_PATHS:
+ raise TypeError('path_keyword cannot be used on the '
+ 'signals of an object that supports '
+ 'multiple object paths')
+ abs_path = keywords.pop(path_keyword, None)
+ if (abs_path != self.__dbus_object_path__ and
+ not self.__dbus_object_path__.startswith(abs_path + '/')):
+ raise ValueError('Path %r is not below %r', abs_path,
+ self.__dbus_object_path__)
+
+ rel_path = None
+ if rel_path_keyword is not None:
+ rel_path = keywords.pop(rel_path_keyword, None)
+
+ func(self, *args, **keywords)
+
+ for location in self.locations:
+ if abs_path is None:
+ # non-deprecated case
+ if rel_path is None or rel_path in ('/', ''):
+ object_path = location[1]
+ else:
+ # will be validated by SignalMessage ctor in a moment
+ object_path = location[1] + rel_path
+ else:
+ object_path = abs_path
+
+ message = SignalMessage(object_path,
+ dbus_interface,
+ member_name)
+ message.append(signature=signature, *args)
+
+ location[0].send_message(message)
+ # end emit_signal
+
+ args = inspect.getargspec(func)[0]
+ args.pop(0)
+
+ for keyword in rel_path_keyword, path_keyword:
+ if keyword is not None:
+ try:
+ args.remove(keyword)
+ except ValueError:
+ raise ValueError('function has no argument "%s"' % keyword)
+
+ if signature:
+ sig = tuple(Signature(signature))
+
+ if len(sig) > len(args):
+ raise ValueError, 'signal signature is longer than the number of arguments provided'
+ elif len(sig) < len(args):
+ raise ValueError, 'signal signature is shorter than the number of arguments provided'
+
+ emit_signal.__name__ = func.__name__
+ emit_signal.__doc__ = func.__doc__
+ emit_signal._dbus_is_signal = True
+ emit_signal._dbus_interface = dbus_interface
+ emit_signal._dbus_signature = signature
+ emit_signal._dbus_args = args
+ return emit_signal
+
+ return decorator
diff --git a/dbus/exceptions.py b/dbus/exceptions.py
new file mode 100644
index 0000000..8d84a29
--- /dev/null
+++ b/dbus/exceptions.py
@@ -0,0 +1,106 @@
+"""D-Bus exceptions."""
+
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('DBusException', 'MissingErrorHandlerException',
+ 'MissingReplyHandlerException', 'ValidationException',
+ 'IntrospectionParserException', 'UnknownMethodException',
+ 'NameExistsException')
+
+class DBusException(Exception):
+
+ include_traceback = False
+ """If True, tracebacks will be included in the exception message sent to
+ D-Bus clients.
+
+ Exceptions that are not DBusException subclasses always behave
+ as though this is True. Set this to True on DBusException subclasses
+ that represent a programming error, and leave it False on subclasses that
+ represent an expected failure condition (e.g. a network server not
+ responding)."""
+
+ def __init__(self, *args, **kwargs):
+ name = kwargs.pop('name', None)
+ if name is not None or getattr(self, '_dbus_error_name', None) is None:
+ self._dbus_error_name = name
+ if kwargs:
+ raise TypeError('DBusException does not take keyword arguments: %s'
+ % ', '.join(kwargs.keys()))
+ Exception.__init__(self, *args)
+
+ def __str__(self):
+ s = Exception.__str__(self)
+ if self._dbus_error_name is not None:
+ return '%s: %s' % (self._dbus_error_name, s)
+ else:
+ return s
+
+ def get_dbus_message(self):
+ s = Exception.__str__(self)
+ return s.decode('utf-8', 'replace')
+
+ def get_dbus_name(self):
+ return self._dbus_error_name
+
+class MissingErrorHandlerException(DBusException):
+
+ include_traceback = True
+
+ def __init__(self):
+ DBusException.__init__(self, "error_handler not defined: if you define a reply_handler you must also define an error_handler")
+
+class MissingReplyHandlerException(DBusException):
+
+ include_traceback = True
+
+ def __init__(self):
+ DBusException.__init__(self, "reply_handler not defined: if you define an error_handler you must also define a reply_handler")
+
+class ValidationException(DBusException):
+
+ include_traceback = True
+
+ def __init__(self, msg=''):
+ DBusException.__init__(self, "Error validating string: %s"%msg)
+
+class IntrospectionParserException(DBusException):
+
+ include_traceback = True
+
+ def __init__(self, msg=''):
+ DBusException.__init__(self, "Error parsing introspect data: %s"%msg)
+
+class UnknownMethodException(DBusException):
+
+ include_traceback = True
+ _dbus_error_name = 'org.freedesktop.DBus.Error.UnknownMethod'
+
+ def __init__(self, method):
+ DBusException.__init__(self, "Unknown method: %s"%method)
+
+class NameExistsException(DBusException):
+
+ include_traceback = True
+
+ def __init__(self, name):
+ DBusException.__init__(self, "Bus name already exists: %s"%name)
diff --git a/dbus/glib.py b/dbus/glib.py
new file mode 100644
index 0000000..610de53
--- /dev/null
+++ b/dbus/glib.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2004 Anders Carlsson
+# Copyright (C) 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""Deprecated module which sets the default GLib main context as the mainloop
+implementation within D-Bus, as a side-effect of being imported!
+
+This API is highly non-obvious, so instead of importing this module,
+new programs which don't need pre-0.80 compatibility should use this
+equivalent code::
+
+ from dbus.mainloop.glib import DBusGMainLoop
+ DBusGMainLoop(set_as_default=True)
+"""
+__docformat__ = 'restructuredtext'
+
+from dbus.mainloop.glib import DBusGMainLoop, threads_init
+
+init_threads = threads_init
+
+DBusGMainLoop(set_as_default=True)
diff --git a/dbus/gobject_service.py b/dbus/gobject_service.py
new file mode 100644
index 0000000..61a7749
--- /dev/null
+++ b/dbus/gobject_service.py
@@ -0,0 +1,71 @@
+"""Support code for implementing D-Bus services via GObjects."""
+
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import gobject
+import dbus.service
+
+class ExportedGObjectType(gobject.GObjectMeta, dbus.service.InterfaceType):
+ """A metaclass which inherits from both GObjectMeta and
+ `dbus.service.InterfaceType`. Used as the metaclass for `ExportedGObject`.
+ """
+ def __init__(cls, name, bases, dct):
+ gobject.GObjectMeta.__init__(cls, name, bases, dct)
+ dbus.service.InterfaceType.__init__(cls, name, bases, dct)
+
+class ExportedGObject(gobject.GObject, dbus.service.Object):
+ """A GObject which is exported on the D-Bus.
+
+ Because GObject and `dbus.service.Object` both have custom metaclasses,
+ the naive approach using simple multiple inheritance won't work. This
+ class has `ExportedGObjectType` as its metaclass, which is sufficient
+ to make it work correctly.
+ """
+ __metaclass__ = ExportedGObjectType
+
+ def __init__(self, conn=None, object_path=None, **kwargs):
+ """Initialize an exported GObject.
+
+ :Parameters:
+ `conn` : dbus.connection.Connection
+ The D-Bus connection or bus
+ `object_path` : str
+ The object path at which to register this object.
+ :Keywords:
+ `bus_name` : dbus.service.BusName
+ A bus name to be held on behalf of this object, or None.
+ `gobject_properties` : dict
+ GObject properties to be set on the constructed object.
+
+ Any unrecognised keyword arguments will also be interpreted
+ as GObject properties.
+ """
+ bus_name = kwargs.pop('bus_name', None)
+ gobject_properties = kwargs.pop('gobject_properties', None)
+
+ if gobject_properties is not None:
+ kwargs.update(gobject_properties)
+ gobject.GObject.__init__(self, **kwargs)
+ dbus.service.Object.__init__(self, conn=conn,
+ object_path=object_path,
+ bus_name=bus_name)
diff --git a/dbus/lowlevel.py b/dbus/lowlevel.py
new file mode 100644
index 0000000..2754344
--- /dev/null
+++ b/dbus/lowlevel.py
@@ -0,0 +1,40 @@
+# Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""Low-level interface to D-Bus."""
+
+__all__ = ('PendingCall', 'Message', 'MethodCallMessage',
+ 'MethodReturnMessage', 'ErrorMessage', 'SignalMessage',
+ 'HANDLER_RESULT_HANDLED', 'HANDLER_RESULT_NOT_YET_HANDLED',
+ 'MESSAGE_TYPE_INVALID', 'MESSAGE_TYPE_METHOD_CALL',
+ 'MESSAGE_TYPE_METHOD_RETURN', 'MESSAGE_TYPE_ERROR',
+ 'MESSAGE_TYPE_SIGNAL')
+
+from _dbus_bindings import PendingCall, Message, MethodCallMessage, \
+ MethodReturnMessage, ErrorMessage, SignalMessage, \
+ HANDLER_RESULT_HANDLED, \
+ HANDLER_RESULT_NOT_YET_HANDLED, \
+ MESSAGE_TYPE_INVALID, \
+ MESSAGE_TYPE_METHOD_CALL, \
+ MESSAGE_TYPE_METHOD_RETURN, \
+ MESSAGE_TYPE_ERROR, \
+ MESSAGE_TYPE_SIGNAL
diff --git a/dbus/mainloop/__init__.py b/dbus/mainloop/__init__.py
new file mode 100644
index 0000000..dfaeefb
--- /dev/null
+++ b/dbus/mainloop/__init__.py
@@ -0,0 +1,62 @@
+# Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""Base definitions, etc. for main loop integration.
+
+"""
+
+import _dbus_bindings
+
+NativeMainLoop = _dbus_bindings.NativeMainLoop
+
+NULL_MAIN_LOOP = _dbus_bindings.NULL_MAIN_LOOP
+"""A null mainloop which doesn't actually do anything.
+
+For advanced users who want to dispatch events by hand. This is almost
+certainly a bad idea - if in doubt, use the GLib main loop found in
+`dbus.mainloop.glib`.
+"""
+
+WATCH_READABLE = _dbus_bindings.WATCH_READABLE
+"""Represents a file descriptor becoming readable.
+Used to implement file descriptor watches."""
+
+WATCH_WRITABLE = _dbus_bindings.WATCH_WRITABLE
+"""Represents a file descriptor becoming readable.
+Used to implement file descriptor watches."""
+
+WATCH_HANGUP = _dbus_bindings.WATCH_HANGUP
+"""Represents a file descriptor reaching end-of-file.
+Used to implement file descriptor watches."""
+
+WATCH_ERROR = _dbus_bindings.WATCH_ERROR
+"""Represents an error condition on a file descriptor.
+Used to implement file descriptor watches."""
+
+__all__ = (
+ # Imported into this module
+ 'NativeMainLoop', 'WATCH_READABLE', 'WATCH_WRITABLE',
+ 'WATCH_HANGUP', 'WATCH_ERROR', 'NULL_MAIN_LOOP',
+
+ # Submodules
+ 'glib'
+ )
diff --git a/dbus/mainloop/glib.py b/dbus/mainloop/glib.py
new file mode 100644
index 0000000..39fbb1b
--- /dev/null
+++ b/dbus/mainloop/glib.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2004 Anders Carlsson
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""GLib main loop integration using libdbus-glib."""
+
+__all__ = ('DBusGMainLoop', 'threads_init')
+
+from _dbus_glib_bindings import DBusGMainLoop, gthreads_init
+
+_dbus_gthreads_initialized = False
+def threads_init():
+ """Initialize threads in dbus-glib, if this has not already been done.
+
+ This must be called before creating a second thread in a program that
+ uses this module.
+ """
+ global _dbus_gthreads_initialized
+ if not _dbus_gthreads_initialized:
+ gthreads_init()
+ _dbus_gthreads_initialized = True
diff --git a/dbus/proxies.py b/dbus/proxies.py
new file mode 100644
index 0000000..cbb5d53
--- /dev/null
+++ b/dbus/proxies.py
@@ -0,0 +1,557 @@
+# Copyright (C) 2003-2007 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import sys
+import logging
+
+try:
+ from threading import RLock
+except ImportError:
+ from dummy_threading import RLock
+
+import _dbus_bindings
+from dbus._expat_introspect_parser import process_introspection_data
+from dbus.exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException, DBusException
+
+__docformat__ = 'restructuredtext'
+
+
+_logger = logging.getLogger('dbus.proxies')
+
+from _dbus_bindings import LOCAL_PATH, \
+ BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
+ INTROSPECTABLE_IFACE
+
+
+class _DeferredMethod:
+ """A proxy method which will only get called once we have its
+ introspection reply.
+ """
+ def __init__(self, proxy_method, append, block):
+ self._proxy_method = proxy_method
+ # the test suite relies on the existence of this property
+ self._method_name = proxy_method._method_name
+ self._append = append
+ self._block = block
+
+ def __call__(self, *args, **keywords):
+ if (keywords.has_key('reply_handler') or
+ keywords.get('ignore_reply', False)):
+ # defer the async call til introspection finishes
+ self._append(self._proxy_method, args, keywords)
+ return None
+ else:
+ # we're being synchronous, so block
+ self._block()
+ return self._proxy_method(*args, **keywords)
+
+ def call_async(self, *args, **keywords):
+ self._append(self._proxy_method, args, keywords)
+
+
+class _ProxyMethod:
+ """A proxy method.
+
+ Typically a member of a ProxyObject. Calls to the
+ method produce messages that travel over the Bus and are routed
+ to a specific named Service.
+ """
+ def __init__(self, proxy, connection, bus_name, object_path, method_name,
+ iface):
+ if object_path == LOCAL_PATH:
+ raise DBusException('Methods may not be called on the reserved '
+ 'path %s' % LOCAL_PATH)
+
+ # trust that the proxy, and the properties it had, are OK
+ self._proxy = proxy
+ self._connection = connection
+ self._named_service = bus_name
+ self._object_path = object_path
+ # fail early if the method name is bad
+ _dbus_bindings.validate_member_name(method_name)
+ # the test suite relies on the existence of this property
+ self._method_name = method_name
+ # fail early if the interface name is bad
+ if iface is not None:
+ _dbus_bindings.validate_interface_name(iface)
+ self._dbus_interface = iface
+
+ def __call__(self, *args, **keywords):
+ reply_handler = keywords.pop('reply_handler', None)
+ error_handler = keywords.pop('error_handler', None)
+ ignore_reply = keywords.pop('ignore_reply', False)
+
+ if reply_handler is not None or error_handler is not None:
+ if reply_handler is None:
+ raise MissingReplyHandlerException()
+ elif error_handler is None:
+ raise MissingErrorHandlerException()
+ elif ignore_reply:
+ raise TypeError('ignore_reply and reply_handler cannot be '
+ 'used together')
+
+ dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
+
+ if dbus_interface is None:
+ key = self._method_name
+ else:
+ key = dbus_interface + '.' + self._method_name
+ introspect_sig = self._proxy._introspect_method_map.get(key, None)
+
+ if ignore_reply or reply_handler is not None:
+ self._connection.call_async(self._named_service,
+ self._object_path,
+ dbus_interface,
+ self._method_name,
+ introspect_sig,
+ args,
+ reply_handler,
+ error_handler,
+ **keywords)
+ else:
+ return self._connection.call_blocking(self._named_service,
+ self._object_path,
+ dbus_interface,
+ self._method_name,
+ introspect_sig,
+ args,
+ **keywords)
+
+ def call_async(self, *args, **keywords):
+ reply_handler = keywords.pop('reply_handler', None)
+ error_handler = keywords.pop('error_handler', None)
+
+ dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
+
+ if dbus_interface:
+ key = dbus_interface + '.' + self._method_name
+ else:
+ key = self._method_name
+ introspect_sig = self._proxy._introspect_method_map.get(key, None)
+
+ self._connection.call_async(self._named_service,
+ self._object_path,
+ dbus_interface,
+ self._method_name,
+ introspect_sig,
+ args,
+ reply_handler,
+ error_handler,
+ **keywords)
+
+
+class ProxyObject(object):
+ """A proxy to the remote Object.
+
+ A ProxyObject is provided by the Bus. ProxyObjects
+ have member functions, and can be called like normal Python objects.
+ """
+ ProxyMethodClass = _ProxyMethod
+ DeferredMethodClass = _DeferredMethod
+
+ INTROSPECT_STATE_DONT_INTROSPECT = 0
+ INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
+ INTROSPECT_STATE_INTROSPECT_DONE = 2
+
+ def __init__(self, conn=None, bus_name=None, object_path=None,
+ introspect=True, follow_name_owner_changes=False, **kwargs):
+ """Initialize the proxy object.
+
+ :Parameters:
+ `conn` : `dbus.connection.Connection`
+ The bus or connection on which to find this object.
+ The keyword argument `bus` is a deprecated alias for this.
+ `bus_name` : str
+ A bus name for the application owning the object, to be used
+ as the destination for method calls and the sender for
+ signal matches. The keyword argument ``named_service`` is a
+ deprecated alias for this.
+ `object_path` : str
+ The object path at which the application exports the object
+ `introspect` : bool
+ If true (default), attempt to introspect the remote
+ object to find out supported methods and their signatures
+ `follow_name_owner_changes` : bool
+ If true (default is false) and the `bus_name` is a
+ well-known name, follow ownership changes for that name
+ """
+ bus = kwargs.pop('bus', None)
+ if bus is not None:
+ if conn is not None:
+ raise TypeError('conn and bus cannot both be specified')
+ conn = bus
+ from warnings import warn
+ warn('Passing the bus parameter to ProxyObject by name is '
+ 'deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+ named_service = kwargs.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both be '
+ 'specified')
+ bus_name = named_service
+ from warnings import warn
+ warn('Passing the named_service parameter to ProxyObject by name '
+ 'is deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+ if kwargs:
+ raise TypeError('ProxyObject.__init__ does not take these '
+ 'keyword arguments: %s'
+ % ', '.join(kwargs.iterkeys()))
+
+ if follow_name_owner_changes:
+ # we don't get the signals unless the Bus has a main loop
+ # XXX: using Bus internals
+ conn._require_main_loop()
+
+ self._bus = conn
+
+ if bus_name is not None:
+ _dbus_bindings.validate_bus_name(bus_name)
+ # the attribute is still called _named_service for the moment,
+ # for the benefit of telepathy-python
+ self._named_service = self._requested_bus_name = bus_name
+
+ _dbus_bindings.validate_object_path(object_path)
+ self.__dbus_object_path__ = object_path
+
+ if not follow_name_owner_changes:
+ self._named_service = conn.activate_name_owner(bus_name)
+
+ #PendingCall object for Introspect call
+ self._pending_introspect = None
+ #queue of async calls waiting on the Introspect to return
+ self._pending_introspect_queue = []
+ #dictionary mapping method names to their input signatures
+ self._introspect_method_map = {}
+
+ # must be a recursive lock because block() is called while locked,
+ # and calls the callback which re-takes the lock
+ self._introspect_lock = RLock()
+
+ if not introspect or self.__dbus_object_path__ == LOCAL_PATH:
+ self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
+ else:
+ self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
+
+ self._pending_introspect = self._Introspect()
+
+ bus_name = property(lambda self: self._named_service, None, None,
+ """The bus name to which this proxy is bound. (Read-only,
+ may change.)
+
+ If the proxy was instantiated using a unique name, this property
+ is that unique name.
+
+ If the proxy was instantiated with a well-known name and with
+ ``follow_name_owner_changes`` set false (the default), this
+ property is the unique name of the connection that owned that
+ well-known name when the proxy was instantiated, which might
+ not actually own the requested well-known name any more.
+
+ If the proxy was instantiated with a well-known name and with
+ ``follow_name_owner_changes`` set true, this property is that
+ well-known name.
+ """)
+
+ requested_bus_name = property(lambda self: self._requested_bus_name,
+ None, None,
+ """The bus name which was requested when this proxy was
+ instantiated.
+ """)
+
+ object_path = property(lambda self: self.__dbus_object_path__,
+ None, None,
+ """The object-path of this proxy.""")
+
+ # XXX: We don't currently support this because it's the signal receiver
+ # that's responsible for tracking name owner changes, but it
+ # seems a natural thing to add in future.
+ #unique_bus_name = property(lambda self: something, None, None,
+ # """The unique name of the connection to which this proxy is
+ # currently bound. (Read-only, may change.)
+ # """)
+
+ def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
+ """Arrange for the given function to be called when the given signal
+ is received.
+
+ :Parameters:
+ `signal_name` : str
+ The name of the signal
+ `handler_function` : callable
+ A function to be called when the signal is emitted by
+ the remote object. Its positional arguments will be the
+ arguments of the signal; optionally, it may be given
+ keyword arguments as described below.
+ `dbus_interface` : str
+ Optional interface with which to qualify the signal name.
+ If None (the default) the handler will be called whenever a
+ signal of the given member name is received, whatever
+ its interface.
+ :Keywords:
+ `utf8_strings` : bool
+ If True, the handler function will receive any string
+ arguments as dbus.UTF8String objects (a subclass of str
+ guaranteed to be UTF-8). If False (default) it will receive
+ any string arguments as dbus.String objects (a subclass of
+ unicode).
+ `byte_arrays` : bool
+ If True, the handler function will receive any byte-array
+ arguments as dbus.ByteArray objects (a subclass of str).
+ If False (default) it will receive any byte-array
+ arguments as a dbus.Array of dbus.Byte (subclasses of:
+ a list of ints).
+ `sender_keyword` : str
+ If not None (the default), the handler function will receive
+ the unique name of the sending endpoint as a keyword
+ argument with this name
+ `destination_keyword` : str
+ If not None (the default), the handler function will receive
+ the bus name of the destination (or None if the signal is a
+ broadcast, as is usual) as a keyword argument with this name.
+ `interface_keyword` : str
+ If not None (the default), the handler function will receive
+ the signal interface as a keyword argument with this name.
+ `member_keyword` : str
+ If not None (the default), the handler function will receive
+ the signal name as a keyword argument with this name.
+ `path_keyword` : str
+ If not None (the default), the handler function will receive
+ the object-path of the sending object as a keyword argument
+ with this name
+ `message_keyword` : str
+ If not None (the default), the handler function will receive
+ the `dbus.lowlevel.SignalMessage` as a keyword argument with
+ this name.
+ `arg...` : unicode or UTF-8 str
+ If there are additional keyword parameters of the form
+ ``arg``\ *n*, match only signals where the *n*\ th argument
+ is the value given for that keyword parameter. As of this time
+ only string arguments can be matched (in particular,
+ object paths and signatures can't).
+ """
+ return \
+ self._bus.add_signal_receiver(handler_function,
+ signal_name=signal_name,
+ dbus_interface=dbus_interface,
+ bus_name=self._named_service,
+ path=self.__dbus_object_path__,
+ **keywords)
+
+ def _Introspect(self):
+ return self._bus.call_async(self._named_service,
+ self.__dbus_object_path__,
+ INTROSPECTABLE_IFACE, 'Introspect', '', (),
+ self._introspect_reply_handler,
+ self._introspect_error_handler,
+ utf8_strings=True,
+ require_main_loop=False)
+
+ def _introspect_execute_queue(self):
+ # FIXME: potential to flood the bus
+ # We should make sure mainloops all have idle handlers
+ # and do one message per idle
+ for (proxy_method, args, keywords) in self._pending_introspect_queue:
+ proxy_method(*args, **keywords)
+
+ def _introspect_reply_handler(self, data):
+ self._introspect_lock.acquire()
+ try:
+ try:
+ self._introspect_method_map = process_introspection_data(data)
+ except IntrospectionParserException, e:
+ self._introspect_error_handler(e)
+ return
+
+ self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
+ self._pending_introspect = None
+ self._introspect_execute_queue()
+ finally:
+ self._introspect_lock.release()
+
+ def _introspect_error_handler(self, error):
+ logging.basicConfig()
+ _logger.error("Introspect error on %s:%s: %s.%s: %s",
+ self._named_service, self.__dbus_object_path__,
+ error.__class__.__module__, error.__class__.__name__,
+ error)
+ self._introspect_lock.acquire()
+ try:
+ _logger.debug('Executing introspect queue due to error')
+ self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
+ self._pending_introspect = None
+ self._introspect_execute_queue()
+ finally:
+ self._introspect_lock.release()
+
+ def _introspect_block(self):
+ self._introspect_lock.acquire()
+ try:
+ if self._pending_introspect is not None:
+ self._pending_introspect.block()
+ # else someone still has a _DeferredMethod from before we
+ # finished introspection: no need to do anything special any more
+ finally:
+ self._introspect_lock.release()
+
+ def _introspect_add_to_queue(self, callback, args, kwargs):
+ self._introspect_lock.acquire()
+ try:
+ if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
+ self._pending_introspect_queue.append((callback, args, kwargs))
+ else:
+ # someone still has a _DeferredMethod from before we
+ # finished introspection
+ callback(*args, **kwargs)
+ finally:
+ self._introspect_lock.release()
+
+ def __getattr__(self, member):
+ if member.startswith('__') and member.endswith('__'):
+ raise AttributeError(member)
+ else:
+ return self.get_dbus_method(member)
+
+ def get_dbus_method(self, member, dbus_interface=None):
+ """Return a proxy method representing the given D-Bus method. The
+ returned proxy method can be called in the usual way. For instance, ::
+
+ proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
+
+ is equivalent to::
+
+ proxy.Foo(123, dbus_interface='com.example.Bar')
+
+ or even::
+
+ getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
+
+ However, using `get_dbus_method` is the only way to call D-Bus
+ methods with certain awkward names - if the author of a service
+ implements a method called ``connect_to_signal`` or even
+ ``__getattr__``, you'll need to use `get_dbus_method` to call them.
+
+ For services which follow the D-Bus convention of CamelCaseMethodNames
+ this won't be a problem.
+ """
+
+ ret = self.ProxyMethodClass(self, self._bus,
+ self._named_service,
+ self.__dbus_object_path__, member,
+ dbus_interface)
+
+ # this can be done without taking the lock - the worst that can
+ # happen is that we accidentally return a _DeferredMethod just after
+ # finishing introspection, in which case _introspect_add_to_queue and
+ # _introspect_block will do the right thing anyway
+ if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
+ ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue,
+ self._introspect_block)
+
+ return ret
+
+ def __repr__(self):
+ return '<ProxyObject wrapping %s %s %s at %#x>'%(
+ self._bus, self._named_service, self.__dbus_object_path__, id(self))
+ __str__ = __repr__
+
+
+class Interface(object):
+ """An interface into a remote object.
+
+ An Interface can be used to wrap ProxyObjects
+ so that calls can be routed to their correct
+ D-Bus interface.
+ """
+
+ def __init__(self, object, dbus_interface):
+ """Construct a proxy for the given interface on the given object.
+
+ :Parameters:
+ `object` : `dbus.proxies.ProxyObject` or `dbus.Interface`
+ The remote object or another of its interfaces
+ `dbus_interface` : str
+ An interface the `object` implements
+ """
+ if isinstance(object, Interface):
+ self._obj = object.proxy_object
+ else:
+ self._obj = object
+ self._dbus_interface = dbus_interface
+
+ object_path = property (lambda self: self._obj.object_path, None, None,
+ "The D-Bus object path of the underlying object")
+ __dbus_object_path__ = object_path
+ bus_name = property (lambda self: self._obj.bus_name, None, None,
+ "The bus name to which the underlying proxy object "
+ "is bound")
+ requested_bus_name = property (lambda self: self._obj.requested_bus_name,
+ None, None,
+ "The bus name which was requested when the "
+ "underlying object was created")
+ proxy_object = property (lambda self: self._obj, None, None,
+ """The underlying proxy object""")
+ dbus_interface = property (lambda self: self._dbus_interface, None, None,
+ """The D-Bus interface represented""")
+
+ def connect_to_signal(self, signal_name, handler_function,
+ dbus_interface=None, **keywords):
+ """Arrange for a function to be called when the given signal is
+ emitted.
+
+ The parameters and keyword arguments are the same as for
+ `dbus.proxies.ProxyObject.connect_to_signal`, except that if
+ `dbus_interface` is None (the default), the D-Bus interface that
+ was passed to the `Interface` constructor is used.
+ """
+ if not dbus_interface:
+ dbus_interface = self._dbus_interface
+
+ return self._obj.connect_to_signal(signal_name, handler_function,
+ dbus_interface, **keywords)
+
+ def __getattr__(self, member):
+ if member.startswith('__') and member.endswith('__'):
+ raise AttributeError(member)
+ else:
+ return self._obj.get_dbus_method(member, self._dbus_interface)
+
+ def get_dbus_method(self, member, dbus_interface=None):
+ """Return a proxy method representing the given D-Bus method.
+
+ This is the same as `dbus.proxies.ProxyObject.get_dbus_method`
+ except that if `dbus_interface` is None (the default),
+ the D-Bus interface that was passed to the `Interface` constructor
+ is used.
+ """
+ if dbus_interface is None:
+ dbus_interface = self._dbus_interface
+ return self._obj.get_dbus_method(member, dbus_interface)
+
+ def __repr__(self):
+ return '<Interface %r implementing %r at %#x>'%(
+ self._obj, self._dbus_interface, id(self))
+ __str__ = __repr__
diff --git a/dbus/server.py b/dbus/server.py
new file mode 100644
index 0000000..1988101
--- /dev/null
+++ b/dbus/server.py
@@ -0,0 +1,117 @@
+# Copyright (C) 2008 Openismus GmbH <http://openismus.com/>
+# Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('Server', )
+__docformat__ = 'reStructuredText'
+
+from _dbus_bindings import _Server
+from dbus.connection import Connection
+
+class Server(_Server):
+ """An opaque object representing a server that listens for connections from
+ other applications.
+
+ This class is not useful to instantiate directly: you must subclass it and
+ either extend the method connection_added, or append to the
+ list on_connection_added.
+
+ :Since: 0.83
+ """
+
+ def __new__(cls, address, connection_class=Connection,
+ mainloop=None, auth_mechanisms=None):
+ """Construct a new Server.
+
+ :Parameters:
+ `address` : str
+ Listen on this address.
+ `connection_class` : type
+ When new connections come in, instantiate this subclass
+ of dbus.connection.Connection to represent them.
+ The default is Connection.
+ `mainloop` : dbus.mainloop.NativeMainLoop or None
+ The main loop with which to associate the new connections.
+ `auth_mechanisms` : sequence of str
+ Authentication mechanisms to allow. The default is to allow
+ any authentication mechanism supported by ``libdbus``.
+ """
+ return super(Server, cls).__new__(cls, address, connection_class,
+ mainloop, auth_mechanisms)
+
+ def __init__(self, *args, **kwargs):
+
+ self.__connections = {}
+
+ self.on_connection_added = []
+ """A list of callbacks to invoke when a connection is added.
+ They receive two arguments: this Server and the new Connection."""
+
+ self.on_connection_removed = []
+ """A list of callbacks to invoke when a connection becomes
+ disconnected. They receive two arguments: this Server and the removed
+ Connection."""
+
+ # This method name is hard-coded in _dbus_bindings._Server.
+ # This is not public API.
+ def _on_new_connection(self, conn):
+ conn.call_on_disconnection(self.connection_removed)
+ self.connection_added(conn)
+
+ def connection_added(self, conn):
+ """Respond to the creation of a new Connection.
+
+ This base-class implementation just invokes the callbacks in
+ the on_connection_added attribute.
+
+ :Parameters:
+ `conn` : dbus.connection.Connection
+ A D-Bus connection which has just been added.
+
+ The type of this parameter is whatever was passed
+ to the Server constructor as the ``connection_class``.
+ """
+ if self.on_connection_added:
+ for cb in self.on_connection_added:
+ cb(conn)
+
+ def connection_removed(self, conn):
+ """Respond to the disconnection of a Connection.
+
+ This base-class implementation just invokes the callbacks in
+ the on_connection_removed attribute.
+
+ :Parameters:
+ `conn` : dbus.connection.Connection
+ A D-Bus connection which has just become disconnected.
+
+ The type of this parameter is whatever was passed
+ to the Server constructor as the ``connection_class``.
+ """
+ if self.on_connection_removed:
+ for cb in self.on_connection_removed:
+ cb(conn)
+
+ address = property(_Server.get_address)
+ id = property(_Server.get_id)
+ is_connected = property(_Server.get_is_connected)
+
diff --git a/dbus/service.py b/dbus/service.py
new file mode 100644
index 0000000..b92d840
--- /dev/null
+++ b/dbus/service.py
@@ -0,0 +1,829 @@
+# Copyright (C) 2003-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005-2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('BusName', 'Object', 'method', 'signal')
+__docformat__ = 'restructuredtext'
+
+import sys
+import logging
+import operator
+import traceback
+try:
+ import thread
+except ImportError:
+ import dummy_thread as thread
+
+import _dbus_bindings
+from dbus import SessionBus, Signature, Struct, validate_bus_name, \
+ validate_object_path, INTROSPECTABLE_IFACE, ObjectPath
+from dbus.decorators import method, signal
+from dbus.exceptions import DBusException, \
+ NameExistsException, \
+ UnknownMethodException
+from dbus.lowlevel import ErrorMessage, MethodReturnMessage, MethodCallMessage
+from dbus.proxies import LOCAL_PATH
+
+
+_logger = logging.getLogger('dbus.service')
+
+
+class _VariantSignature(object):
+ """A fake method signature which, when iterated, yields an endless stream
+ of 'v' characters representing variants (handy with zip()).
+
+ It has no string representation.
+ """
+ def __iter__(self):
+ """Return self."""
+ return self
+
+ def next(self):
+ """Return 'v' whenever called."""
+ return 'v'
+
+class BusName(object):
+ """A base class for exporting your own Named Services across the Bus.
+
+ When instantiated, objects of this class attempt to claim the given
+ well-known name on the given bus for the current process. The name is
+ released when the BusName object becomes unreferenced.
+
+ If a well-known name is requested multiple times, multiple references
+ to the same BusName object will be returned.
+
+ Caveats
+ -------
+ - Assumes that named services are only ever requested using this class -
+ if you request names from the bus directly, confusion may occur.
+ - Does not handle queueing.
+ """
+ def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
+ """Constructor, which may either return an existing cached object
+ or a new object.
+
+ :Parameters:
+ `name` : str
+ The well-known name to be advertised
+ `bus` : dbus.Bus
+ A Bus on which this service will be advertised.
+
+ Omitting this parameter or setting it to None has been
+ deprecated since version 0.82.1. For backwards compatibility,
+ if this is done, the global shared connection to the session
+ bus will be used.
+
+ `allow_replacement` : bool
+ If True, other processes trying to claim the same well-known
+ name will take precedence over this one.
+ `replace_existing` : bool
+ If True, this process can take over the well-known name
+ from other processes already holding it.
+ `do_not_queue` : bool
+ If True, this service will not be placed in the queue of
+ services waiting for the requested name if another service
+ already holds it.
+ """
+ validate_bus_name(name, allow_well_known=True, allow_unique=False)
+
+ # if necessary, get default bus (deprecated)
+ if bus is None:
+ import warnings
+ warnings.warn('Omitting the "bus" parameter to '
+ 'dbus.service.BusName.__init__ is deprecated',
+ DeprecationWarning, stacklevel=2)
+ bus = SessionBus()
+
+ # see if this name is already defined, return it if so
+ # FIXME: accessing internals of Bus
+ if name in bus._bus_names:
+ return bus._bus_names[name]
+
+ # otherwise register the name
+ name_flags = (
+ (allow_replacement and _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT or 0) |
+ (replace_existing and _dbus_bindings.NAME_FLAG_REPLACE_EXISTING or 0) |
+ (do_not_queue and _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE or 0))
+
+ retval = bus.request_name(name, name_flags)
+
+ # TODO: more intelligent tracking of bus name states?
+ if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
+ pass
+ elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
+ # queueing can happen by default, maybe we should
+ # track this better or let the user know if they're
+ # queued or not?
+ pass
+ elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
+ raise NameExistsException(name)
+ elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
+ # if this is a shared bus which is being used by someone
+ # else in this process, this can happen legitimately
+ pass
+ else:
+ raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
+
+ # and create the object
+ bus_name = object.__new__(cls)
+ bus_name._bus = bus
+ bus_name._name = name
+
+ # cache instance (weak ref only)
+ # FIXME: accessing Bus internals again
+ bus._bus_names[name] = bus_name
+
+ return bus_name
+
+ # do nothing because this is called whether or not the bus name
+ # object was retrieved from the cache or created new
+ def __init__(self, *args, **keywords):
+ pass
+
+ # we can delete the low-level name here because these objects
+ # are guaranteed to exist only once for each bus name
+ def __del__(self):
+ self._bus.release_name(self._name)
+ pass
+
+ def get_bus(self):
+ """Get the Bus this Service is on"""
+ return self._bus
+
+ def get_name(self):
+ """Get the name of this service"""
+ return self._name
+
+ def __repr__(self):
+ return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
+ __str__ = __repr__
+
+
+def _method_lookup(self, method_name, dbus_interface):
+ """Walks the Python MRO of the given class to find the method to invoke.
+
+ Returns two methods, the one to call, and the one it inherits from which
+ defines its D-Bus interface name, signature, and attributes.
+ """
+ parent_method = None
+ candidate_class = None
+ successful = False
+
+ # split up the cases when we do and don't have an interface because the
+ # latter is much simpler
+ if dbus_interface:
+ # search through the class hierarchy in python MRO order
+ for cls in self.__class__.__mro__:
+ # if we haven't got a candidate class yet, and we find a class with a
+ # suitably named member, save this as a candidate class
+ if (not candidate_class and method_name in cls.__dict__):
+ if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
+ and "_dbus_interface" in cls.__dict__[method_name].__dict__):
+ # however if it is annotated for a different interface
+ # than we are looking for, it cannot be a candidate
+ if cls.__dict__[method_name]._dbus_interface == dbus_interface:
+ candidate_class = cls
+ parent_method = cls.__dict__[method_name]
+ successful = True
+ break
+ else:
+ pass
+ else:
+ candidate_class = cls
+
+ # if we have a candidate class, carry on checking this and all
+ # superclasses for a method annoated as a dbus method
+ # on the correct interface
+ if (candidate_class and method_name in cls.__dict__
+ and "_dbus_is_method" in cls.__dict__[method_name].__dict__
+ and "_dbus_interface" in cls.__dict__[method_name].__dict__
+ and cls.__dict__[method_name]._dbus_interface == dbus_interface):
+ # the candidate class has a dbus method on the correct interface,
+ # or overrides a method that is, success!
+ parent_method = cls.__dict__[method_name]
+ successful = True
+ break
+
+ else:
+ # simpler version of above
+ for cls in self.__class__.__mro__:
+ if (not candidate_class and method_name in cls.__dict__):
+ candidate_class = cls
+
+ if (candidate_class and method_name in cls.__dict__
+ and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
+ parent_method = cls.__dict__[method_name]
+ successful = True
+ break
+
+ if successful:
+ return (candidate_class.__dict__[method_name], parent_method)
+ else:
+ if dbus_interface:
+ raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
+ else:
+ raise UnknownMethodException('%s is not a valid method' % method_name)
+
+
+def _method_reply_return(connection, message, method_name, signature, *retval):
+ reply = MethodReturnMessage(message)
+ try:
+ reply.append(signature=signature, *retval)
+ except Exception, e:
+ logging.basicConfig()
+ if signature is None:
+ try:
+ signature = reply.guess_signature(retval) + ' (guessed)'
+ except Exception, e:
+ _logger.error('Unable to guess signature for arguments %r: '
+ '%s: %s', retval, e.__class__, e)
+ raise
+ _logger.error('Unable to append %r to message with signature %s: '
+ '%s: %s', retval, signature, e.__class__, e)
+ raise
+
+ connection.send_message(reply)
+
+
+def _method_reply_error(connection, message, exception):
+ name = getattr(exception, '_dbus_error_name', None)
+
+ if name is not None:
+ pass
+ elif getattr(exception, '__module__', '') in ('', '__main__'):
+ name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
+ else:
+ name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
+
+ et, ev, etb = sys.exc_info()
+ if isinstance(exception, DBusException) and not exception.include_traceback:
+ # We don't actually want the traceback anyway
+ contents = exception.get_dbus_message()
+ elif ev is exception:
+ # The exception was actually thrown, so we can get a traceback
+ contents = ''.join(traceback.format_exception(et, ev, etb))
+ else:
+ # We don't have any traceback for it, e.g.
+ # async_err_cb(MyException('Failed to badger the mushroom'))
+ # see also https://bugs.freedesktop.org/show_bug.cgi?id=12403
+ contents = ''.join(traceback.format_exception_only(exception.__class__,
+ exception))
+ reply = ErrorMessage(message, name, contents)
+
+ connection.send_message(reply)
+
+
+class InterfaceType(type):
+ def __init__(cls, name, bases, dct):
+ # these attributes are shared between all instances of the Interface
+ # object, so this has to be a dictionary that maps class names to
+ # the per-class introspection/interface data
+ class_table = getattr(cls, '_dbus_class_table', {})
+ cls._dbus_class_table = class_table
+ interface_table = class_table[cls.__module__ + '.' + name] = {}
+
+ # merge all the name -> method tables for all the interfaces
+ # implemented by our base classes into our own
+ for b in bases:
+ base_name = b.__module__ + '.' + b.__name__
+ if getattr(b, '_dbus_class_table', False):
+ for (interface, method_table) in class_table[base_name].iteritems():
+ our_method_table = interface_table.setdefault(interface, {})
+ our_method_table.update(method_table)
+
+ # add in all the name -> method entries for our own methods/signals
+ for func in dct.values():
+ if getattr(func, '_dbus_interface', False):
+ method_table = interface_table.setdefault(func._dbus_interface, {})
+ method_table[func.__name__] = func
+
+ super(InterfaceType, cls).__init__(name, bases, dct)
+
+ # methods are different to signals, so we have two functions... :)
+ def _reflect_on_method(cls, func):
+ args = func._dbus_args
+
+ if func._dbus_in_signature:
+ # convert signature into a tuple so length refers to number of
+ # types, not number of characters. the length is checked by
+ # the decorator to make sure it matches the length of args.
+ in_sig = tuple(Signature(func._dbus_in_signature))
+ else:
+ # magic iterator which returns as many v's as we need
+ in_sig = _VariantSignature()
+
+ if func._dbus_out_signature:
+ out_sig = Signature(func._dbus_out_signature)
+ else:
+ # its tempting to default to Signature('v'), but
+ # for methods that return nothing, providing incorrect
+ # introspection data is worse than providing none at all
+ out_sig = []
+
+ reflection_data = ' <method name="%s">\n' % (func.__name__)
+ for pair in zip(in_sig, args):
+ reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
+ for type in out_sig:
+ reflection_data += ' <arg direction="out" type="%s" />\n' % type
+ reflection_data += ' </method>\n'
+
+ return reflection_data
+
+ def _reflect_on_signal(cls, func):
+ args = func._dbus_args
+
+ if func._dbus_signature:
+ # convert signature into a tuple so length refers to number of
+ # types, not number of characters
+ sig = tuple(Signature(func._dbus_signature))
+ else:
+ # magic iterator which returns as many v's as we need
+ sig = _VariantSignature()
+
+ reflection_data = ' <signal name="%s">\n' % (func.__name__)
+ for pair in zip(sig, args):
+ reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
+ reflection_data = reflection_data + ' </signal>\n'
+
+ return reflection_data
+
+class Interface(object):
+ __metaclass__ = InterfaceType
+
+#: A unique object used as the value of Object._object_path and
+#: Object._connection if it's actually in more than one place
+_MANY = object()
+
+class Object(Interface):
+ r"""A base class for exporting your own Objects across the Bus.
+
+ Just inherit from Object and mark exported methods with the
+ @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
+
+ Example::
+
+ class Example(dbus.service.object):
+ def __init__(self, object_path):
+ dbus.service.Object.__init__(self, dbus.SessionBus(), path)
+ self._last_input = None
+
+ @dbus.service.method(interface='com.example.Sample',
+ in_signature='v', out_signature='s')
+ def StringifyVariant(self, var):
+ self.LastInputChanged(var) # emits the signal
+ return str(var)
+
+ @dbus.service.signal(interface='com.example.Sample',
+ signature='v')
+ def LastInputChanged(self, var):
+ # run just before the signal is actually emitted
+ # just put "pass" if nothing should happen
+ self._last_input = var
+
+ @dbus.service.method(interface='com.example.Sample',
+ in_signature='', out_signature='v')
+ def GetLastInput(self):
+ return self._last_input
+ """
+
+ #: If True, this object can be made available at more than one object path.
+ #: If True but `SUPPORTS_MULTIPLE_CONNECTIONS` is False, the object may
+ #: handle more than one object path, but they must all be on the same
+ #: connection.
+ SUPPORTS_MULTIPLE_OBJECT_PATHS = False
+
+ #: If True, this object can be made available on more than one connection.
+ #: If True but `SUPPORTS_MULTIPLE_OBJECT_PATHS` is False, the object must
+ #: have the same object path on all its connections.
+ SUPPORTS_MULTIPLE_CONNECTIONS = False
+
+ def __init__(self, conn=None, object_path=None, bus_name=None):
+ """Constructor. Either conn or bus_name is required; object_path
+ is also required.
+
+ :Parameters:
+ `conn` : dbus.connection.Connection or None
+ The connection on which to export this object.
+
+ If None, use the Bus associated with the given ``bus_name``.
+ If there is no ``bus_name`` either, the object is not
+ initially available on any Connection.
+
+ For backwards compatibility, if an instance of
+ dbus.service.BusName is passed as the first parameter,
+ this is equivalent to passing its associated Bus as
+ ``conn``, and passing the BusName itself as ``bus_name``.
+
+ `object_path` : str or None
+ A D-Bus object path at which to make this Object available
+ immediately. If this is not None, a `conn` or `bus_name` must
+ also be provided.
+
+ `bus_name` : dbus.service.BusName or None
+ Represents a well-known name claimed by this process. A
+ reference to the BusName object will be held by this
+ Object, preventing the name from being released during this
+ Object's lifetime (unless it's released manually).
+ """
+ if object_path is not None:
+ validate_object_path(object_path)
+
+ if isinstance(conn, BusName):
+ # someone's using the old API; don't gratuitously break them
+ bus_name = conn
+ conn = bus_name.get_bus()
+ elif conn is None:
+ if bus_name is not None:
+ # someone's using the old API but naming arguments, probably
+ conn = bus_name.get_bus()
+
+ #: Either an object path, None or _MANY
+ self._object_path = None
+ #: Either a dbus.connection.Connection, None or _MANY
+ self._connection = None
+ #: A list of tuples (Connection, object path, False) where the False
+ #: is for future expansion (to support fallback paths)
+ self._locations = []
+ #: Lock protecting `_locations`, `_connection` and `_object_path`
+ self._locations_lock = thread.allocate_lock()
+
+ #: True if this is a fallback object handling a whole subtree.
+ self._fallback = False
+
+ self._name = bus_name
+
+ if conn is None and object_path is not None:
+ raise TypeError('If object_path is given, either conn or bus_name '
+ 'is required')
+ if conn is not None and object_path is not None:
+ self.add_to_connection(conn, object_path)
+
+ @property
+ def __dbus_object_path__(self):
+ """The object-path at which this object is available.
+ Access raises AttributeError if there is no object path, or more than
+ one object path.
+
+ Changed in 0.82.0: AttributeError can be raised.
+ """
+ if self._object_path is _MANY:
+ raise AttributeError('Object %r has more than one object path: '
+ 'use Object.locations instead' % self)
+ elif self._object_path is None:
+ raise AttributeError('Object %r has no object path yet' % self)
+ else:
+ return self._object_path
+
+ @property
+ def connection(self):
+ """The Connection on which this object is available.
+ Access raises AttributeError if there is no Connection, or more than
+ one Connection.
+
+ Changed in 0.82.0: AttributeError can be raised.
+ """
+ if self._connection is _MANY:
+ raise AttributeError('Object %r is on more than one Connection: '
+ 'use Object.locations instead' % self)
+ elif self._connection is None:
+ raise AttributeError('Object %r has no Connection yet' % self)
+ else:
+ return self._connection
+
+ @property
+ def locations(self):
+ """An iterable over tuples representing locations at which this
+ object is available.
+
+ Each tuple has at least two items, but may have more in future
+ versions of dbus-python, so do not rely on their exact length.
+ The first two items are the dbus.connection.Connection and the object
+ path.
+
+ :Since: 0.82.0
+ """
+ return iter(self._locations)
+
+ def add_to_connection(self, connection, path):
+ """Make this object accessible via the given D-Bus connection and
+ object path.
+
+ :Parameters:
+ `connection` : dbus.connection.Connection
+ Export the object on this connection. If the class attribute
+ SUPPORTS_MULTIPLE_CONNECTIONS is False (default), this object
+ can only be made available on one connection; if the class
+ attribute is set True by a subclass, the object can be made
+ available on more than one connection.
+
+ `path` : dbus.ObjectPath or other str
+ Place the object at this object path. If the class attribute
+ SUPPORTS_MULTIPLE_OBJECT_PATHS is False (default), this object
+ can only be made available at one object path; if the class
+ attribute is set True by a subclass, the object can be made
+ available with more than one object path.
+
+ :Raises ValueError: if the object's class attributes do not allow the
+ object to be exported in the desired way.
+ :Since: 0.82.0
+ """
+ if path == LOCAL_PATH:
+ raise ValueError('Objects may not be exported on the reserved '
+ 'path %s' % LOCAL_PATH)
+
+ self._locations_lock.acquire()
+ try:
+ if (self._connection is not None and
+ self._connection is not connection and
+ not self.SUPPORTS_MULTIPLE_CONNECTIONS):
+ raise ValueError('%r is already exported on '
+ 'connection %r' % (self, self._connection))
+
+ if (self._object_path is not None and
+ not self.SUPPORTS_MULTIPLE_OBJECT_PATHS and
+ self._object_path != path):
+ raise ValueError('%r is already exported at object '
+ 'path %s' % (self, self._object_path))
+
+ connection._register_object_path(path, self._message_cb,
+ self._unregister_cb,
+ self._fallback)
+
+ if self._connection is None:
+ self._connection = connection
+ elif self._connection is not connection:
+ self._connection = _MANY
+
+ if self._object_path is None:
+ self._object_path = path
+ elif self._object_path != path:
+ self._object_path = _MANY
+
+ self._locations.append((connection, path, self._fallback))
+ finally:
+ self._locations_lock.release()
+
+ def remove_from_connection(self, connection=None, path=None):
+ """Make this object inaccessible via the given D-Bus connection
+ and object path. If no connection or path is specified,
+ the object ceases to be accessible via any connection or path.
+
+ :Parameters:
+ `connection` : dbus.connection.Connection or None
+ Only remove the object from this Connection. If None,
+ remove from all Connections on which it's exported.
+ `path` : dbus.ObjectPath or other str, or None
+ Only remove the object from this object path. If None,
+ remove from all object paths.
+ :Raises LookupError:
+ if the object was not exported on the requested connection
+ or path, or (if both are None) was not exported at all.
+ :Since: 0.81.1
+ """
+ self._locations_lock.acquire()
+ try:
+ if self._object_path is None or self._connection is None:
+ raise LookupError('%r is not exported' % self)
+
+ if connection is not None or path is not None:
+ dropped = []
+ for location in self._locations:
+ if ((connection is None or location[0] is connection) and
+ (path is None or location[1] == path)):
+ dropped.append(location)
+ else:
+ dropped = self._locations
+ self._locations = []
+
+ if not dropped:
+ raise LookupError('%r is not exported at a location matching '
+ '(%r,%r)' % (self, connection, path))
+
+ for location in dropped:
+ try:
+ location[0]._unregister_object_path(location[1])
+ except LookupError:
+ pass
+ if self._locations:
+ try:
+ self._locations.remove(location)
+ except ValueError:
+ pass
+ finally:
+ self._locations_lock.release()
+
+ def _unregister_cb(self, connection):
+ # there's not really enough information to do anything useful here
+ _logger.info('Unregistering exported object %r from some path '
+ 'on %r', self, connection)
+
+ def _message_cb(self, connection, message):
+ if not isinstance(message, MethodCallMessage):
+ return
+
+ try:
+ # lookup candidate method and parent method
+ method_name = message.get_member()
+ interface_name = message.get_interface()
+ (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
+
+ # set up method call parameters
+ args = message.get_args_list(**parent_method._dbus_get_args_options)
+ keywords = {}
+
+ if parent_method._dbus_out_signature is not None:
+ signature = Signature(parent_method._dbus_out_signature)
+ else:
+ signature = None
+
+ # set up async callback functions
+ if parent_method._dbus_async_callbacks:
+ (return_callback, error_callback) = parent_method._dbus_async_callbacks
+ keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
+ keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
+
+ # include the sender etc. if desired
+ if parent_method._dbus_sender_keyword:
+ keywords[parent_method._dbus_sender_keyword] = message.get_sender()
+ if parent_method._dbus_path_keyword:
+ keywords[parent_method._dbus_path_keyword] = message.get_path()
+ if parent_method._dbus_rel_path_keyword:
+ path = message.get_path()
+ rel_path = path
+ for exp in self._locations:
+ # pathological case: if we're exported in two places,
+ # one of which is a subtree of the other, then pick the
+ # subtree by preference (i.e. minimize the length of
+ # rel_path)
+ if exp[0] is connection:
+ if path == exp[1]:
+ rel_path = '/'
+ break
+ if exp[1] == '/':
+ # we already have rel_path == path at the beginning
+ continue
+ if path.startswith(exp[1] + '/'):
+ # yes we're in this exported subtree
+ suffix = path[len(exp[1]):]
+ if len(suffix) < len(rel_path):
+ rel_path = suffix
+ rel_path = ObjectPath(rel_path)
+ keywords[parent_method._dbus_rel_path_keyword] = rel_path
+
+ if parent_method._dbus_destination_keyword:
+ keywords[parent_method._dbus_destination_keyword] = message.get_destination()
+ if parent_method._dbus_message_keyword:
+ keywords[parent_method._dbus_message_keyword] = message
+ if parent_method._dbus_connection_keyword:
+ keywords[parent_method._dbus_connection_keyword] = connection
+
+ # call method
+ retval = candidate_method(self, *args, **keywords)
+
+ # we're done - the method has got callback functions to reply with
+ if parent_method._dbus_async_callbacks:
+ return
+
+ # otherwise we send the return values in a reply. if we have a
+ # signature, use it to turn the return value into a tuple as
+ # appropriate
+ if signature is not None:
+ signature_tuple = tuple(signature)
+ # if we have zero or one return values we want make a tuple
+ # for the _method_reply_return function, otherwise we need
+ # to check we're passing it a sequence
+ if len(signature_tuple) == 0:
+ if retval == None:
+ retval = ()
+ else:
+ raise TypeError('%s has an empty output signature but did not return None' %
+ method_name)
+ elif len(signature_tuple) == 1:
+ retval = (retval,)
+ else:
+ if operator.isSequenceType(retval):
+ # multi-value signature, multi-value return... proceed unchanged
+ pass
+ else:
+ raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
+ (method_name, signature))
+
+ # no signature, so just turn the return into a tuple and send it as normal
+ else:
+ if retval is None:
+ retval = ()
+ elif (isinstance(retval, tuple)
+ and not isinstance(retval, Struct)):
+ # If the return is a tuple that is not a Struct, we use it
+ # as-is on the assumption that there are multiple return
+ # values - this is the usual Python idiom. (fd.o #10174)
+ pass
+ else:
+ retval = (retval,)
+
+ _method_reply_return(connection, message, method_name, signature, *retval)
+ except Exception, exception:
+ # send error reply
+ _method_reply_error(connection, message, exception)
+
+ @method(INTROSPECTABLE_IFACE, in_signature='', out_signature='s',
+ path_keyword='object_path', connection_keyword='connection')
+ def Introspect(self, object_path, connection):
+ """Return a string of XML encoding this object's supported interfaces,
+ methods and signals.
+ """
+ reflection_data = _dbus_bindings.DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ reflection_data += '<node name="%s">\n' % object_path
+
+ interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
+ for (name, funcs) in interfaces.iteritems():
+ reflection_data += ' <interface name="%s">\n' % (name)
+
+ for func in funcs.values():
+ if getattr(func, '_dbus_is_method', False):
+ reflection_data += self.__class__._reflect_on_method(func)
+ elif getattr(func, '_dbus_is_signal', False):
+ reflection_data += self.__class__._reflect_on_signal(func)
+
+ reflection_data += ' </interface>\n'
+
+ for name in connection.list_exported_child_objects(object_path):
+ reflection_data += ' <node name="%s"/>\n' % name
+
+ reflection_data += '</node>\n'
+
+ return reflection_data
+
+ def __repr__(self):
+ where = ''
+ if (self._object_path is not _MANY
+ and self._object_path is not None):
+ where = ' at %s' % self._object_path
+ return '<%s.%s%s at %#x>' % (self.__class__.__module__,
+ self.__class__.__name__, where,
+ id(self))
+ __str__ = __repr__
+
+class FallbackObject(Object):
+ """An object that implements an entire subtree of the object-path
+ tree.
+
+ :Since: 0.82.0
+ """
+
+ SUPPORTS_MULTIPLE_OBJECT_PATHS = True
+
+ def __init__(self, conn=None, object_path=None):
+ """Constructor.
+
+ Note that the superclass' ``bus_name`` __init__ argument is not
+ supported here.
+
+ :Parameters:
+ `conn` : dbus.connection.Connection or None
+ The connection on which to export this object. If this is not
+ None, an `object_path` must also be provided.
+
+ If None, the object is not initially available on any
+ Connection.
+
+ `object_path` : str or None
+ A D-Bus object path at which to make this Object available
+ immediately. If this is not None, a `conn` must also be
+ provided.
+
+ This object will implements all object-paths in the subtree
+ starting at this object-path, except where a more specific
+ object has been added.
+ """
+ super(FallbackObject, self).__init__()
+ self._fallback = True
+
+ if conn is None:
+ if object_path is not None:
+ raise TypeError('If object_path is given, conn is required')
+ elif object_path is None:
+ raise TypeError('If conn is given, object_path is required')
+ else:
+ self.add_to_connection(conn, object_path)
diff --git a/dbus/types.py b/dbus/types.py
new file mode 100644
index 0000000..cc4a678
--- /dev/null
+++ b/dbus/types.py
@@ -0,0 +1,9 @@
+__all__ = ('ObjectPath', 'ByteArray', 'Signature', 'Byte', 'Boolean',
+ 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64',
+ 'Double', 'String', 'Array', 'Struct', 'Dictionary',
+ 'UTF8String')
+
+from _dbus_bindings import ObjectPath, ByteArray, Signature, Byte,\
+ Int16, UInt16, Int32, UInt32,\
+ Int64, UInt64, Dictionary, Array, \
+ String, Boolean, Double, Struct, UTF8String