first add files

This commit is contained in:
2023-10-08 20:59:00 +08:00
parent b494be364b
commit 1dac226337
991 changed files with 368151 additions and 40 deletions

93
lib/dbus/__init__.py Normal file
View File

@@ -0,0 +1,93 @@
"""\
Implements the public API for a D-Bus client. See the dbus.service module
to export objects or claim well-known names.
"""
# 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/>
#
# SPDX-License-Identifier: MIT
#
# 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__ = [
# 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',
# from exceptions
'DBusException',
'MissingErrorHandlerException', 'MissingReplyHandlerException',
'ValidationException', 'IntrospectionParserException',
'UnknownMethodException', 'NameExistsException',
# submodules
'service', 'mainloop', 'lowlevel'
]
from dbus._compat import is_py2
__docformat__ = 'restructuredtext'
# OLPC Sugar compatibility
import dbus.exceptions as exceptions
import dbus.types as types
from _dbus_bindings import __version__
version = tuple(map(int, __version__.split('.')))
from _dbus_bindings import (
get_default_main_loop, set_default_main_loop, validate_bus_name,
validate_error_name, validate_interface_name, validate_member_name,
validate_object_path)
from _dbus_bindings import (
BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, INTROSPECTABLE_IFACE,
LOCAL_IFACE, LOCAL_PATH, PEER_IFACE, PROPERTIES_IFACE)
from dbus.exceptions import (
DBusException, IntrospectionParserException, MissingErrorHandlerException,
MissingReplyHandlerException, NameExistsException, UnknownMethodException,
ValidationException)
from _dbus_bindings import (
Array, Boolean, Byte, ByteArray, Dictionary, Double, Int16, Int32, Int64,
ObjectPath, Signature, String, Struct, UInt16, UInt32, UInt64)
from dbus._dbus import Bus, SystemBus, SessionBus, StarterBus
from dbus.proxies import Interface

15
lib/dbus/_compat.py Normal file
View File

@@ -0,0 +1,15 @@
# Python 2 / Python 3 compatibility helpers.
# Copyright 2011 Barry Warsaw
# Copyright 2021 Collabora Ltd.
# SPDX-License-Identifier: MIT
import sys
is_py3 = True
is_py2 = False
if sys.version_info.major < 3:
raise AssertionError(
'Python 2 has reached end-of-life, and dbus-python no longer '
'supports it.'
)

229
lib/dbus/_dbus.py Normal file
View File

