first add files
This commit is contained in:
93
lib/dbus/__init__.py
Normal file
93
lib/dbus/__init__.py
Normal 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
15
lib/dbus/_compat.py
Normal 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
229
lib/dbus/_dbus.py
Normal 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)
|
||||
87
lib/dbus/_expat_introspect_parser.py
Normal file
87
lib/dbus/_expat_introspect_parser.py
Normal 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
434
lib/dbus/bus.py
Normal 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
651
lib/dbus/connection.py
Normal 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
362
lib/dbus/decorators.py
Normal 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
133
lib/dbus/exceptions.py
Normal 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
87
lib/dbus/gi_service.py
Normal 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
53
lib/dbus/glib.py
Normal 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
38
lib/dbus/lowlevel.py
Normal 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)
|
||||
64
lib/dbus/mainloop/__init__.py
Normal file
64
lib/dbus/mainloop/__init__.py
Normal 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
43
lib/dbus/mainloop/glib.py
Normal 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
567
lib/dbus/proxies.py
Normal 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
119
lib/dbus/server.py
Normal 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
840
lib/dbus/service.py
Normal 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
15
lib/dbus/types.py
Normal 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
|
||||
Reference in New Issue
Block a user