@@ -0,0 +1,229 @@
"""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/>
#
# SPDX-License-Identifier: MIT
#
# 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'
from dbus.exceptions import DBusException
from _dbus_bindings import (
BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_SESSION,
BUS_STARTER, BUS_SYSTEM, DBUS_START_REPLY_ALREADY_RUNNING,
DBUS_START_REPLY_SUCCESS, validate_bus_name,
validate_interface_name, validate_member_name, validate_object_path)
from dbus.bus import BusConnection
from dbus.lowlevel import SignalMessage
from dbus._compat import is_py2
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)

View File

@@ -0,0 +1,87 @@
# 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/>
#
# SPDX-License-Identifier: MIT
#
# 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 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 as e:
raise IntrospectionParserException('%s: %s' % (e.__class__, e))

434
lib/dbus/bus.py Normal file
View File

@@ -0,0 +1,434 @@
# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
#
# SPDX-License-Identifier: MIT
#
# 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 (
BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_SESSION,
BUS_STARTER, BUS_SYSTEM, DBUS_START_REPLY_ALREADY_RUNNING,
DBUS_START_REPLY_SUCCESS, 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, validate_bus_name, validate_error_name,
validate_interface_name, validate_member_name, validate_object_path)
from dbus.connection import Connection
from dbus.exceptions import DBusException
from dbus.lowlevel import HANDLER_RESULT_NOT_YET_HANDLED
from dbus._compat import is_py2
_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)
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 as 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.keys()))
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',
'', ())
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, 'ListActivatableNames',
'', ())
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,))
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)

651
lib/dbus/connection.py Normal file
View File

@@ -0,0 +1,651 @@
# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
#
# SPDX-License-Identifier: MIT
#
# 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
import threading
import weakref
from _dbus_bindings import (
Connection as _Connection, LOCAL_IFACE, LOCAL_PATH, validate_bus_name,
validate_interface_name, validate_member_name, validate_object_path)
from dbus.exceptions import DBusException
from dbus.lowlevel import (
ErrorMessage, HANDLER_RESULT_NOT_YET_HANDLED, MethodCallMessage,
MethodReturnMessage, SignalMessage)
from dbus.proxies import ProxyObject
from dbus._compat import is_py2, is_py3
from _dbus_bindings import String
_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',
'_byte_arrays', '_conn_weakref',
'_destination_keyword', '_interface_keyword',
'_message_keyword', '_member_keyword',
'_sender_keyword', '_path_keyword', '_int_args_match']
__slots__ = tuple(_slots)
def __init__(self, conn, sender, object_path, dbus_interface,
member, handler, 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
if 'utf8_strings' in kwargs:
raise TypeError("unexpected keyword argument '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.items():
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 byte_arrays is less work
kwargs = dict(byte_arrays=True)
args = message.get_args_list(**kwargs)
for index, value in self._int_args_match.items():
if (index >= len(args)
or not isinstance(args[index], String)
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._byte_arrays:
args = message.get_args_list(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 = threading.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.keys()))
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)
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:
# 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, byte_arrays=False,
require_main_loop=True, **kwargs):
"""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 = dict(byte_arrays=byte_arrays)
if 'utf8_strings' in kwargs:
raise TypeError("unexpected keyword argument 'utf8_strings'")
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 as 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,
byte_arrays=False, **kwargs):
"""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 = dict(byte_arrays=byte_arrays)
if 'utf8_strings' in kwargs:
raise TypeError("unexpected keyword argument 'utf8_strings'")
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 as 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)

362
lib/dbus/decorators.py Normal file
View File

@@ -0,0 +1,362 @@
"""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/>
#
# SPDX-License-Identifier: MIT
#
# 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
from dbus._compat import is_py2
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,
byte_arrays=False,
rel_path_keyword=None, **kwargs):
"""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):
if hasattr(inspect, 'Signature'):
args = []
for arg in inspect.signature(func).parameters.values():
if arg.kind in (inspect.Parameter.POSITIONAL_ONLY,
inspect.Parameter.POSITIONAL_OR_KEYWORD):
args.append(arg.name)
else:
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 = dict(byte_arrays=byte_arrays)
if 'utf8_strings' in kwargs:
raise TypeError("unexpected keyword argument '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
if hasattr(inspect, 'Signature'):
args = []
for arg in inspect.signature(func).parameters.values():
if arg.kind in (inspect.Parameter.POSITIONAL_ONLY,
inspect.Parameter.POSITIONAL_OR_KEYWORD):
args.append(arg.name)
else:
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

133
lib/dbus/exceptions.py Normal file
View File

@@ -0,0 +1,133 @@
"""D-Bus exceptions."""
# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
#
# SPDX-License-Identifier: MIT
#
# 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')
from dbus._compat import is_py3
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 __unicode__(self):
"""Return a unicode error"""
# We can't just use Exception.__unicode__ because it chains up weirdly.
# https://code.launchpad.net/~mvo/ubuntu/quantal/dbus-python/lp846044/+merge/129214
if len(self.args) > 1:
s = unicode(self.args)
else:
s = ''.join(self.args)
if self._dbus_error_name is not None:
return '%s: %s' % (self._dbus_error_name, s)
else:
return s
def __str__(self):
"""Return a str error"""
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):
if len(self.args) > 1:
s = str(self.args)
else:
s = ''.join(self.args)
if isinstance(s, bytes):
return s.decode('utf-8', 'replace')
return s
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)

87
lib/dbus/gi_service.py Normal file
View File

@@ -0,0 +1,87 @@
"""Support code for implementing D-Bus services via PyGI."""
# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
#
# SPDX-License-Identifier: MIT
#
# 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__ = ['ExportedGObject']
from gi.repository import GObject
import dbus.service
# The odd syntax used here is required so that the code is compatible with
# both Python 2 and Python 3. It essentially creates a new class called
# ExportedGObject with a metaclass of ExportGObjectType and an __init__()
# function.
#
# 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.
class ExportedGObjectType(GObject.GObject.__class__, 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.GObject.__class__.__init__(cls, name, bases, dct)
dbus.service.InterfaceType.__init__(cls, name, bases, dct)
def ExportedGObject__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)
ExportedGObject__doc__ = '''
A GObject which is exported on D-Bus.
'''
ExportedGObject = ExportedGObjectType(
'ExportedGObject',
(GObject.GObject, dbus.service.Object),
{'__init__': ExportedGObject__init__,
'__doc__': ExportedGObject__doc__,
})

53
lib/dbus/glib.py Normal file
View File

@@ -0,0 +1,53 @@
# 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/>
#
# SPDX-License-Identifier: MIT
#
# 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
from warnings import warn as _warn
init_threads = threads_init
DBusGMainLoop(set_as_default=True)
_warn(DeprecationWarning("""\
Importing dbus.glib to use the GLib main loop with dbus-python is deprecated.
Instead, use this sequence:
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
"""), DeprecationWarning, stacklevel=2)

38
lib/dbus/lowlevel.py Normal file
View File

@@ -0,0 +1,38 @@
# Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
#
# SPDX-License-Identifier: MIT
#
# 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 (
ErrorMessage, HANDLER_RESULT_HANDLED, HANDLER_RESULT_NOT_YET_HANDLED,
MESSAGE_TYPE_ERROR, MESSAGE_TYPE_INVALID, MESSAGE_TYPE_METHOD_CALL,
MESSAGE_TYPE_METHOD_RETURN, MESSAGE_TYPE_SIGNAL, Message,
MethodCallMessage, MethodReturnMessage, PendingCall, SignalMessage)

View File

@@ -0,0 +1,64 @@
# Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
#
# SPDX-License-Identifier: MIT
#
# 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'
)

43
lib/dbus/mainloop/glib.py Normal file
View File

@@ -0,0 +1,43 @@
# 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/>
#
# SPDX-License-Identifier: MIT
#
# 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

567
lib/dbus/proxies.py Normal file
View File

@@ -0,0 +1,567 @@
# 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/>
#
# SPDX-License-Identifier: MIT
#
# 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 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 (
DBusException, IntrospectionParserException, MissingErrorHandlerException,
MissingReplyHandlerException)
__docformat__ = 'restructuredtext'
_logger = logging.getLogger('dbus.proxies')
from _dbus_bindings import (
BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, INTROSPECTABLE_IFACE,
LOCAL_PATH)
from dbus._compat import is_py2
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 ('reply_handler' in keywords 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)
signature = keywords.pop('signature', None)
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 signature is None:
if dbus_interface is None:
key = self._method_name
else:
key = dbus_interface + '.' + self._method_name
signature = 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,
signature,
args,
reply_handler,
error_handler,
**keywords)
else:
return self._connection.call_blocking(self._named_service,
self._object_path,
dbus_interface,
self._method_name,
signature,
args,
**keywords)
def call_async(self, *args, **keywords):
reply_handler = keywords.pop('reply_handler', None)
error_handler = keywords.pop('error_handler', None)
signature = keywords.pop('signature', None)
dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
if signature is None:
if dbus_interface:
key = dbus_interface + '.' + self._method_name
else:
key = self._method_name
signature = self._proxy._introspect_method_map.get(key, None)
self._connection.call_async(self._named_service,
self._object_path,
dbus_interface,
self._method_name,
signature,
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.keys()))
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):
kwargs = {}
return self._bus.call_async(self._named_service,
self.__dbus_object_path__,
INTROSPECTABLE_IFACE, 'Introspect', '', (),
self._introspect_reply_handler,
self._introspect_error_handler,
require_main_loop=False, **kwargs)
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)
self._pending_introspect_queue = []
def _introspect_reply_handler(self, data):
self._introspect_lock.acquire()
try:
try:
self._introspect_method_map = process_introspection_data(data)
except IntrospectionParserException as 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__

119
lib/dbus/server.py Normal file
View File

@@ -0,0 +1,119 @@
# Copyright (C) 2008 Openismus GmbH <http://openismus.com/>
# Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
#
# SPDX-License-Identifier: MIT
#
# 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)

840
lib/dbus/service.py Normal file
View File

@@ -0,0 +1,840 @@
# 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/>
#
# SPDX-License-Identifier: MIT
#
# 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', 'FallbackObject', 'method', 'signal')
__docformat__ = 'restructuredtext'
import sys
import logging
import threading
import traceback
try:
from collections.abc import Sequence
except ImportError:
# Python 2 (and 3.x < 3.3, but we don't support those)
from collections import Sequence
import _dbus_bindings
from dbus import (
INTROSPECTABLE_IFACE, ObjectPath, SessionBus, Signature, Struct,
validate_bus_name, validate_object_path)
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
from dbus._compat import is_py2
_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 as e:
logging.basicConfig()
if signature is None:
try:
signature = reply.guess_signature(retval) + ' (guessed)'
except Exception as 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
if not message.get_no_reply():
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)
if not message.get_no_reply():
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].items():
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
# Define Interface as an instance of the metaclass InterfaceType, in a way
# that is compatible across both Python 2 and Python 3.
Interface = InterfaceType('Interface', (object,), {})
#: 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 = threading.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 isinstance(retval, Sequence):
# 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 as 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.items():
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)

15
lib/dbus/types.py Normal file
View File

@@ -0,0 +1,15 @@
# Copyright 2006-2021 Collabora Ltd.
# Copyright 2011 Barry Warsaw
# SPDX-License-Identifier: MIT
__all__ = ['ObjectPath', 'ByteArray', 'Signature', 'Byte', 'Boolean',
'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64',
'Double', 'String', 'Array', 'Struct', 'Dictionary',
'UnixFd']
from _dbus_bindings import (
Array, Boolean, Byte, ByteArray, Dictionary, Double, Int16, Int32, Int64,
ObjectPath, Signature, String, Struct, UInt16, UInt32, UInt64,
UnixFd)
from dbus._compat import is_py2