"""
Pure-Python binding for D-Bus <https://www.freedesktop.org/wiki/Software/dbus/>,
built around libdbus <https://dbus.freedesktop.org/doc/api/html/index.html>.

This Python binding supports hooking into event loops via Python’s standard
asyncio module.
"""
#+
# Copyright 2017-2020 Lawrence D'Oliveiro <ldo@geek-central.gen.nz>.
# Licensed under the GNU Lesser General Public License v2.1 or later.
#-

import os
import builtins
import operator
import array
import enum
import ctypes as ct
from weakref import \
    ref as weak_ref, \
    WeakValueDictionary
import threading
import io
import atexit
import asyncio
import functools
from xml.etree import \
    ElementTree as XMLElementTree
from xml.sax.saxutils import \
    quoteattr as quote_xml_attr

dbus = ct.cdll.LoadLibrary("libdbus-1.so.3")

class DBUS :
    "useful definitions adapted from the D-Bus includes. You will need to use the" \
    " constants, but apart from that, see the more Pythonic wrappers defined outside" \
    " this class in preference to accessing low-level structures directly."

    # General ctypes gotcha: when passing addresses of ctypes-constructed objects
    # to routine calls, do not construct the objects directly in the call. Otherwise
    # the refcount goes to 0 before the routine is actually entered, and the object
    # can get prematurely disposed. Always store the object reference into a local
    # variable, and pass the value of the variable instead.

    # from dbus-protocol.h:

    # Message byte order
    LITTLE_ENDIAN = 'l'
    BIG_ENDIAN = 'B'

    # Protocol version.
    MAJOR_PROTOCOL_VERSION = 1

    # Type code that is never equal to a legitimate type code
    TYPE_INVALID = 0

    # Primitive types
    TYPE_BYTE = ord('y') # 8-bit unsigned integer
    TYPE_BOOLEAN = ord('b') # boolean
    TYPE_INT16 = ord('n') # 16-bit signed integer
    TYPE_UINT16 = ord('q') # 16-bit unsigned integer
    TYPE_INT32 = ord('i') # 32-bit signed integer
    TYPE_UINT32 = ord('u') # 32-bit unsigned integer
    TYPE_INT64 = ord('x') # 64-bit signed integer
    TYPE_UINT64 = ord('t') # 64-bit unsigned integer
    TYPE_DOUBLE = ord('d') # 8-byte double in IEEE 754 format
    TYPE_STRING = ord('s') # UTF-8 encoded, nul-terminated Unicode string
    TYPE_OBJECT_PATH = ord('o') # D-Bus object path
    TYPE_SIGNATURE = ord('g') # D-Bus type signature
    TYPE_UNIX_FD = ord('h') # unix file descriptor

    basic_to_ctypes = \
        { # ctypes objects suitable for holding values of D-Bus types
            TYPE_BYTE : ct.c_ubyte,
            TYPE_BOOLEAN : ct.c_ubyte,
            TYPE_INT16 : ct.c_short,
            TYPE_UINT16 : ct.c_ushort,
            TYPE_INT32 : ct.c_int,
            TYPE_UINT32 : ct.c_uint,
            TYPE_INT64 : ct.c_longlong,
            TYPE_UINT64 : ct.c_ulonglong,
            TYPE_DOUBLE : ct.c_double,
            TYPE_STRING : ct.c_char_p,
            TYPE_OBJECT_PATH : ct.c_char_p,
            TYPE_SIGNATURE : ct.c_char_p,
            TYPE_UNIX_FD : ct.c_int,
        }

    def int_subtype(i, bits, signed) :
        "returns integer i after checking that it fits in the given number of bits."
        if not isinstance(i, int) :
            raise TypeError("value is not int: %s" % repr(i))
        #end if
        if signed :
            lo = - 1 << bits - 1
            hi = (1 << bits - 1) - 1
        else :
            lo = 0
            hi = (1 << bits) - 1
        #end if
        if i < lo or i > hi :
            raise ValueError \
              (
                "%d not in range of %s %d-bit value" % (i, ("unsigned", "signed")[signed], bits)
              )
        #end if
        return \
            i
    #end int_subtype

    subtype_boolean = lambda i : DBUS.int_subtype(i, 1, False)
    subtype_byte = lambda i : DBUS.int_subtype(i, 8, False)
    subtype_int16 = lambda i : DBUS.int_subtype(i, 16, True)
    subtype_uint16 = lambda i : DBUS.int_subtype(i, 16, False)
    subtype_int32 = lambda i : DBUS.int_subtype(i, 32, True)
    subtype_uint32 = lambda i : DBUS.int_subtype(i, 32, False)
    subtype_int64 = lambda i : DBUS.int_subtype(i, 64, True)
    subtype_uint64 = lambda i : DBUS.int_subtype(i, 64, False)

    int_convert = \
        { # range checks for the various D-Bus integer types
            TYPE_BOOLEAN : subtype_boolean,
            TYPE_BYTE : subtype_byte,
            TYPE_INT16 : subtype_int16,
            TYPE_UINT16 : subtype_uint16,
            TYPE_INT32 : subtype_int32,
            TYPE_UINT32 : subtype_uint32,
            TYPE_INT64 : subtype_int64,
            TYPE_UINT64 : subtype_uint64,
        }

    # subclasses for distinguishing various special kinds of D-Bus values:

    class ObjectPath(str) :
        "an object path string."

        def __repr__(self) :
            return \
                "%s(%s)" % (self.__class__.__name__, super().__repr__())
        #end __repr__

    #end ObjectPath

    class Signature(str) :
        "a type-signature string."

        def __repr__(self) :
            return \
                "%s(%s)" % (self.__class__.__name__, super().__repr__())
        #end __repr__

    #end Signature

    class UnixFD(int) :
        "a file-descriptor integer."

        def __repr__(self) :
            return \
                "%s(%s)" % (self.__class__.__name__, super().__repr__())
        #end __repr__

    #end UnixFD

    basic_subclasses = \
        {
            TYPE_BOOLEAN : bool,
            TYPE_OBJECT_PATH : ObjectPath,
            TYPE_SIGNATURE : Signature,
            TYPE_UNIX_FD : UnixFD,
        }

    # Compound types
    TYPE_ARRAY = ord('a') # D-Bus array type
    TYPE_VARIANT = ord('v') # D-Bus variant type

    TYPE_STRUCT = ord('r') # a struct; however, type signatures use STRUCT_BEGIN/END_CHAR
    TYPE_DICT_ENTRY = ord('e') # a dict entry; however, type signatures use DICT_ENTRY_BEGIN/END_CHAR
    NUMBER_OF_TYPES = 16 # does not include TYPE_INVALID or STRUCT/DICT_ENTRY_BEGIN/END_CHAR

    # characters other than typecodes that appear in type signatures
    STRUCT_BEGIN_CHAR = ord('(') # start of a struct type in a type signature
    STRUCT_END_CHAR = ord(')') # end of a struct type in a type signature
    DICT_ENTRY_BEGIN_CHAR = ord('{') # start of a dict entry type in a type signature
    DICT_ENTRY_END_CHAR = ord('}') # end of a dict entry type in a type signature

    MAXIMUM_NAME_LENGTH = 255 # max length in bytes of a bus name, interface or member (object paths are unlimited)

    MAXIMUM_SIGNATURE_LENGTH = 255 # fits in a byte

    MAXIMUM_MATCH_RULE_LENGTH = 1024

    MAXIMUM_MATCH_RULE_ARG_NUMBER = 63

    MAXIMUM_ARRAY_LENGTH = 67108864 # 2 * 26
    MAXIMUM_ARRAY_LENGTH_BITS = 26 # to store the max array size

    MAXIMUM_MESSAGE_LENGTH = MAXIMUM_ARRAY_LENGTH * 2
    MAXIMUM_MESSAGE_LENGTH_BITS = 27

    MAXIMUM_MESSAGE_UNIX_FDS = MAXIMUM_MESSAGE_LENGTH // 4 # FDs are at least 32 bits
    MAXIMUM_MESSAGE_UNIX_FDS_BITS = MAXIMUM_MESSAGE_LENGTH_BITS - 2

    MAXIMUM_TYPE_RECURSION_DEPTH = 32

    # Types of message

    MESSAGE_TYPE_INVALID = 0 # never a valid message type
    MESSAGE_TYPE_METHOD_CALL = 1
    MESSAGE_TYPE_METHOD_RETURN = 2
    MESSAGE_TYPE_ERROR = 3
    MESSAGE_TYPE_SIGNAL = 4

    NUM_MESSAGE_TYPES = 5

    # Header flags

    HEADER_FLAG_NO_REPLY_EXPECTED = 0x1
    HEADER_FLAG_NO_AUTO_START = 0x2
    HEADER_FLAG_ALLOW_INTERACTIVE_AUTHORIZATION = 0x4

    # Header fields

    HEADER_FIELD_INVALID = 0
    HEADER_FIELD_PATH = 1
    HEADER_FIELD_INTERFACE = 2
    HEADER_FIELD_MEMBER = 3
    HEADER_FIELD_ERROR_NAME = 4
    HEADER_FIELD_REPLY_SERIAL = 5
    HEADER_FIELD_DESTINATION = 6
    HEADER_FIELD_SENDER = 7
    HEADER_FIELD_SIGNATURE = 8
    HEADER_FIELD_UNIX_FDS = 9

    HEADER_FIELD_LAST = HEADER_FIELD_UNIX_FDS

    HEADER_SIGNATURE = bytes \
      ((
        TYPE_BYTE,
        TYPE_BYTE,
        TYPE_BYTE,
        TYPE_BYTE,
        TYPE_UINT32,
        TYPE_UINT32,
        TYPE_ARRAY,
        STRUCT_BEGIN_CHAR,
        TYPE_BYTE,
        TYPE_VARIANT,
        STRUCT_END_CHAR,
      ))
    MINIMUM_HEADER_SIZE = 16 # smallest header size that can occur (missing required fields, though)

    # Errors
    ERROR_FAILED = "org.freedesktop.DBus.Error.Failed" # generic error
    ERROR_NO_MEMORY = "org.freedesktop.DBus.Error.NoMemory"
    ERROR_SERVICE_UNKNOWN = "org.freedesktop.DBus.Error.ServiceUnknown"
    ERROR_NAME_HAS_NO_OWNER = "org.freedesktop.DBus.Error.NameHasNoOwner"
    ERROR_NO_REPLY = "org.freedesktop.DBus.Error.NoReply"
    ERROR_IO_ERROR = "org.freedesktop.DBus.Error.IOError"
    ERROR_BAD_ADDRESS = "org.freedesktop.DBus.Error.BadAddress"
    ERROR_NOT_SUPPORTED = "org.freedesktop.DBus.Error.NotSupported"
    ERROR_LIMITS_EXCEEDED = "org.freedesktop.DBus.Error.LimitsExceeded"
    ERROR_ACCESS_DENIED = "org.freedesktop.DBus.Error.AccessDenied"
    ERROR_AUTH_FAILED = "org.freedesktop.DBus.Error.AuthFailed"
    ERROR_NO_SERVER = "org.freedesktop.DBus.Error.NoServer"
    ERROR_TIMEOUT = "org.freedesktop.DBus.Error.Timeout"
    ERROR_NO_NETWORK = "org.freedesktop.DBus.Error.NoNetwork"
    ERROR_ADDRESS_IN_USE = "org.freedesktop.DBus.Error.AddressInUse"
    ERROR_DISCONNECTED = "org.freedesktop.DBus.Error.Disconnected"
    ERROR_INVALID_ARGS = "org.freedesktop.DBus.Error.InvalidArgs"
    ERROR_FILE_NOT_FOUND = "org.freedesktop.DBus.Error.FileNotFound"
    ERROR_FILE_EXISTS = "org.freedesktop.DBus.Error.FileExists"
    ERROR_UNKNOWN_METHOD = "org.freedesktop.DBus.Error.UnknownMethod"
    ERROR_UNKNOWN_OBJECT = "org.freedesktop.DBus.Error.UnknownObject"
    ERROR_UNKNOWN_INTERFACE = "org.freedesktop.DBus.Error.UnknownInterface"
    ERROR_UNKNOWN_PROPERTY = "org.freedesktop.DBus.Error.UnknownProperty"
    ERROR_PROPERTY_READ_ONLY = "org.freedesktop.DBus.Error.PropertyReadOnly"
    ERROR_TIMED_OUT = "org.freedesktop.DBus.Error.TimedOut"
    ERROR_MATCH_RULE_NOT_FOUND = "org.freedesktop.DBus.Error.MatchRuleNotFound"
    ERROR_MATCH_RULE_INVALID = "org.freedesktop.DBus.Error.MatchRuleInvalid"
    ERROR_SPAWN_EXEC_FAILED = "org.freedesktop.DBus.Error.Spawn.ExecFailed"
    ERROR_SPAWN_FORK_FAILED = "org.freedesktop.DBus.Error.Spawn.ForkFailed"
    ERROR_SPAWN_CHILD_EXITED = "org.freedesktop.DBus.Error.Spawn.ChildExited"
    ERROR_SPAWN_CHILD_SIGNALED = "org.freedesktop.DBus.Error.Spawn.ChildSignaled"
    ERROR_SPAWN_FAILED = "org.freedesktop.DBus.Error.Spawn.Failed"
    ERROR_SPAWN_SETUP_FAILED = "org.freedesktop.DBus.Error.Spawn.FailedToSetup"
    ERROR_SPAWN_CONFIG_INVALID = "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"
    ERROR_SPAWN_SERVICE_INVALID = "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"
    ERROR_SPAWN_SERVICE_NOT_FOUND = "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"
    ERROR_SPAWN_PERMISSIONS_INVALID = "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"
    ERROR_SPAWN_FILE_INVALID = "org.freedesktop.DBus.Error.Spawn.FileInvalid"
    ERROR_SPAWN_NO_MEMORY = "org.freedesktop.DBus.Error.Spawn.NoMemory"
    ERROR_UNIX_PROCESS_ID_UNKNOWN = "org.freedesktop.DBus.Error.UnixProcessIdUnknown"
    ERROR_INVALID_SIGNATURE = "org.freedesktop.DBus.Error.InvalidSignature"
    ERROR_INVALID_FILE_CONTENT = "org.freedesktop.DBus.Error.InvalidFileContent"
    ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN = "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"
    ERROR_ADT_AUDIT_DATA_UNKNOWN = "org.freedesktop.DBus.Error.AdtAuditDataUnknown"
    ERROR_OBJECT_PATH_IN_USE = "org.freedesktop.DBus.Error.ObjectPathInUse"
    ERROR_INCONSISTENT_MESSAGE = "org.freedesktop.DBus.Error.InconsistentMessage"
    ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED = "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired"

    # XML introspection format
    INTROSPECT_1_0_XML_NAMESPACE = "http://www.freedesktop.org/standards/dbus"
    INTROSPECT_1_0_XML_PUBLIC_IDENTIFIER = "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
    INTROSPECT_1_0_XML_SYSTEM_IDENTIFIER = "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"
    INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE = \
        (
            "<!DOCTYPE node PUBLIC \""
        +
            INTROSPECT_1_0_XML_PUBLIC_IDENTIFIER
        +
            "\"\n\"" + INTROSPECT_1_0_XML_SYSTEM_IDENTIFIER
        +
            "\">\n"
        )

    # from dbus-shared.h:

    # well-known bus types
    BusType = ct.c_uint
    BUS_SESSION = 0
    BUS_SYSTEM = 1
    BUS_STARTER = 2

    # results that a message handler can return
    BusHandlerResult = ct.c_uint
    HANDLER_RESULT_HANDLED = 0 # no need to try more handlers
    HANDLER_RESULT_NOT_YET_HANDLED = 1 # see if other handlers want it
    HANDLER_RESULT_NEED_MEMORY = 2 # try again later with more memory

    # Bus names
    SERVICE_DBUS = "org.freedesktop.DBus" # used to talk to the bus itself

    # Paths
    PATH_DBUS = "/org/freedesktop/DBus" # object path used to talk to the bus itself
    PATH_LOCAL = "/org/freedesktop/DBus/Local" # path used in local/in-process-generated messages

    # Interfaces
    INTERFACE_DBUS = "org.freedesktop.DBus" # interface exported by the object with SERVICE_DBUS and PATH_DBUS
    INTERFACE_MONITORING = "org.freedesktop.DBus.Monitoring" # monitoring interface exported by the dbus-daemon
    INTERFACE_VERBOSE = "org.freedesktop.DBus.Verbose" # verbose interface exported by the dbus-daemon
    INTERFACE_INTROSPECTABLE = "org.freedesktop.DBus.Introspectable" # interface supported by introspectable objects
    INTERFACE_PROPERTIES = "org.freedesktop.DBus.Properties" # interface supported by objects with properties
    INTERFACE_PEER = "org.freedesktop.DBus.Peer" # interface supported by most dbus peers
    INTERFACE_LOCAL = "org.freedesktop.DBus.Local" # methods can only be invoked locally

    # Owner flags for request_name
    NAME_FLAG_ALLOW_REPLACEMENT = 0x1
    NAME_FLAG_REPLACE_EXISTING = 0x2
    NAME_FLAG_DO_NOT_QUEUE = 0x4

    # Replies to request for a name
    REQUEST_NAME_REPLY_PRIMARY_OWNER = 1
    REQUEST_NAME_REPLY_IN_QUEUE = 2
    REQUEST_NAME_REPLY_EXISTS = 3
    REQUEST_NAME_REPLY_ALREADY_OWNER = 4

    # Replies to releasing a name
    RELEASE_NAME_REPLY_RELEASED = 1
    RELEASE_NAME_REPLY_NON_EXISTENT = 2
    RELEASE_NAME_REPLY_NOT_OWNER = 3

    # Replies to service starts
    START_REPLY_SUCCESS = 1
    START_REPLY_ALREADY_RUNNING = 2

    # from dbus-types.h:

    bool_t = ct.c_uint

    # from dbus-memory.h:

    FreeFunction = ct.CFUNCTYPE(None, ct.c_void_p)

    # from dbus-connection.h:

    HandlerResult = ct.c_uint

    class Error(ct.Structure) :
        _fields_ = \
            [
                ("name", ct.c_char_p),
                ("message", ct.c_char_p),
                ("padding", 2 * ct.c_void_p),
            ]
    #end Error
    ErrorPtr = ct.POINTER(Error)

    WatchFlags = ct.c_uint
    WATCH_READABLE = 1 << 0
    WATCH_WRITABLE = 1 << 1
    WATCH_ERROR = 1 << 2
    WATCH_HANGUP = 1 << 3

    DispatchStatus = ct.c_uint
    DISPATCH_DATA_REMAINS = 0 # more data available
    DISPATCH_COMPLETE = 1 # all available data has been processed
    DISPATCH_NEED_MEMORY = 2 # not enough memory to continue

    AddWatchFunction = ct.CFUNCTYPE(bool_t, ct.c_void_p, ct.c_void_p)
      # add_watch(DBusWatch, user_data) returns success/failure
    WatchToggledFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p)
      # watch_toggled(DBusWatch, user_data)
    RemoveWatchFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p)
      # remove_watch(DBusWatch, user_data)

    AddTimeoutFunction = ct.CFUNCTYPE(bool_t, ct.c_void_p, ct.c_void_p)
      # add_timeout(DBusTimeout, user_data) returns success/failure
    TimeoutToggledFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p)
      # timeout_toggled(DBusTimeout, user_data)
    RemoveTimeoutFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p)
      # remove_timeout(DBusTimeout, user_data)

    DispatchStatusFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.POINTER(DispatchStatus), ct.c_void_p)
      # dispatch_status(DBusConnection, DBusDispatchStatus, user_data)
    WakeupMainFunction = ct.CFUNCTYPE(None, ct.c_void_p)
      # wakeup_main(user_data)

    AllowUnixUserFunction = ct.CFUNCTYPE(bool_t, ct.c_void_p, ct.c_ulong, ct.c_void_p)
      # allow_unix_user(DBusConnection, uid, user_data) returns success/failure
    AllowWindowsUserFunction = ct.CFUNCTYPE(bool_t, ct.c_void_p, ct.c_void_p, ct.c_void_p)
      # allow_windows_user(DBusConnection, user_sid, user_data)returns success/failure

    PendingCallNotifyFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p)
      # notify(DBusPendingCall, user_data)

    HandleMessageFunction = ct.CFUNCTYPE(HandlerResult, ct.c_void_p, ct.c_void_p, ct.c_void_p)
      # handle_message(DBusConnection, DBusMessage, user_data)

    ObjectPathUnregisterFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p)
      # unregister(DBusConnection, user_data)
    ObjectPathMessageFunction = ct.CFUNCTYPE(HandlerResult, ct.c_void_p, ct.c_void_p, ct.c_void_p)
      # handle_message(DBusConnection, DBusMessage, user_data)

    class ObjectPathVTable(ct.Structure) :
        pass
    #end ObjectPathVTable
    ObjectPathVTable._fields_ = \
        [
            ("unregister_function", ObjectPathUnregisterFunction),
            ("message_function", ObjectPathMessageFunction),
            ("internal_pad1", ct.CFUNCTYPE(None, ct.c_void_p)),
            ("internal_pad2", ct.CFUNCTYPE(None, ct.c_void_p)),
            ("internal_pad3", ct.CFUNCTYPE(None, ct.c_void_p)),
            ("internal_pad4", ct.CFUNCTYPE(None, ct.c_void_p)),
        ]
    ObjectPathVTablePtr = ct.POINTER(ObjectPathVTable)

    # from dbus-pending-call.h:
    TIMEOUT_INFINITE = 0x7fffffff
    TIMEOUT_USE_DEFAULT = -1

    # from dbus-message.h:
    class MessageIter(ct.Structure) :
        "contains no public fields."
        _fields_ = \
            [
                ("dummy1", ct.c_void_p),
                ("dummy2", ct.c_void_p),
                ("dummy3", ct.c_uint),
                ("dummy4", ct.c_int),
                ("dummy5", ct.c_int),
                ("dummy6", ct.c_int),
                ("dummy7", ct.c_int),
                ("dummy8", ct.c_int),
                ("dummy9", ct.c_int),
                ("dummy10", ct.c_int),
                ("dummy11", ct.c_int),
                ("pad1", ct.c_int),
                ("pad2", ct.c_void_p),
                ("pad3", ct.c_void_p),
            ]
    #end MessageIter
    MessageIterPtr = ct.POINTER(MessageIter)

    # from dbus-server.h:
    NewConnectionFunction = ct.CFUNCTYPE(None, ct.c_void_p, ct.c_void_p, ct.c_void_p)
      # new_connection(DBusServer, DBusConnection, user_data)

    # from dbus-signature.h:
    class SignatureIter(ct.Structure) :
        "contains no public fields."
        _fields_ = \
            [
                ("dummy1", ct.c_void_p),
                ("dummy2", ct.c_void_p),
                ("dummy8", ct.c_uint),
                ("dummy12", ct.c_int),
                ("dummy17", ct.c_int),
            ]
    #end SignatureIter
    SignatureIterPtr = ct.POINTER(SignatureIter)

#end DBUS

class DBUSX:
    "additional definitions not part of the official interfaces"

    DEFAULT_TIMEOUT = 25 # seconds, from dbus-connection-internal.h in libdbus source

    # For reference implementation for how to connect to daemon,
    # see libdbus sources, dbus/dbus-bus.c (internal_bus_get routine
    # and stuff that it calls)

    # environment variables used to find addresses of bus daemons
    SESSION_BUS_ADDRESS_VAR = "DBUS_SESSION_BUS_ADDRESS"
    SYSTEM_BUS_ADDRESS_VAR = "DBUS_SYSTEM_BUS_ADDRESS"
    STARTER_BUS_ADDRESS_VAR = "DBUS_STARTER_ADDRESS"
    STARTER_BUS_ADDRESS_TYPE = "DBUS_STARTER_BUS_TYPE"

    # values for value of STARTER_BUS_ADDRESS_TYPE
    # If cannot determine type, then default to session bus
    BUS_TYPE_SESSION = "session"
    BUS_TYPE_SYSTEM = "system"

    SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket"
      # default system bus daemon address if value of SYSTEM_BUS_ADDRESS_VAR is not defined
    SESSION_BUS_ADDRESS = "autolaunch:"
      # default session bus daemon address if value of SESSION_BUS_ADDRESS_VAR is not defined

    INTERFACE_OBJECT_MANAGER = "org.freedesktop.DBus.ObjectManager"
      # no symbolic name for this in standard headers as yet

#end DBUSX

#+
# Useful stuff
#-

if hasattr(asyncio, "get_running_loop") :
    # new in Python 3.7
    get_running_loop = asyncio.get_running_loop
else :
    # as long as I want to support pre-3.7...
    get_running_loop = asyncio.get_event_loop
#end if

def get_event_loop() :
    "Python docs indicate that asyncio.get_event_loop() is going away" \
    " in its current form. But I still need to be able to attach objects" \
    " to the default event loop from a non-coroutine context. So I" \
    " reimplement its original semantics here."
    return \
        asyncio.get_event_loop_policy().get_event_loop()
#end get_event_loop

def _wderef(w_self, parent) :
    self = w_self()
    assert self != None, "%s has gone away" % parent
    return \
        self
#end _wderef

def call_async(func, funcargs = (), timeout = None, abort = None, loop = None) :
    "invokes func on a separate temporary thread and returns a Future that" \
    " can be used to wait for its completion and obtain its result. If timeout" \
    " is not None, then waiters on the Future will get a TimeoutError exception" \
    " if the function has not completed execution after that number of seconds." \
    " This allows easy invocation of blocking I/O functions in an asyncio-" \
    "compatible fashion. But note that the operation cannot be cancelled" \
    " if the timeout elapses; instead, you can specify an abort callback" \
    " which will be invoked with whatever result is eventually returned from" \
    " func."

    if loop == None :
        loop = get_running_loop()
    #end if

    timeout_task = None

    def func_done(ref_awaiting, result) :
        awaiting = ref_awaiting()
        if awaiting != None :
            if not awaiting.done() :
                awaiting.set_result(result)
                if timeout_task != None :
                    timeout_task.cancel()
                #end if
            else :
                if abort != None :
                    abort(result)
                #end if
            #end if
        #end if
    #end func_done

    def do_func_timedout(ref_awaiting) :
        awaiting = ref_awaiting()
        if awaiting != None :
            if not awaiting.done() :
                awaiting.set_exception(TimeoutError())
                # Python doesn’t give me any (easy) way to cancel the thread running the
                # do_func() call, so just let it run to completion, whereupon func_done()
                # will get rid of the result. Even if I could delete the thread, can I be sure
                # that would clean up memory and OS/library resources properly?
            #end if
        #end if
    #end do_func_timedout

    def do_func(ref_awaiting) :
        # makes the blocking call on a separate thread.
        result = func(*funcargs)
        # A Future is not itself threadsafe, but I can thread-safely
        # run a callback on the main thread to set it.
        loop.call_soon_threadsafe(func_done, ref_awaiting, result)
    #end do_func

#begin call_async
    awaiting = loop.create_future()
    ref_awaiting = weak_ref(awaiting)
      # weak ref to avoid circular refs with loop
    subthread = threading.Thread(target = do_func, args = (ref_awaiting,), daemon = True)
    subthread.start()
    if timeout != None :
        timeout_task = loop.call_later(timeout, do_func_timedout, ref_awaiting)
    #end if
    return \
        awaiting
#end call_async

#+
# Higher-level interface to type system
#-

class TYPE(enum.Enum) :
    "D-Bus type codes wrapped up in an enumeration."

    BYTE = ord('y') # 8-bit unsigned integer
    BOOLEAN = ord('b') # boolean
    INT16 = ord('n') # 16-bit signed integer
    UINT16 = ord('q') # 16-bit unsigned integer
    INT32 = ord('i') # 32-bit signed integer
    UINT32 = ord('u') # 32-bit unsigned integer
    INT64 = ord('x') # 64-bit signed integer
    UINT64 = ord('t') # 64-bit unsigned integer
    DOUBLE = ord('d') # 8-byte double in IEEE 754 format
    STRING = ord('s') # UTF-8 encoded, nul-terminated Unicode string
    OBJECT_PATH = ord('o') # D-Bus object path
    SIGNATURE = ord('g') # D-Bus type signature
    UNIX_FD = ord('h') # unix file descriptor

    ARRAY = ord('a') # array of elements all of same type, or possibly dict
    STRUCT = ord('r') # sequence of elements of arbitrary types
    VARIANT = ord('v') # a single element of dynamic type

    @property
    def is_basic(self) :
        "does this code represent a basic (non-container) type."
        return \
            self.value in DBUS.basic_to_ctypes
    #end is_basic

#end TYPE

class Type :
    "base class for all Types. The “signature” property returns the fully-encoded" \
    " signature string for the entire Type."

    __slots__ = ("code",)

    def __init__(self, code) :
        if not isinstance(code, TYPE) :
            raise TypeError("only TYPE.xxx values allowed")
        #end if
        self.code = code
    #end __init__

    @property
    def signature(self) :
        raise NotImplementedError("subclass forgot to override signature property")
    #end signature

    def __eq__(t1, t2) :
        raise NotImplementedError("subclass forgot to override __eq__ method")
    #end __eq__

    def validate(self, val) :
        "returns val if it is an acceptable value of this Type, else raises" \
        " TypeError or ValueError."
        raise NotImplementedError("subclass forgot to override validate method")
    #end validate

    def __repr__(self) :
        return \
            "%s(sig = %s)" % (type(self).__name__, repr(self.signature))
    #end __repr__

#end Type

class BasicType(Type) :
    "a basic (non-container) type."

    __slots__ = ()

    def __init__(self, code) :
        if not isinstance(code, TYPE) or not code.is_basic :
            raise TypeError("only basic TYPE.xxx values allowed")
        #end if
        super().__init__(code)
    #end __init__

    def __repr__(self) :
        return \
            "%s(%s)" % (type(self).__name__, repr(self.code))
    #end __repr__

    @property
    def signature(self) :
        return \
            chr(self.code.value)
    #end signature

    def __eq__(t1, t2) :
        return \
            isinstance(t2, BasicType) and t1.code == t2.code
    #end __eq__

    def validate(self, val) :
        if self.code.value in DBUS.int_convert :
            val = DBUS.int_convert[self.code.value](val)
        elif self.code == TYPE.DOUBLE :
            if not isinstance(val, float) :
                raise TypeError("expecting a float, not %s: %s" % (type(val).__name__, repr(val)))
            #end if
        elif self.code == TYPE.UNIX_FD :
            val = DBUS.subtype_uint32(val)
        elif DBUS.basic_to_ctypes[self.code.value] == ct.c_char_p :
            if not isinstance(val, str) :
                raise TypeError("expecting a string, not %s: %s" % (type(val).__name__, repr(val)))
            #end if
        else :
            raise RuntimeError("unknown basic type %s" % repr(self.code))
        #end if
        return \
            val
    #end validate

#end BasicType

class VariantType(Type) :
    "the variant type--a single element of a type determined at run-time."

    def __init__(self) :
        super().__init__(TYPE.VARIANT)
    #end __init__

    @property
    def signature(self) :
        return \
            chr(TYPE.VARIANT.value)
    #end signature

    def __repr__(self) :
        return \
            "%s()" % type(self).__name__
    #end __repr__

    def __eq__(t1, t2) :
        return \
            isinstance(t2, VariantType)
    #end __eq__

    def validate(self, val) :
        if not isinstance(val, (tuple, list)) or len(val) != 2 :
            raise ValueError("expecting a (type, value) pair")
        #end if
        valtype, val = val
        valtype = parse_single_signature(valtype)
        return \
            (valtype, valtype.validate(val))
    #end validate

#end VariantType

class StructType(Type) :
    "a sequence of one or more arbitrary types (empty structs are not allowed)."

    __slots__ = ("elttypes",)

    def __init__(self, *types) :
        if len(types) == 0 :
            raise TypeError("must have at least one element type")
        #end if
        if not all(isinstance(t, Type) for t in types) :
            raise TypeError("struct elements must be Types")
        #end if
        super().__init__(TYPE.STRUCT)
        self.elttypes = tuple(types)
    #end __init__

    def __repr__(self) :
        return \
            "%s(%s)" % (type(self).__name__, repr(self.elttypes))
    #end __repr__

    @property
    def signature(self) :
        return \
            "(%s)" % "".join(t.signature for t in self.elttypes)
    #end signature

    def __eq__(t1, t2) :
        return \
            (
                    isinstance(t2, StructType)
                and
                    len(t1.elttypes) == len(t2.elttypes)
                and
                    all(e1 == e2 for e1, e2 in zip(t1.elttypes, t2.elttypes))
            )
    #end __eq__

    def validate(self, val) :
        if not isinstance(val, (tuple, list)) or len(val) != len(self.elttypes) :
            raise TypeError \
              (
                "need a list or tuple of %d elements, not %s" % (len(self.elttypes), repr(val))
              )
        #end if
        return \
            type(val)(elttype.validate(elt) for elttype, elt in zip(self.elttypes, val))
    #end validate

#end StructType

class ArrayType(Type) :
    "an array of zero or more elements all of the same type."

    __slots__ = ("elttype",)

    def __init__(self, elttype) :
        if not isinstance(elttype, Type) :
            raise TypeError("invalid array element type")
        #end if
        super().__init__(TYPE.ARRAY)
        self.elttype = elttype
    #end __init__

    def __repr__(self) :
        return \
            "%s[%s]" % (type(self).__name__, repr(self.elttype))
    #end __repr__

    @property
    def signature(self) :
        return \
            chr(TYPE.ARRAY.value) + self.elttype.signature
    #end signature

    def __eq__(t1, t2) :
        return \
            isinstance(t2, ArrayType) and t1.elttype == t2.elttype
    #end __eq__

    def validate(self, val) :
        if not isinstance(val, (tuple, list)) :
            raise TypeError("need a tuple or list, not %s: %s" % (type(val).__name__, repr(val)))
        #end if
        return \
            type(val)(self.elttype.validate(elt) for elt in val)
    #end validate

#end ArrayType

class DictType(Type) :
    "a dictionary mapping zero or more keys to values."

    __slots__ = ("keytype", "valuetype")

    def __init__(self, keytype, valuetype) :
        if not isinstance(keytype, BasicType) or not isinstance(valuetype, Type) :
            raise TypeError("invalid dict key/value type")
        #end if
        super().__init__(TYPE.ARRAY)
        self.keytype = keytype
        self.valuetype = valuetype
    #end keytype

    def __repr__(self) :
        return \
            "%s[%s : %s]" % (type(self).__name__, repr(self.keytype), repr(self.valuetype))
    #end __repr__

    @property
    def signature(self) :
        return \
            "%s{%s%s}" % (chr(TYPE.ARRAY.value), self.keytype.signature, self.valuetype.signature)
    #end signature

    @property
    def entry_signature(self) :
        "signature for a dict entry."
        return \
            "{%s%s}" % (self.keytype.signature, self.valuetype.signature)
    #end entry_signature

    def __eq__(t1, t2) :
        return \
            isinstance(t2, DictType) and t1.keytype == t2.keytype and t1.valuetype == t2.valuetype
    #end __eq__

    def validate(self, val) :
        if not isinstance(val, dict) :
            raise TypeError("need a dict, not %s: %s" % (type(val).__name__, repr(val)))
        #end if
        return \
            type(val) \
              (
                (self.keytype.validate(key), self.valuetype.validate(val[key]))
                for key in val
              )
    #end validate

#end DictType

def data_key(data) :
    "returns a unique value that allows data to be used as a dict/set key."
    if isinstance(data, (bytes, float, frozenset, int, str, tuple)) :
        result = data
    else :
        # data itself is non-hashable
        result = id(data)
    #end if
    return \
        result
#end data_key

#+
# Library prototypes
#-

# from dbus-connection.h:
dbus.dbus_connection_open.restype = ct.c_void_p
dbus.dbus_connection_open.argtypes = (ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_connection_open_private.restype = ct.c_void_p
dbus.dbus_connection_open_private.argtypes = (ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_connection_ref.restype = ct.c_void_p
dbus.dbus_connection_ref.argtypes = (ct.c_void_p,)
dbus.dbus_connection_unref.restype = None
dbus.dbus_connection_unref.argtypes = (ct.c_void_p,)
dbus.dbus_connection_close.restype = None
dbus.dbus_connection_close.argtypes = (ct.c_void_p,)
dbus.dbus_connection_get_is_connected.restype = DBUS.bool_t
dbus.dbus_connection_get_is_connected.argtypes = (ct.c_void_p,)
dbus.dbus_connection_get_is_authenticated.restype = DBUS.bool_t
dbus.dbus_connection_get_is_authenticated.argtypes = (ct.c_void_p,)
dbus.dbus_connection_get_is_anonymous.restype = DBUS.bool_t
dbus.dbus_connection_get_is_anonymous.argtypes = (ct.c_void_p,)
dbus.dbus_connection_get_server_id.restype = ct.c_void_p
dbus.dbus_connection_get_server_id.argtypes = (ct.c_void_p,)
dbus.dbus_connection_can_send_type.restype = DBUS.bool_t
dbus.dbus_connection_can_send_type.argtypes = (ct.c_void_p, ct.c_int)
dbus.dbus_connection_set_exit_on_disconnect.restype = None
dbus.dbus_connection_set_exit_on_disconnect.argtypes = (ct.c_void_p, DBUS.bool_t)
dbus.dbus_connection_preallocate_send.restype = ct.c_void_p
dbus.dbus_connection_preallocate_send.argtypes = (ct.c_void_p,)
dbus.dbus_connection_free_preallocated_send.restype = None
dbus.dbus_connection_free_preallocated_send.argtypes = (ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_send_preallocated.restype = None
dbus.dbus_connection_send_preallocated.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.POINTER(ct.c_uint))
dbus.dbus_connection_has_messages_to_send.restype = DBUS.bool_t
dbus.dbus_connection_has_messages_to_send.argtypes = (ct.c_void_p,)
dbus.dbus_connection_send.restype = DBUS.bool_t
dbus.dbus_connection_send.argtypes = (ct.c_void_p, ct.c_void_p, ct.POINTER(ct.c_uint))
dbus.dbus_connection_send_with_reply.restype = DBUS.bool_t
dbus.dbus_connection_send_with_reply.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_int)
dbus.dbus_connection_send_with_reply_and_block.restype = ct.c_void_p
dbus.dbus_connection_send_with_reply_and_block.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_int, DBUS.ErrorPtr)
dbus.dbus_connection_flush.restype = None
dbus.dbus_connection_flush.argtypes = (ct.c_void_p,)
dbus.dbus_connection_read_write_dispatch.restype = DBUS.bool_t
dbus.dbus_connection_read_write_dispatch.argtypes = (ct.c_void_p, ct.c_int)
dbus.dbus_connection_read_write.restype = DBUS.bool_t
dbus.dbus_connection_read_write.argtypes = (ct.c_void_p, ct.c_int)
dbus.dbus_connection_borrow_message.restype = ct.c_void_p
dbus.dbus_connection_borrow_message.argtypes = (ct.c_void_p,)
dbus.dbus_connection_return_message.restype = None
dbus.dbus_connection_return_message.argtypes = (ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_steal_borrowed_message.restype = None
dbus.dbus_connection_steal_borrowed_message.argtypes = (ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_pop_message.restype = ct.c_void_p
dbus.dbus_connection_pop_message.argtypes = (ct.c_void_p,)
dbus.dbus_connection_get_dispatch_status.restype = ct.c_uint
dbus.dbus_connection_get_dispatch_status.argtypes = (ct.c_void_p,)
dbus.dbus_connection_dispatch.restype = ct.c_uint
dbus.dbus_connection_dispatch.argtypes = (ct.c_void_p,)
dbus.dbus_connection_set_watch_functions.restype = DBUS.bool_t
dbus.dbus_connection_set_watch_functions.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_set_timeout_functions.restype = DBUS.bool_t
dbus.dbus_connection_set_timeout_functions.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_set_wakeup_main_function.restype = None
dbus.dbus_connection_set_wakeup_main_function.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_set_dispatch_status_function.restype = None
dbus.dbus_connection_set_dispatch_status_function.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_get_unix_user.restype = DBUS.bool_t
dbus.dbus_connection_get_unix_user.argtypes = (ct.c_void_p, ct.POINTER(ct.c_ulong))
dbus.dbus_connection_get_unix_process_id.restype = DBUS.bool_t
dbus.dbus_connection_get_unix_process_id.argtypes = (ct.c_void_p, ct.POINTER(ct.c_ulong))
dbus.dbus_connection_get_adt_audit_session_data.restype = DBUS.bool_t
dbus.dbus_connection_get_adt_audit_session_data.argtypes = (ct.c_void_p, ct.c_void_p, ct.POINTER(ct.c_uint))
dbus.dbus_connection_set_unix_user_function.restype = None
dbus.dbus_connection_set_unix_user_function.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_get_windows_user.restype = DBUS.bool_t
dbus.dbus_connection_get_windows_user.argtypes = (ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_set_windows_user_function.restype = None
dbus.dbus_connection_set_windows_user_function.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_set_allow_anonymous.restype = None
dbus.dbus_connection_set_allow_anonymous.argtypes = (ct.c_void_p, DBUS.bool_t)
dbus.dbus_connection_set_route_peer_messages.restype = None
dbus.dbus_connection_set_route_peer_messages.argtypes = (ct.c_void_p, DBUS.bool_t)

dbus.dbus_connection_add_filter.restype = DBUS.bool_t
dbus.dbus_connection_add_filter.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_remove_filter.restype = None
dbus.dbus_connection_remove_filter.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p)

dbus.dbus_connection_allocate_data_slot.restype = DBUS.bool_t
dbus.dbus_connection_allocate_data_slot.argtypes = (ct.POINTER(ct.c_uint),)
dbus.dbus_connection_free_data_slot.restype = None
dbus.dbus_connection_free_data_slot.argtypes = (ct.c_uint,)
dbus.dbus_connection_set_data.restype = DBUS.bool_t
dbus.dbus_connection_set_data.argtypes = (ct.c_void_p, ct.c_uint, ct.c_void_p, ct.c_void_p)
dbus.dbus_connection_get_data.restype = ct.c_void_p
dbus.dbus_connection_get_data.argtypes = (ct.c_void_p, ct.c_uint)
dbus.dbus_connection_set_change_sigpipe.restype = None
dbus.dbus_connection_set_change_sigpipe.argtypes = (DBUS.bool_t,)
dbus.dbus_connection_set_max_message_size.restype = None
dbus.dbus_connection_set_max_message_size.argtypes = (ct.c_void_p, ct.c_long)
dbus.dbus_connection_get_max_message_size.restype = ct.c_long
dbus.dbus_connection_get_max_message_size.argtypes = (ct.c_void_p,)
dbus.dbus_connection_set_max_received_size.restype = None
dbus.dbus_connection_set_max_received_size.argtypes = (ct.c_void_p, ct.c_long)
dbus.dbus_connection_get_max_received_size.restype = ct.c_long
dbus.dbus_connection_get_max_received_size.argtypes = (ct.c_void_p,)
dbus.dbus_connection_set_max_message_unix_fds.restype = None
dbus.dbus_connection_set_max_message_unix_fds.argtypes = (ct.c_void_p, ct.c_long)
dbus.dbus_connection_get_max_message_unix_fds.restype = ct.c_long
dbus.dbus_connection_get_max_message_unix_fds.argtypes = (ct.c_void_p,)
dbus.dbus_connection_set_max_received_unix_fds.restype = None
dbus.dbus_connection_set_max_received_unix_fds.argtypes = (ct.c_void_p, ct.c_long)
dbus.dbus_connection_get_max_received_unix_fds.restype = ct.c_long
dbus.dbus_connection_get_max_received_unix_fds.argtypes = (ct.c_void_p,)

dbus.dbus_connection_get_outgoing_size.restype = ct.c_long
dbus.dbus_connection_get_outgoing_size.argtypes = (ct.c_void_p,)
dbus.dbus_connection_get_outgoing_unix_fds.restype = ct.c_long
dbus.dbus_connection_get_outgoing_unix_fds.argtypes = (ct.c_void_p,)

dbus.dbus_connection_register_object_path.restype = DBUS.bool_t
dbus.dbus_connection_register_object_path.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ObjectPathVTablePtr, ct.c_void_p)
dbus.dbus_connection_try_register_object_path.restype = DBUS.bool_t
dbus.dbus_connection_try_register_object_path.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ObjectPathVTablePtr, ct.c_void_p, DBUS.ErrorPtr)
dbus.dbus_connection_register_fallback.restype = DBUS.bool_t
dbus.dbus_connection_register_fallback.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ObjectPathVTablePtr, ct.c_void_p)
dbus.dbus_connection_try_register_fallback.restype = DBUS.bool_t
dbus.dbus_connection_try_register_fallback.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ObjectPathVTablePtr, ct.c_void_p, DBUS.ErrorPtr)
dbus.dbus_connection_get_object_path_data.restype = DBUS.bool_t
dbus.dbus_connection_get_object_path_data.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_void_p)
dbus.dbus_connection_list_registered.restype = DBUS.bool_t
dbus.dbus_connection_list_registered.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_void_p)
dbus.dbus_connection_get_unix_fd.restype = DBUS.bool_t
dbus.dbus_connection_get_unix_fd.argtypes = (ct.c_void_p, ct.POINTER(ct.c_int))
dbus.dbus_connection_get_socket.restype = DBUS.bool_t
dbus.dbus_connection_get_socket.argtypes = (ct.c_void_p, ct.POINTER(ct.c_int))
dbus.dbus_connection_unregister_object_path.restype = DBUS.bool_t
dbus.dbus_connection_unregister_object_path.argtypes = (ct.c_void_p, ct.c_char_p)

dbus.dbus_watch_get_unix_fd.restype = ct.c_int
dbus.dbus_watch_get_unix_fd.argtypes = (ct.c_void_p,)
dbus.dbus_watch_get_socket.restype = ct.c_int
dbus.dbus_watch_get_socket.argtypes = (ct.c_void_p,)
dbus.dbus_watch_get_flags.restype = ct.c_uint
dbus.dbus_watch_get_flags.argtypes = (ct.c_void_p,)
dbus.dbus_watch_get_data.restype = ct.c_void_p
dbus.dbus_watch_get_data.argtypes = (ct.c_void_p,)
dbus.dbus_watch_set_data.restype = None
dbus.dbus_watch_set_data.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_watch_handle.restype = DBUS.bool_t
dbus.dbus_watch_handle.argtypes = (ct.c_void_p, ct.c_uint)
dbus.dbus_watch_get_enabled.restype = DBUS.bool_t
dbus.dbus_watch_get_enabled.argtypes = (ct.c_void_p,)

dbus.dbus_timeout_get_interval.restype = ct.c_int
dbus.dbus_timeout_get_interval.argtypes = (ct.c_void_p,)
dbus.dbus_timeout_get_data.restype = ct.c_void_p
dbus.dbus_timeout_get_data.argtypes = (ct.c_void_p,)
dbus.dbus_timeout_set_data.restype = None
dbus.dbus_timeout_set_data.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_timeout_handle.restype = DBUS.bool_t
dbus.dbus_timeout_handle.argtypes = (ct.c_void_p,)
dbus.dbus_timeout_get_enabled.restype = DBUS.bool_t
dbus.dbus_timeout_get_enabled.argtypes = (ct.c_void_p,)

# from dbus-bus.h:
dbus.dbus_bus_get.restype = ct.c_void_p
dbus.dbus_bus_get.argtypes = (ct.c_uint, DBUS.ErrorPtr)
dbus.dbus_bus_get_private.restype = ct.c_void_p
dbus.dbus_bus_get_private.argtypes = (ct.c_uint, DBUS.ErrorPtr)
dbus.dbus_bus_register.restype = DBUS.bool_t
dbus.dbus_bus_register.argtypes = (ct.c_void_p, DBUS.ErrorPtr)
dbus.dbus_bus_set_unique_name.restype = DBUS.bool_t
dbus.dbus_bus_set_unique_name.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_bus_get_unique_name.restype = ct.c_char_p
dbus.dbus_bus_get_unique_name.argtypes = (ct.c_void_p,)
dbus.dbus_bus_get_unix_user.restype = ct.c_ulong
dbus.dbus_bus_get_unix_user.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_bus_get_id.restype = ct.c_void_p
dbus.dbus_bus_get_id.argtypes = (ct.c_void_p, DBUS.ErrorPtr)
dbus.dbus_bus_request_name.restype = ct.c_int
dbus.dbus_bus_request_name.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_uint, DBUS.ErrorPtr)
dbus.dbus_bus_release_name.restype = ct.c_int
dbus.dbus_bus_release_name.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_bus_name_has_owner.restype = DBUS.bool_t
dbus.dbus_bus_name_has_owner.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_bus_start_service_by_name.restype = DBUS.bool_t
dbus.dbus_bus_start_service_by_name.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_uint, ct.POINTER(ct.c_uint), DBUS.ErrorPtr)
dbus.dbus_bus_add_match.restype = None
dbus.dbus_bus_add_match.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_bus_remove_match.restype = None
dbus.dbus_bus_remove_match.argtypes = (ct.c_void_p, ct.c_char_p, DBUS.ErrorPtr)

dbus.dbus_error_init.restype = None
dbus.dbus_error_init.argtypes = (DBUS.ErrorPtr,)
dbus.dbus_error_free.restype = None
dbus.dbus_error_free.argtypes = (DBUS.ErrorPtr,)
dbus.dbus_move_error.restype = None
dbus.dbus_move_error.argtypes = (DBUS.ErrorPtr, DBUS.ErrorPtr)
dbus.dbus_error_has_name.restype = DBUS.bool_t
dbus.dbus_error_has_name.argtypes = (DBUS.ErrorPtr, ct.c_char_p)
dbus.dbus_error_is_set.restype = DBUS.bool_t
dbus.dbus_error_is_set.argtypes = (DBUS.ErrorPtr,)
dbus.dbus_set_error.restype = None
dbus.dbus_set_error.argtypes = (DBUS.ErrorPtr, ct.c_char_p, ct.c_char_p, ct.c_char_p)
  # note I can’t handle varargs

# from dbus-pending-call.h:
dbus.dbus_pending_call_ref.restype = ct.c_void_p
dbus.dbus_pending_call_ref.argtypes = (ct.c_void_p,)
dbus.dbus_pending_call_unref.restype = None
dbus.dbus_pending_call_unref.argtypes = (ct.c_void_p,)
dbus.dbus_pending_call_set_notify.restype = DBUS.bool_t
dbus.dbus_pending_call_set_notify.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_pending_call_cancel.restype = None
dbus.dbus_pending_call_cancel.argtypes = (ct.c_void_p,)
dbus.dbus_pending_call_get_completed.restype = DBUS.bool_t
dbus.dbus_pending_call_get_completed.argtypes = (ct.c_void_p,)
dbus.dbus_pending_call_steal_reply.restype = ct.c_void_p
dbus.dbus_pending_call_steal_reply.argtypes = (ct.c_void_p,)
dbus.dbus_pending_call_block.restype = None
dbus.dbus_pending_call_block.argtypes = (ct.c_void_p,)
dbus.dbus_pending_call_allocate_data_slot.restype = DBUS.bool_t
dbus.dbus_pending_call_allocate_data_slot.argtypes = (ct.POINTER(ct.c_int),)
dbus.dbus_pending_call_free_data_slot.restype = None
dbus.dbus_pending_call_free_data_slot.argtypes = (ct.c_int,)
dbus.dbus_pending_call_set_data.restype = DBUS.bool_t
dbus.dbus_pending_call_set_data.argtypes = (ct.c_void_p, ct.c_int, ct.c_void_p, ct.c_void_p)
dbus.dbus_pending_call_get_data.restype = ct.c_void_p
dbus.dbus_pending_call_get_data.argtypes = (ct.c_void_p, ct.c_int)

# from dbus-message.h:
dbus.dbus_message_new.restype = ct.c_void_p
dbus.dbus_message_new.argtypes = (ct.c_int,)
dbus.dbus_message_new_method_call.restype = ct.c_void_p
dbus.dbus_message_new_method_call.argtypes = (ct.c_char_p, ct.c_char_p, ct.c_char_p, ct.c_char_p)
dbus.dbus_message_new_method_return.restype = ct.c_void_p
dbus.dbus_message_new_method_return.argtypes = (ct.c_void_p,)
dbus.dbus_message_new_signal.restype = ct.c_void_p
dbus.dbus_message_new_signal.argtypes = (ct.c_char_p, ct.c_char_p, ct.c_char_p)
dbus.dbus_message_new_error.restype = ct.c_void_p
dbus.dbus_message_new_error.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_char_p)
dbus.dbus_message_new_error_printf.restype = ct.c_void_p
dbus.dbus_message_new_error_printf.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_char_p, ct.c_char_p)
  # note I can’t handle varargs
dbus.dbus_message_copy.restype = ct.c_void_p
dbus.dbus_message_copy.argtypes = (ct.c_void_p,)
dbus.dbus_message_ref.restype = ct.c_void_p
dbus.dbus_message_ref.argtypes = (ct.c_void_p,)
dbus.dbus_message_unref.restype = None
dbus.dbus_message_unref.argtypes = (ct.c_void_p,)
dbus.dbus_message_get_type.restype = ct.c_int
dbus.dbus_message_get_type.argtypes = (ct.c_void_p,)
dbus.dbus_message_set_path.restype = DBUS.bool_t
dbus.dbus_message_set_path.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_get_path.restype = ct.c_char_p
dbus.dbus_message_get_path.argtypes = (ct.c_void_p,)
dbus.dbus_message_has_path.restype = DBUS.bool_t
dbus.dbus_message_has_path.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_set_interface.restype = DBUS.bool_t
dbus.dbus_message_set_interface.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_get_interface.restype = ct.c_char_p
dbus.dbus_message_get_interface.argtypes = (ct.c_void_p,)
dbus.dbus_message_has_interface.restype = DBUS.bool_t
dbus.dbus_message_has_interface.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_set_member.restype = DBUS.bool_t
dbus.dbus_message_set_member.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_get_member.restype = ct.c_char_p
dbus.dbus_message_get_member.argtypes = (ct.c_void_p,)
dbus.dbus_message_has_member.restype = DBUS.bool_t
dbus.dbus_message_has_member.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_set_error_name.restype = DBUS.bool_t
dbus.dbus_message_set_error_name.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_get_error_name.restype = ct.c_char_p
dbus.dbus_message_get_error_name.argtypes = (ct.c_void_p,)
dbus.dbus_message_set_destination.restype = DBUS.bool_t
dbus.dbus_message_set_destination.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_get_destination.restype = ct.c_char_p
dbus.dbus_message_get_destination.argtypes = (ct.c_void_p,)
dbus.dbus_message_set_sender.restype = DBUS.bool_t
dbus.dbus_message_set_sender.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_get_sender.restype = ct.c_char_p
dbus.dbus_message_get_sender.argtypes = (ct.c_void_p,)
dbus.dbus_message_get_signature.restype = ct.c_char_p
dbus.dbus_message_get_signature.argtypes = (ct.c_void_p,)
dbus.dbus_message_set_no_reply.restype = None
dbus.dbus_message_set_no_reply.argtypes = (ct.c_void_p, DBUS.bool_t)
dbus.dbus_message_get_no_reply.restype = DBUS.bool_t
dbus.dbus_message_get_no_reply.argtypes = (ct.c_void_p,)
dbus.dbus_message_is_method_call.restype = DBUS.bool_t
dbus.dbus_message_is_method_call.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_char_p)
dbus.dbus_message_is_signal.restype = DBUS.bool_t
dbus.dbus_message_is_signal.argtypes = (ct.c_void_p, ct.c_char_p, ct.c_char_p)
dbus.dbus_message_is_error.restype = DBUS.bool_t
dbus.dbus_message_is_error.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_has_destination.restype = DBUS.bool_t
dbus.dbus_message_has_destination.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_has_sender.restype = DBUS.bool_t
dbus.dbus_message_has_sender.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_has_signature.restype = DBUS.bool_t
dbus.dbus_message_has_signature.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_message_get_serial.restype = ct.c_uint
dbus.dbus_message_get_serial.argtypes = (ct.c_void_p,)
dbus.dbus_message_set_serial.restype = None
dbus.dbus_message_set_serial.argtypes = (ct.c_void_p, ct.c_uint)
dbus.dbus_message_set_reply_serial.restype = DBUS.bool_t
dbus.dbus_message_set_reply_serial.argtypes = (ct.c_void_p, ct.c_uint)
dbus.dbus_message_get_reply_serial.restype = ct.c_uint
dbus.dbus_message_get_reply_serial.argtypes = (ct.c_void_p,)
dbus.dbus_message_set_auto_start.restype = None
dbus.dbus_message_set_auto_start.argtypes = (ct.c_void_p, DBUS.bool_t)
dbus.dbus_message_get_auto_start.restype = DBUS.bool_t
dbus.dbus_message_get_auto_start.argtypes = (ct.c_void_p,)
dbus.dbus_message_get_path_decomposed.restype = DBUS.bool_t
dbus.dbus_message_get_path_decomposed.argtypes = (ct.c_void_p, ct.c_void_p)
dbus.dbus_message_append_args.restype = DBUS.bool_t
dbus.dbus_message_append_args.argtypes = (ct.c_void_p, ct.c_int, ct.c_void_p, ct.c_int)
  # note I can’t handle varargs
# probably cannot make use of dbus.dbus_message_append_args_valist
dbus.dbus_message_get_args.restype = DBUS.bool_t
dbus.dbus_message_get_args.argtypes = (ct.c_void_p, DBUS.ErrorPtr, ct.c_int, ct.c_void_p, ct.c_int)
  # note I can’t handle varargs
# probably cannot make use of dbus.dbus_message_get_args_valist
dbus.dbus_message_contains_unix_fds.restype = DBUS.bool_t
dbus.dbus_message_contains_unix_fds.argtypes = (ct.c_void_p,)
dbus.dbus_message_iter_init.restype = DBUS.bool_t
dbus.dbus_message_iter_init.argtypes = (ct.c_void_p, DBUS.MessageIterPtr)
dbus.dbus_message_iter_has_next.restype = DBUS.bool_t
dbus.dbus_message_iter_has_next.argtypes = (DBUS.MessageIterPtr,)
dbus.dbus_message_iter_next.restype = DBUS.bool_t
dbus.dbus_message_iter_next.argtypes = (DBUS.MessageIterPtr,)
dbus.dbus_message_iter_get_signature.restype = ct.c_void_p
dbus.dbus_message_iter_next.argtypes = (DBUS.MessageIterPtr,)
dbus.dbus_message_iter_get_signature.restype = ct.c_void_p
dbus.dbus_message_iter_get_signature.argtypes = (DBUS.MessageIterPtr,)
dbus.dbus_message_iter_get_arg_type.restype = ct.c_int
dbus.dbus_message_iter_get_arg_type.argtypes = (DBUS.MessageIterPtr,)
dbus.dbus_message_iter_get_element_type.restype = ct.c_int
dbus.dbus_message_iter_get_element_type.argtypes = (DBUS.MessageIterPtr,)
dbus.dbus_message_iter_recurse.restype = None
dbus.dbus_message_iter_recurse.argtypes = (DBUS.MessageIterPtr, DBUS.MessageIterPtr)
dbus.dbus_message_iter_get_basic.restype = None
dbus.dbus_message_iter_get_basic.argtypes = (DBUS.MessageIterPtr, ct.c_void_p)
if hasattr(dbus, "dbus_message_iter_get_element_count") :
    dbus.dbus_message_iter_get_element_count.restype = ct.c_int
    dbus.dbus_message_iter_get_element_count.argtypes = (DBUS.MessageIterPtr,)
#end if
# dbus_message_iter_get_array_len deprecated
dbus.dbus_message_iter_get_fixed_array.restype = None
dbus.dbus_message_iter_get_fixed_array.argtypes = (DBUS.MessageIterPtr, ct.c_void_p, ct.POINTER(ct.c_int))
dbus.dbus_message_iter_init_append.restype = None
dbus.dbus_message_iter_init_append.argtypes = (ct.c_void_p, DBUS.MessageIterPtr)
dbus.dbus_message_iter_append_basic.restype = DBUS.bool_t
dbus.dbus_message_iter_append_basic.argtypes = (DBUS.MessageIterPtr, ct.c_int, ct.c_void_p)
dbus.dbus_message_iter_append_fixed_array.restype = DBUS.bool_t
dbus.dbus_message_iter_append_fixed_array.argtypes = (DBUS.MessageIterPtr, ct.c_int, ct.c_void_p, ct.c_int)
dbus.dbus_message_iter_open_container.restype = DBUS.bool_t
dbus.dbus_message_iter_open_container.argtypes = (DBUS.MessageIterPtr, ct.c_int, ct.c_char_p, DBUS.MessageIterPtr)
dbus.dbus_message_iter_close_container.restype = DBUS.bool_t
dbus.dbus_message_iter_close_container.argtypes = (DBUS.MessageIterPtr, DBUS.MessageIterPtr)
dbus.dbus_message_iter_abandon_container.restype = None
dbus.dbus_message_iter_abandon_container.argtypes = (DBUS.MessageIterPtr, DBUS.MessageIterPtr)
dbus.dbus_message_lock.restype = None
dbus.dbus_message_lock.argtypes = (DBUS.MessageIterPtr,)
dbus.dbus_set_error_from_message.restype = DBUS.bool_t
dbus.dbus_set_error_from_message.argtypes = (DBUS.ErrorPtr, ct.c_void_p)
dbus.dbus_message_allocate_data_slot.restype = DBUS.bool_t
dbus.dbus_message_allocate_data_slot.argtypes = (ct.POINTER(ct.c_int),)
dbus.dbus_message_free_data_slot.restype = None
dbus.dbus_message_free_data_slot.argtypes = (ct.POINTER(ct.c_int),)
dbus.dbus_message_set_data.restype = DBUS.bool_t
dbus.dbus_message_set_data.argtypes = (ct.c_void_p, ct.c_int, ct.c_void_p, ct.c_void_p)
dbus.dbus_message_get_data.restype = ct.c_void_p
dbus.dbus_message_get_data.argtypes = (ct.c_void_p, ct.c_int)
dbus.dbus_message_type_from_string.restype = ct.c_int
dbus.dbus_message_type_from_string.argtypes = (ct.c_char_p,)
dbus.dbus_message_type_to_string.restype = ct.c_char_p
dbus.dbus_message_type_to_string.argtypes = (ct.c_int,)
dbus.dbus_message_marshal.restype = DBUS.bool_t
dbus.dbus_message_marshal.argtypes = (ct.c_void_p, ct.c_void_p, ct.POINTER(ct.c_int))
dbus.dbus_message_demarshal.restype = ct.c_void_p
dbus.dbus_message_demarshal.argtypes = (ct.c_void_p, ct.c_int, DBUS.ErrorPtr)
dbus.dbus_message_demarshal_bytes_needed.restype = ct.c_int
dbus.dbus_message_demarshal_bytes_needed.argtypes = (ct.c_void_p, ct.c_int)
if hasattr(dbus, "dbus_message_set_allow_interactive_authorization") :
    dbus.dbus_message_set_allow_interactive_authorization.restype = None
    dbus.dbus_message_set_allow_interactive_authorization.argtypes = (ct.c_void_p, DBUS.bool_t)
#end if
if hasattr(dbus, "dbus_message_get_allow_interactive_authorization") :
    dbus.dbus_message_get_allow_interactive_authorization.restype = DBUS.bool_t
    dbus.dbus_message_get_allow_interactive_authorization.argtypes = (ct.c_void_p,)
#end if

# from dbus-memory.h:
dbus.dbus_malloc.restype = ct.c_void_p
dbus.dbus_malloc.argtypes = (ct.c_size_t,)
dbus.dbus_malloc0.restype = ct.c_void_p
dbus.dbus_malloc0.argtypes = (ct.c_size_t,)
dbus.dbus_realloc.restype = ct.c_void_p
dbus.dbus_realloc.argtypes = (ct.c_void_p, ct.c_size_t)
dbus.dbus_free.restype = None
dbus.dbus_free.argtypes = (ct.c_void_p,)
dbus.dbus_free_string_array.restype = None
dbus.dbus_free_string_array.argtypes = (ct.c_void_p,)

# from dbus-misc.h:
dbus.dbus_get_local_machine_id.restype = ct.c_void_p
dbus.dbus_get_local_machine_id.argtypes = ()
dbus.dbus_get_version.restype = None
dbus.dbus_get_version.argtypes = (ct.POINTER(ct.c_int), ct.POINTER(ct.c_int), ct.POINTER(ct.c_int))
dbus.dbus_setenv.restype = DBUS.bool_t
dbus.dbus_setenv.argtypes = (ct.c_char_p, ct.c_char_p)

# from dbus-address.h:
dbus.dbus_parse_address.restype = DBUS.bool_t
dbus.dbus_parse_address.argtypes = (ct.c_char_p, ct.c_void_p, ct.POINTER(ct.c_int), DBUS.ErrorPtr)
dbus.dbus_address_entry_get_value.restype = ct.c_char_p
dbus.dbus_address_entry_get_value.argtypes = (ct.c_void_p, ct.c_char_p)
dbus.dbus_address_entry_get_method.restype = ct.c_char_p
dbus.dbus_address_entry_get_method.argtypes = (ct.c_void_p,)
dbus.dbus_address_entries_free.restype = None
dbus.dbus_address_entries_free.argtypes = (ct.c_void_p,)
dbus.dbus_address_escape_value.restype = ct.c_void_p
dbus.dbus_address_escape_value.argtypes = (ct.c_char_p,)
dbus.dbus_address_unescape_value.restype = ct.c_void_p
dbus.dbus_address_unescape_value.argtypes = (ct.c_char_p, DBUS.ErrorPtr)

# from dbus-signature.h:
dbus.dbus_signature_iter_init.restype = None
dbus.dbus_signature_iter_init.argtypes = (DBUS.SignatureIterPtr, ct.c_char_p)
dbus.dbus_signature_iter_get_current_type.restype = ct.c_int
dbus.dbus_signature_iter_get_current_type.argtypes = (DBUS.SignatureIterPtr,)
dbus.dbus_signature_iter_get_signature.restype = ct.c_void_p
dbus.dbus_signature_iter_get_signature.argtypes = (DBUS.SignatureIterPtr,)
dbus.dbus_signature_iter_get_element_type.restype = ct.c_int
dbus.dbus_signature_iter_get_element_type.argtypes = (DBUS.SignatureIterPtr,)
dbus.dbus_signature_iter_next.restype = DBUS.bool_t
dbus.dbus_signature_iter_next.argtypes = (DBUS.SignatureIterPtr,)
dbus.dbus_signature_iter_recurse.restype = None
dbus.dbus_signature_iter_recurse.argtypes = (DBUS.SignatureIterPtr, DBUS.SignatureIterPtr)
dbus.dbus_signature_validate.restype = DBUS.bool_t
dbus.dbus_signature_validate.argtypes = (ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_signature_validate_single.restype = DBUS.bool_t
dbus.dbus_signature_validate_single.argtypes = (ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_type_is_valid.restype = DBUS.bool_t
dbus.dbus_type_is_valid.argtypes = (ct.c_int,)
dbus.dbus_type_is_basic.restype = DBUS.bool_t
dbus.dbus_type_is_basic.argtypes = (ct.c_int,)
dbus.dbus_type_is_container.restype = DBUS.bool_t
dbus.dbus_type_is_container.argtypes = (ct.c_int,)
dbus.dbus_type_is_fixed.restype = DBUS.bool_t
dbus.dbus_type_is_fixed.argtypes = (ct.c_int,)

# from dbus-syntax.h:
dbus.dbus_validate_path.restype = DBUS.bool_t
dbus.dbus_validate_path.argtypes = (ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_validate_interface.restype = DBUS.bool_t
dbus.dbus_validate_interface.argtypes = (ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_validate_member.restype = DBUS.bool_t
dbus.dbus_validate_member.argtypes = (ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_validate_error_name.restype = DBUS.bool_t
dbus.dbus_validate_error_name.argtypes = (ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_validate_bus_name.restype = DBUS.bool_t
dbus.dbus_validate_bus_name.argtypes = (ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_validate_utf8.restype = DBUS.bool_t
dbus.dbus_validate_utf8.argtypes = (ct.c_char_p, DBUS.ErrorPtr)

# from dbus-server.h:
dbus.dbus_server_listen.restype = ct.c_void_p
dbus.dbus_server_listen.argtypes = (ct.c_char_p, DBUS.ErrorPtr)
dbus.dbus_server_ref.restype = ct.c_void_p
dbus.dbus_server_ref.argtypes = (ct.c_void_p,)
dbus.dbus_server_unref.restype = ct.c_void_p
dbus.dbus_server_unref.argtypes = (ct.c_void_p,)
dbus.dbus_server_disconnect.restype = None
dbus.dbus_server_disconnect.argtypes = (ct.c_void_p,)
dbus.dbus_server_get_is_connected.restype = DBUS.bool_t
dbus.dbus_server_get_is_connected.argtypes = (ct.c_void_p,)
dbus.dbus_server_get_address.restype = ct.c_void_p
dbus.dbus_server_get_address.argtypes = (ct.c_void_p,)
dbus.dbus_server_get_id.restype = ct.c_void_p
dbus.dbus_server_get_id.argtypes = (ct.c_void_p,)
dbus.dbus_server_set_new_connection_function.restype = None
dbus.dbus_server_set_new_connection_function.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_server_set_watch_functions.restype = DBUS.bool_t
dbus.dbus_server_set_watch_functions.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_server_set_timeout_functions.restype = DBUS.bool_t
dbus.dbus_server_set_timeout_functions.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
dbus.dbus_server_set_auth_mechanisms.restype = DBUS.bool_t
dbus.dbus_server_set_auth_mechanisms.argtypes = (ct.c_void_p, ct.c_void_p)
dbus.dbus_server_allocate_data_slot.restype = DBUS.bool_t
dbus.dbus_server_allocate_data_slot.argtypes = (ct.POINTER(ct.c_int),)
dbus.dbus_server_free_data_slot.restype = DBUS.bool_t
dbus.dbus_server_free_data_slot.argtypes = (ct.POINTER(ct.c_int),)
dbus.dbus_server_set_data.restype = DBUS.bool_t
dbus.dbus_server_set_data.argtypes = (ct.c_void_p, ct.c_int, ct.c_void_p, ct.c_void_p)
dbus.dbus_server_set_data.restype = ct.c_void_p
dbus.dbus_server_set_data.argtypes = (ct.c_void_p, ct.c_int)

# TODO dbus-threads.h <https://dbus.freedesktop.org/doc/api/html/group__DBusThreads.html>
# Seems like the only call worth making is dbus_threads_init_default.

#+
# High-level stuff follows
#-

class DBusError(Exception) :
    "for raising an exception that reports a D-Bus error name and accompanying message."

    __slots__ = ("name", "message")

    def __init__(self, name, message) :
        self.args = ("%s -- %s" % (name, message),)
        self.name = name
        self.message = message
    #end __init__

#end DBusError

class CallFailed(Exception) :
    "used internally for reporting general failure from calling a libdbus routine."

    __slots__ = ("funcname",)

    def __init__(self, funcname) :
        self.args = ("%s failed" % funcname,)
        self.funcname = funcname
    #end __init__

#end CallFailed

class _Abort(Exception) :
    pass
#end _Abort

class TaskKeeper :
    "Base class for classes that need to call EventLoop.create_task() to" \
    " schedule caller-created coroutines for execution. asyncio only keeps" \
    " weak references to Task objects when they are not being scheduled," \
    " so to keep them from disappearing unexpectedly, I maintain a list of" \
    " strong references here, and clean them out as they end execution."

    __slots__ = ("__weakref__", "loop", "_cur_tasks")

    def _init(self) :
        # avoid __init__ so I don't get passed spurious args
        self.loop = None
        self._cur_tasks = set()
    #end _init

    def create_task(self, coro) :
        assert self.loop != None, "no event loop to attach coroutine to"
        task = self.loop.create_task(coro)
        task.add_done_callback(functools.partial(self._reaper, weak_ref(self)))
        self._cur_tasks.add(task)
    #end create_task

    @staticmethod
    def _reaper(self, task) :
        self = self() # avoid reference circularity
        self._cur_tasks.remove(task)
    #end _reaper

    if "loop" in asyncio.wait.__kwdefaults__ :

        def wait(self, futures, *, timeout = None, return_when = asyncio.ALL_COMPLETED) :
            "wrapper around asyncio.wait for compatibility with pre-Python-3.7."
            return \
                asyncio.wait(futures, loop = self.loop, timeout = timeout, return_when = return_when)
                  # No default loop in pre-3.7.
        #end wait

    else :

        wait = staticmethod(asyncio.wait)
          # no need to pass loop arg in ≥ 3.7, removed in ≥ 3.10.

    #end if

#end TaskKeeper

# Misc: <https://dbus.freedesktop.org/doc/api/html/group__DBusMisc.html>

def get_local_machine_id() :
    "returns a systemwide unique ID that is supposed to remain constant at least" \
    " until the next reboot. Two processes seeing the same value for this can assume" \
    " they are on the same machine."
    c_result = dbus.dbus_get_local_machine_id()
    if c_result == None :
        raise CallFailed("dbus_get_local_machine_id")
    #end if
    result = ct.cast(c_result, ct.c_char_p).value.decode()
    dbus.dbus_free(c_result)
    return \
        result
#end get_local_machine_id

def get_version() :
    "returns the libdbus library version as a tuple of integers (major, minor, micro)."
    major = ct.c_int()
    minor = ct.c_int()
    micro = ct.c_int()
    dbus.dbus_get_version(ct.byref(major), ct.byref(minor), ct.byref(micro))
    return \
        (major.value, minor.value, micro.value)
#end get_version

def setenv(key, value) :
    key = key.encode()
    if value != None :
        value = value.encode()
    #end if
    if not dbus.dbus_setenv(key, value) :
        raise CallFailed("dbus_setenv")
    #end if
#end setenv

def unsetenv(key) :
    setenv(key, None)
#end unsetenv

class Watch :
    "wrapper around a DBusWatch object. Do not instantiate directly; they" \
    " are created and destroyed by libdbus.\n" \
    "\n" \
    "A Watch is the basic mechanism for plugging libdbus-created file descriptors" \
    " into your event loop. When created, they are passed to your add-watch callback" \
    " to manage; and conversely, when deleted, your remove-watch callback is notified." \
    " (These callbacks are ones you attach to Server and Connection objects.)\n" \
    "\n" \
    "Check the enabled property to decide if you need to pay attention to this Watch, and" \
    " look at the flags to see if you need to check for pending reads, or writes, or both." \
    " Call the handle() method with the appropriate flags when you see that reads or writes" \
    " are pending."
    # <https://dbus.freedesktop.org/doc/api/html/group__DBusWatch.html>

    __slots__ = ("__weakref__", "_dbobj",) # to forestall typos

    _instances = WeakValueDictionary()

    def __new__(celf, _dbobj) :
        self = celf._instances.get(_dbobj)
        if self == None :
            self = super().__new__(celf)
            self._dbobj = _dbobj
            celf._instances[_dbobj] = self
        #end if
        return \
            self
    #end __new__

    # no __del__ method -- no underlying dispose API call

    @property
    def unix_fd(self) :
        "the underlying file descriptor for this Watch."
        return \
            dbus.dbus_watch_get_unix_fd(self._dbobj)
    #end unix_fd

    def fileno(self) :
        "for use with Python’s “select” functions."
        return \
            self.unix_fd
    #end fileno

    @property
    def socket(self) :
        return \
            dbus.dbus_watch_get_socket(self._dbobj)
    #end socket

    @property
    def flags(self) :
        "returns WATCH_READABLE and/or WATCH_WRITABLE, indicating what to watch for."
        return \
            dbus.dbus_watch_get_flags(self._dbobj)
    #end flags

    # TODO: get/set data

    def handle(self, flags) :
        "tells libdbus that there is something to be read or written." \
        " flags are a combination of WATCH_xxx values."
        return \
            dbus.dbus_watch_handle(self._dbobj, flags) != 0
    #end handle

    @property
    def enabled(self) :
        "does libdbus want you to actually watch this Watch."
        return \
            dbus.dbus_watch_get_enabled(self._dbobj) != 0
    #end enabled

#end Watch

class Timeout :
    "wrapper around a DBusTimeout object. Do not instantiate directly; they" \
    " are created and destroyed by libdbus.\n" \
    "\n" \
    " A Timeout is the basic mechanism for plugging libdbus-created timeouts" \
    " into your event loop. When created, they are passed to your add-timeout" \
    " callback to manage; and conversely, when deleted, your remove-timeout" \
    " callback is notified. (These callbacks are ones you attach to Server and" \
    " Connection objects.)\n" \
    "\n" \
    "Check the enabled property to decide if you need to pay attention to this" \
    " Timeout. Call the handle() method when the timeout becomes due, as measured" \
    " from when it was initially created or most recently enabled, whichever" \
    " happened last."
    # <https://dbus.freedesktop.org/doc/api/html/group__DBusTimeout.html>

    __slots__ = ("__weakref__", "_dbobj",) # to forestall typos

    _instances = WeakValueDictionary()

    def __new__(celf, _dbobj) :
        self = celf._instances.get(_dbobj)
        if self == None :
            self = super().__new__(celf)
            self._dbobj = _dbobj
            celf._instances[_dbobj] = self
        #end if
        return \
            self
    #end __new__

    # no __del__ method -- no underlying dispose API call

    @property
    def interval(self) :
        "how long in float seconds until the timeout should fire."
        return \
            dbus.dbus_timeout_get_interval(self._dbobj) / 1000
    #end interval

    # TODO: get/set data

    def handle(self) :
        "tells libdbus the timeout has fired."
        return \
            dbus.dbus_timeout_handle(self._dbobj)
    #end handle

    @property
    def enabled(self) :
        "does libdbus want you to actually schedule this Timeout."
        return \
            dbus.dbus_timeout_get_enabled(self._dbobj) != 0
    #end enabled

#end Timeout

class ObjectPathVTable(TaskKeeper) :
    "wrapper around an ObjectPathVTable struct. You can instantiate directly, or call" \
    " the init method. An additional feature beyond the underlying libdbus capabilities" \
    " is the option to specify an asyncio event loop. If the message handler returns" \
    " a coroutine, then an asyncio task is created to run it, and a result of" \
    " DBUS.HANDLER_RESULT_HANDLED is returned on behalf of the message handler;" \
    " that way, the message function can do the minimum beyond some initial filtering of" \
    " the message, leaving the time-consuming part of the work to the coroutine."

    __slots__ = \
      (
        "_dbobj",
        # need to keep references to ctypes-wrapped functions
        # so they don't disappear prematurely:
        "_wrap_unregister_func",
        "_wrap_message_func",
      ) # to forestall typos

    def __init__(self, *, loop = None, unregister = None, message = None) :
        super().__init__()
        super()._init()
        self._dbobj = DBUS.ObjectPathVTable()
        self.loop = loop
        self._wrap_unregister_func = None
        self._wrap_message_func = None
        if unregister != None :
            self.set_unregister(unregister)
        #end if
        if message != None :
            self.set_message(message)
        #end if
    #end __init__

    @classmethod
    def init(celf, *, loop = None, unregister = None, message = None) :
        "for consistency with other classes that don’t want caller to instantiate directly."
        return \
            celf \
              (
                loop = loop,
                unregister = unregister,
                message = message,
              )
    #end init

    def set_unregister(self, unregister) :

        def wrap_unregister(c_conn, c_user_data) :
            conn = Connection(dbus.dbus_connection_ref(c_conn))
            unregister(conn, conn._user_data.get(c_user_data))
        #end wrap_unregister

    #begin set_unregister
        if unregister != None :
            self._wrap_unregister_func = DBUS.ObjectPathUnregisterFunction(wrap_unregister)
        else :
            self._wrap_unregister_func = None
        #end if
        self._dbobj.unregister_function = self._wrap_unregister_func
        return \
            self
    #end set_unregister

    def set_message(self, message) :

        w_self = weak_ref(self)

        def wrap_message(c_conn, c_message, c_user_data) :
            self = _wderef(w_self, "vtable")
            conn = Connection(dbus.dbus_connection_ref(c_conn))
            msg = Message(dbus.dbus_message_ref(c_message))
            user_data = conn._user_data.get(c_user_data)
            result = message(conn, msg, user_data)
            if asyncio.iscoroutine(result) :
                self.create_task(result)
                result = DBUS.HANDLER_RESULT_HANDLED
            #end if
            return \
                result
        #end wrap_message

    #begin set_message
        if message != None :
            self._wrap_message_func = DBUS.ObjectPathMessageFunction(wrap_message)
        else :
            self._wrap_message_func = None
        #end if
        self._dbobj.message_function = self._wrap_message_func
        return \
            self
    #end set_message

#end ObjectPathVTable

class _DummyError :
    # like an Error, but is never set and so will never raise.

    @property
    def is_set(self) :
        return \
            False
    #end is_set

    def raise_if_set(self) :
        pass
    #end raise_if_set

#end _DummyError

def _get_error(error) :
    # Common routine which processes an optional user-supplied Error
    # argument, and returns 2 Error-like objects: the first a real
    # Error object to be passed to the libdbus call, the second is
    # either the same Error object or a separate _DummyError object
    # on which to call raise_if_set() afterwards. The procedure for
    # using this is
    #
    #     error, my_error = _get_error(error)
    #     ... call libdbus routine, passing error._dbobj ...
    #     my_error.raise_if_set()
    #
    # If the user passes None for error, then an internal Error object
    # is created, and returned as both results. That way, if it is
    # filled in by the libdbus call, calling raise_if_set() will
    # automatically raise the exception.
    # But if the user passed their own Error object, then it is
    # returned as the first result, and a _DummyError as the second
    # result. This means the raise_if_set() call becomes a noop, and
    # it is up to the caller to check if their Error object was filled
    # in or not.
    if error != None and not isinstance(error, Error) :
        raise TypeError("error must be an Error")
    #end if
    if error != None :
        my_error = _DummyError()
    else :
        my_error = Error()
        error = my_error
    #end if
    return \
        error, my_error
#end _get_error

def _get_timeout(timeout) :
    # accepts a timeout in float seconds and converts it to integer milliseconds
    # as expected by libdbus. Special-cases DBUS.TIMEOUT_INFINITE and DBUS.TIMEOUT_USE_DEFAULT,
    # allowing these to be passed through unchanged.
    if not isinstance(timeout, int) or timeout not in (DBUS.TIMEOUT_INFINITE, DBUS.TIMEOUT_USE_DEFAULT) :
        timeout = round(timeout * 1000)
    #end if
    return \
        timeout
#end _get_timeout

def _loop_attach(self, loop, dispatch) :
    # attaches a Server or Connection object to a given asyncio event loop.
    # If loop is None, then the default asyncio loop is used. The actual loop
    # value is also stored as the loop attribute of the object.

    if loop == None :
        try :
            # if running within a task, current loop takes priority
            loop = get_running_loop()
        except RuntimeError :
            # not running within a task, use default loop
            loop = get_event_loop()
        #end try
    #end if

    watches = [] # do I need to keep track of Watch objects?
    timeouts = []

    def call_dispatch() :
        status = dispatch()
        if status == DBUS.DISPATCH_NEED_MEMORY :
            raise DBusError(DBUS.ERROR_NO_MEMORY, "not enough memory for connection dispatch")
        #end if
        if status == DBUS.DISPATCH_DATA_REMAINS :
            loop.call_soon(call_dispatch)
        #end if
    #end call_dispatch

    def add_remove_watch(watch, add) :

        def handle_watch_event(flags) :
            # seems I need to remove the watch and add it again to
            # avoid an endless stream of notifications that cause
            # excessive CPU usage -- asyncio bug?
            add_remove_watch(watch, False)
            watch.handle(flags)
            if watch.enabled :
                add_remove_watch(watch, True)
             #end if
            if dispatch != None :
                call_dispatch()
            #end if
        #end handle_watch_event

    #end add_remove_watch
        if DBUS.WATCH_READABLE & watch.flags != 0 :
            if add :
                loop.add_reader(watch, handle_watch_event, DBUS.WATCH_READABLE)
            else :
                loop.remove_reader(watch)
            #end if
        #end if
        if DBUS.WATCH_WRITABLE & watch.flags != 0 :
            if add :
                loop.add_writer(watch, handle_watch_event, DBUS.WATCH_WRITABLE)
            else :
                loop.remove_writer(watch)
            #end if
        #end if
    #end add_remove_watch

    def handle_add_watch(watch, data) :
        if watch not in watches :
            watches.append(watch)
            add_remove_watch(watch, True)
        #end if
        return \
            True
    #end handle_add_watch

    def handle_watch_toggled(watch, data) :
        add_remove_watch(watch, watch.enabled)
    #end handle_watch_toggled

    def handle_remove_watch(watch, data) :
        try :
            pos = watches.index(watch)
        except ValueError :
            pos = None
        #end try
        if pos != None :
            watches[pos : pos + 1] = []
            add_remove_watch(watch, False)
        #end if
    #end handle_remove_watch

    def handle_timeout(timeout) :
        if timeout["due"] != None and timeout["due"] <= loop.time() and timeout["timeout"].enabled :
            timeout["timeout"].handle()
        #end if
    #end handle_timeout

    def handle_add_timeout(timeout, data) :
        if not any(timeout == t["timeout"] for t in timeouts) :
            entry = \
                {
                    "timeout" : timeout,
                    "due" : (lambda : None, lambda : loop.time() + timeout.interval)[timeout.enabled](),
                }
            timeouts.append(entry)
            if timeout.enabled :
                loop.call_later(timeout.interval, handle_timeout, entry)
            #end if
        #end if
        return \
            True
    #end handle_add_timeout

    def handle_timeout_toggled(timeout, data) :
        # not sure what to do if a Timeout gets toggled from enabled to disabled
        # and then to enabled again; effectively I update the due time from
        # the time of re-enabling.
        search = iter(timeouts)
        while True :
            entry = next(search, None)
            if entry == None :
                break
            #end if
            if entry["timeout"] == timeout :
                if timeout.enabled :
                    entry["due"] = loop.time() + timeout.enterval
                    loop.call_later(timeout.interval, handle_timeout, entry)
                else :
                    entry["due"] = None
                #end if
                break
            #end if
        #end while
    #end handle_timeout_toggled

    def handle_remove_timeout(timeout, data) :
        new_timeouts = []
        for entry in timeouts :
            if entry["timeout"] == timeout :
                entry["due"] = None # in case already queued, avoid segfault in handle_timeout
            else :
                new_timeouts.append(entry)
            #end if
        #end for
        timeouts[:] = new_timeouts
    #end handle_remove_timeout

#begin _loop_attach
    self.set_watch_functions \
      (
        add_function = handle_add_watch,
        remove_function = handle_remove_watch,
        toggled_function = handle_watch_toggled,
        data = None
      )
    self.set_timeout_functions \
      (
        add_function = handle_add_timeout,
        remove_function = handle_remove_timeout,
        toggled_function = handle_timeout_toggled,
        data = None
      )
    self.loop = loop
    self = None # avoid circularity
#end _loop_attach

class _MatchActionEntry :
    __slots__ = ("rule", "actions")

    class _Action :
        __slots__ = ("func", "user_data")

        def __init__(self, func, user_data) :
            self.func = func
            self.user_data = user_data
        #end __init__

        def __eq__(a, b) :
            # needed to allow equality comparison of set entries
            return \
                (
                    a.func == b.func
                and
                    data_key(a.user_data) == data_key(b.user_data)
                )
        #end __eq__

        def __hash__(self) :
            return \
                hash((self.func, data_key(self.user_data)))
        #end __hash__

    #end _Action

    def __init__(self, rule) :
        self.rule = rule
        self.actions = set()
    #end __init__

#end _MatchActionEntry

@enum.unique
class STOP_ON(enum.Enum) :
    "set of conditions on which to raise StopAsyncIteration:\n" \
    "\n" \
    "    TIMEOUT - timeout has elapsed\n" \
    "    CLOSED  - server/connection has closed.\n" \
    "\n" \
    "Otherwise None will be returned on timeout, and the usual BrokenPipeError" \
    " exception will be raised when the connection is closed."
    TIMEOUT = 1
    CLOSED = 2
#end STOP_ON

class Connection(TaskKeeper) :
    "wrapper around a DBusConnection object. Do not instantiate directly; use the open" \
    " or bus_get methods."
    # <https://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html>

    __slots__ = \
      (
        "_dbobj",
        "_filters",
        "_match_actions",
        "_receive_queue",
        "_receive_queue_enabled",
        "_awaiting_receive",
        "_user_data",
        # need to keep references to ctypes-wrapped functions
        # so they don't disappear prematurely:
        "_object_paths",
        "_add_watch_function",
        "_remove_watch_function",
        "_toggled_watch_function",
        "_free_watch_data",
        "_add_timeout_function",
        "_remove_timeout_function",
        "_toggled_timeout_function",
        "_free_timeout_data",
        "_wakeup_main",
        "_free_wakeup_main_data",
        "_dispatch_status",
        "_free_dispatch_status_data",
        "_allow_unix_user",
        "_free_unix_user_data",
      ) # to forestall typos

    _instances = WeakValueDictionary()
    _shared_connections = [None, None]

    def __new__(celf, _dbobj) :
        self = celf._instances.get(_dbobj)
        if self == None :
            self = super().__new__(celf)
            super()._init(self)
            self._dbobj = _dbobj
            self._user_data = {}
            self._filters = {}
            self._match_actions = {}
            self._receive_queue = None
            self._receive_queue_enabled = set()
            self._awaiting_receive = []
            self._object_paths = {}
            celf._instances[_dbobj] = self
        else :
            dbus.dbus_connection_unref(self._dbobj)
              # lose extra reference created by caller
        #end if
        return \
            self
    #end __new__

    def __del__(self) :
        if self._dbobj != None :
            if self.loop != None :
                # remove via direct low-level libdbus calls
                dbus.dbus_connection_set_watch_functions(self._dbobj, None, None, None, None, None)
                dbus.dbus_connection_set_timeout_functions(self._dbobj, None, None, None, None, None)
                self.loop = None
            #end if
            # Any entries still in super(TaskKeeper, self)._cur_tasks will be lost
            # at this point. I leave it to asyncio to report them as destroyed
            # while still pending, and the caller to notice this as a program bug.
            dbus.dbus_connection_unref(self._dbobj)
            self._dbobj = None
        #end if
    #end __del__

    @classmethod
    def open(celf, address, private, error = None) :
        "opens a Connection to a specified address, separate from the" \
        " system or session buses."
        error, my_error = _get_error(error)
        result = (dbus.dbus_connection_open, dbus.dbus_connection_open_private)[private](address.encode(), error._dbobj)
        my_error.raise_if_set()
        if result != None :
            result = celf(result)
        #end if
        return \
            result
    #end open

    @classmethod
    async def open_async(celf, address, private, error = None, loop = None, timeout = DBUS.TIMEOUT_INFINITE) :
        "opens a Connection to a specified address, separate from the" \
        " system or session buses."
        # There is no nonblocking version of dbus_connection_open/dbus_connection_open_private,
        # so I invoke it in a separate thread.

        if loop == None :
            loop = get_running_loop()
        #end if
        error, my_error = _get_error(error)
        if timeout == DBUS.TIMEOUT_USE_DEFAULT :
            timeout = DBUSX.DEFAULT_TIMEOUT
        elif timeout == DBUS.TIMEOUT_INFINITE :
            timeout = None
        #end if
        try :
            result = await call_async \
              (
                func = (dbus.dbus_connection_open, dbus.dbus_connection_open_private)[private],
                funcargs = (address.encode(), error._dbobj),
                timeout = timeout,
                abort = dbus.dbus_connection_unref,
                loop = loop
              )
        except TimeoutError :
            result = None
            error.set(DBUS.ERROR_TIMEOUT, "connection did not open in time")
        #end try
        my_error.raise_if_set()
        if result != None :
            result = celf(result)
            result.attach_asyncio(loop)
        #end if
        return \
            result
    #end open_async

    def _flush_awaiting_receive(self) :
        if self._receive_queue != None :
            while len(self._awaiting_receive) != 0 :
                waiting = self._awaiting_receive.pop(0)
                waiting.set_exception(BrokenPipeError("async receives have been disabled"))
            #end while
        #end if
    #end _flush_awaiting_receive

    def close(self) :
        self._flush_awaiting_receive()
        dbus.dbus_connection_close(self._dbobj)
    #end close

    @property
    def is_connected(self) :
        return \
            dbus.dbus_connection_get_is_connected(self._dbobj) != 0
    #end is_connected

    @property
    def is_authenticated(self) :
        return \
            dbus.dbus_connection_get_is_authenticated(self._dbobj) != 0
    #end is_authenticated

    @property
    def is_anonymous(self) :
        return \
            dbus.dbus_connection_get_is_anonymous(self._dbobj) != 0
    #end is_anonymous

    @property
    def server_id(self) :
        "asks the server at the other end for its unique id."
        c_result = dbus.dbus_connection_get_server_id(self._dbobj)
        result = ct.cast(c_result, ct.c_char_p).value.decode()
        dbus.dbus_free(c_result)
        return \
            result
    #end server_id

    def can_send_type(self, type_code) :
        "can this Connection send values of the specified TYPE_XXX code." \
        " Mainly useful for checking if we can send TYPE_UNIX_FD values."
        return \
            dbus.dbus_connection_can_send_type(self._dbobj, type_code) != 0
    #end can_send_type

    def set_exit_on_disconnect(self, exit_on_disconnect) :
        dbus.dbus_connection_set_exit_on_disconnect(self._dbobj, exit_on_disconnect)
    #end set_exit_on_disconnect

    def preallocate_send(self) :
        result = dbus.dbus_connection_preallocate_send(self._dbobj)
        if result == None :
            raise CallFailed("dbus_connection_preallocate_send")
        #end if
        return \
            PreallocatedSend(result, self)
    #end preallocate_send

    def send_preallocated(self, preallocated, message) :
        if not isinstance(preallocated, PreallocatedSend) or not isinstance(message, Message) :
            raise TypeError("preallocated must be a PreallocatedSend and message must be a Message")
        #end if
        assert not preallocated._sent, "preallocated has already been sent"
        serial = ct.c_uint()
        dbus.dbus_connection_send_preallocated(self._dbobj, preallocated._dbobj, message._dbobj, ct.byref(serial))
        preallocated._sent = True
        return \
            serial.value
    #end send_preallocated

    def send(self, message) :
        "puts a message in the outgoing queue."
        if not isinstance(message, Message) :
            raise TypeError("message must be a Message")
        #end if
        serial = ct.c_uint()
        if not dbus.dbus_connection_send(self._dbobj, message._dbobj, ct.byref(serial)) :
            raise CallFailed("dbus_connection_send")
        #end if
        return \
            serial.value
    #end send

    def send_with_reply(self, message, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "puts a message in the outgoing queue and returns a PendingCall" \
        " that you can use to obtain the reply."
        if not isinstance(message, Message) :
            raise TypeError("message must be a Message")
        #end if
        pending_call = ct.c_void_p()
        if not dbus.dbus_connection_send_with_reply(self._dbobj, message._dbobj, ct.byref(pending_call), _get_timeout(timeout)) :
            raise CallFailed("dbus_connection_send_with_reply")
        #end if
        if pending_call.value != None :
            result = PendingCall(pending_call.value, self)
        else :
            result = None
        #end if
        return \
            result
    #end send_with_reply

    def send_with_reply_and_block(self, message, timeout = DBUS.TIMEOUT_USE_DEFAULT, error = None) :
        "sends a message, blocks the thread until the reply is available, and returns it."
        if not isinstance(message, Message) :
            raise TypeError("message must be a Message")
        #end if
        error, my_error = _get_error(error)
        reply = dbus.dbus_connection_send_with_reply_and_block(self._dbobj, message._dbobj, _get_timeout(timeout), error._dbobj)
        my_error.raise_if_set()
        if reply != None :
            result = Message(reply)
        else :
            result = None
        #end if
        return \
            result
    #end send_with_reply_and_block

    async def send_await_reply(self, message, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "queues a message, suspends the coroutine (letting the event loop do" \
        " other things) until the reply is available, and returns it."
        if not isinstance(message, Message) :
            raise TypeError("message must be a Message")
        #end if
        assert self.loop != None, "no event loop to attach coroutine to"
        pending_call = ct.c_void_p()
        if not dbus.dbus_connection_send_with_reply(self._dbobj, message._dbobj, ct.byref(pending_call), _get_timeout(timeout)) :
            raise CallFailed("dbus_connection_send_with_reply")
        #end if
        if pending_call.value != None :
            pending = PendingCall(pending_call.value, self)
        else :
            pending = None
        #end if
        reply = None # to begin with
        if pending != None :
            reply = await pending.await_reply()
        #end if
        return \
            reply
    #end send_await_reply

    def flush(self) :
        "makes sure all queued messages have been sent, blocking" \
        " the thread until this is done."
        dbus.dbus_connection_flush(self._dbobj)
    #end flush

    def read_write_dispatch(self, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "dispatches the first available message, if any. Otherwise blocks the" \
        " thread until it can read or write, and does so before returning. Returns" \
        " True as long as the Connection remains connected."
        return \
            dbus.dbus_connection_read_write_dispatch(self._dbobj, _get_timeout(timeout)) != 0
    #end read_write_dispatch

    def read_write(self, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "blocks the thread until something can be read or written on the Connection," \
        " and does so, returning True. If the Connection has been disconnected," \
        " immediately returns False."
        return \
            dbus.dbus_connection_read_write(self._dbobj, _get_timeout(timeout)) != 0
    #end read_write

    def borrow_message(self) :
        "tries to peek at the next available message waiting to be read, returning" \
        " None if these isn’t one. Call the Message’s return_borrowed() method" \
        " to return it to the queue, or steal_borrowed() to confirm that you have" \
        " read the message."
        msg = dbus.dbus_connection_borrow_message(self._dbobj)
        if msg != None :
            msg = Message(msg)
            msg._conn = self
            msg._borrowed = True
        #end if
        return \
            msg
    #end borrow_message

    # returning/stealing borrowed messages done with
    # Message.return_borrowed and Message.steal_borrowed

    def pop_message(self) :
        "returns the next available incoming Message, if any, otherwise returns None." \
        " Note this bypasses all message filtering/dispatching on this Connection."
        message = dbus.dbus_connection_pop_message(self._dbobj)
        if message != None :
            message = Message(message)
        #end if
        return \
            message
    #end pop_message

    @property
    def dispatch_status(self) :
        "checks the state of the incoming message queue; returns a DISPATCH_XXX code."
        return \
            dbus.dbus_connection_get_dispatch_status(self._dbobj)
    #end dispatch_status

    def dispatch(self) :
        "processes any available data, adding messages into the incoming" \
        " queue as appropriate. returns a DISPATCH_XXX code."
        return \
            dbus.dbus_connection_dispatch(self._dbobj)
    #end dispatch

    def set_watch_functions(self, add_function, remove_function, toggled_function, data, free_data = None) :
        "sets the callbacks for libdbus to use to notify you of Watch objects it wants" \
        " you to manage."

        def wrap_add_function(c_watch, _data) :
            return \
                add_function(Watch(c_watch), data)
        #end wrap_add_function

        def wrap_remove_function(c_watch, _data) :
            return \
                remove_function(Watch(c_watch), data)
        #end wrap_remove_function

        def wrap_toggled_function(c_watch, _data) :
            return \
                toggled_function(Watch(c_watch), data)
        #end wrap_toggled_function

        def wrap_free_data(_data) :
            free_data(data)
        #end wrap_free_data

    #begin set_watch_functions
        self._add_watch_function = DBUS.AddWatchFunction(wrap_add_function)
        self._remove_watch_function = DBUS.RemoveWatchFunction(wrap_remove_function)
        if toggled_function != None :
            self._toggled_watch_function = DBUS.WatchToggledFunction(wrap_toggled_function)
        else :
            self._toggled_watch_function = None
        #end if
        if free_data != None :
            self._free_watch_data = DBUS.FreeFunction(wrap_free_data)
        else :
            self._free_watch_data = None
        #end if
        if not dbus.dbus_connection_set_watch_functions(self._dbobj, self._add_watch_function, self._remove_watch_function, self._toggled_watch_function, None, self._free_watch_data) :
            raise CallFailed("dbus_connection_set_watch_functions")
        #end if
    #end set_watch_functions

    def set_timeout_functions(self, add_function, remove_function, toggled_function, data, free_data = None) :
        "sets the callbacks for libdbus to use to notify you of Timeout objects it wants" \
        " you to manage."

        def wrap_add_function(c_timeout, _data) :
            return \
                add_function(Timeout(c_timeout), data)
        #end wrap_add_function

        def wrap_remove_function(c_timeout, _data) :
            return \
                remove_function(Timeout(c_timeout), data)
        #end wrap_remove_function

        def wrap_toggled_function(c_timeout, _data) :
            return \
                toggled_function(Timeout(c_timeout), data)
        #end wrap_toggled_function

        def wrap_free_data(_data) :
            free_data(data)
        #end wrap_free_data

    #begin set_timeout_functions
        self._add_timeout_function = DBUS.AddTimeoutFunction(wrap_add_function)
        self._remove_timeout_function = DBUS.RemoveTimeoutFunction(wrap_remove_function)
        if toggled_function != None :
            self._toggled_timeout_function = DBUS.TimeoutToggledFunction(wrap_toggled_function)
        else :
            self._toggled_timeout_function = None
        #end if
        if free_data != None :
            self._free_timeout_data = DBUS.FreeFunction(wrap_free_data)
        else :
            self._free_timeout_data = None
        #end if
        if not dbus.dbus_connection_set_timeout_functions(self._dbobj, self._add_timeout_function, self._remove_timeout_function, self._toggled_timeout_function, None, self._free_timeout_data) :
            raise CallFailed("dbus_connection_set_timeout_functions")
        #end if
    #end set_timeout_functions

    def set_wakeup_main_function(self, wakeup_main, data, free_data = None) :
        "sets the callback to use for libdbus to notify you that something has" \
        " happened requiring processing on the Connection."

        def wrap_wakeup_main(_data) :
            wakeup_main(data)
        #end wrap_wakeup_main

        def wrap_free_data(_data) :
            free_data(data)
        #end wrap_free_data

    #begin set_wakeup_main_function
        if wakeup_main != None :
            self._wakeup_main = DBUS.WakeupMainFunction(wrap_wakeup_main)
        else :
            self._wakeup_main = None
        #end if
        if free_data != None :
            self._free_wakeup_main_data = DBUS.FreeFunction(wrap_free_data)
        else :
            self._free_wakeup_main_data = None
        #end if
        dbus.dbus_connection_set_wakeup_main_function(self._dbobj, self._wakeup_main, None, self._free_wakeup_main_data)
    #end set_wakeup_main_function

    def set_dispatch_status_function(self, function, data, free_data = None) :
        "sets the callback to use for libdbus to notify you of a change in the" \
        " dispatch status of the Connection."

        w_self = weak_ref(self)

        def wrap_dispatch_status(_conn, status, _data) :
            function(_wderef(w_self, "connection"), status, data)
        #end wrap_dispatch_status

        def wrap_free_data(_data) :
            free_data(data)
        #end wrap_free_data

    #begin set_dispatch_status_function
        self._dispatch_status = DBUS.DispatchStatusFunction(wrap_dispatch_status)
        if free_data != None :
            self._free_wakeup_main_data = DBUS.FreeFunction(wrap_free_data)
        else :
            self._free_wakeup_main_data = None
        #end if
        dbus.dbus_connection_set_dispatch_status_function(self._dbobj, self._dispatch_status, None, self._free_wakeup_main_data)
    #end set_dispatch_status_function

    @property
    def unix_fd(self) :
        c_fd = ct.c_int()
        if dbus.dbus_connection_get_unix_fd(self._dbobj, ct.byref(c_fd)) :
            result = c_fd.value
        else :
            result = None
        #end if
        return \
            result
    #end unix_fd

    def fileno(self) :
        "for use with Python’s “select” functions."
        return \
            self.unix_fd
    #end fileno

    @property
    def socket(self) :
        c_fd = ct.c_int()
        if dbus.dbus_connection_get_socket(self._dbobj, ct.byref(c_fd)) :
            result = c_fd.value
        else :
            result = None
        #end if
        return \
            result
    #end socket

    @property
    def unix_process_id(self) :
        c_pid = ct.c_ulong()
        if dbus.dbus_connection_get_unix_process_id(self._dbobj, ct.byref(c_pid)) :
            result = c_pid.value
        else :
            result = None
        #end if
        return \
            result
    #end unix_process_id

    @property
    def unix_user(self) :
        c_uid = ct.c_ulong()
        if dbus.dbus_connection_get_unix_user(self._dbobj, ct.byref(c_uid)) :
            result = c_uid.value
        else :
            result = None
        #end if
        return \
            result
    #end unix_user

    # TODO: get_adt

    def set_unix_user_function(self, allow_unix_user, data, free_data = None) :

        w_self = weak_ref(self)

        def wrap_allow_unix_user(c_conn, uid, c_data) :
            return \
                allow_unix_user(_wderef(w_self, "connection"), uid, data)
        #end wrap_allow_unix_user

        def wrap_free_data(_data) :
            free_data(data)
        #end wrap_free_data

    #begin set_unix_user_function
        if allow_unix_user != None :
            self._allow_unix_user = DBUS.AllowUnixUserFunction(wrap_allow_unix_user)
        else :
            self._allow_unix_user = None
        #end if
        if free_data != None :
            self._free_unix_user_data = DBUS.FreeFunction(wrap_free_data)
        else :
            self._free_unix_user_data = None
        #end if
        dbus.dbus_connection_set_unix_user_function(self._dbobj, self._allow_unix_user, None, self._free_unix_user_data)
    #end set_unix_user_function

    def set_allow_anonymous(self, allow) :
        dbus.dbus_connection_set_allow_anonymous(self._dbobj, allow)
    #end set_allow_anonymous

    def set_route_peer_messages(self, enable) :
        dbus.dbus_connection_set_route_peer_messages(self._dbobj, enable)
    #end set_route_peer_messages

    def add_filter(self, function, user_data, free_data = None) :
        "adds a filter callback that gets to look at all incoming messages" \
        " before they get to the dispatch system. The same function can be added" \
        " multiple times as long as the user_data is different."

        w_self = weak_ref(self)

        def wrap_function(c_conn, c_message, _data) :
            self = _wderef(w_self, "connection")
            message = Message(dbus.dbus_message_ref(c_message))
            result = function(self, message, user_data)
            if asyncio.iscoroutine(result) :
                self.create_task(result)
                result = DBUS.HANDLER_RESULT_HANDLED
            #end if
            return \
                result
        #end wrap_function

        def wrap_free_data(_data) :
            free_data(user_data)
        #end wrap_free_data

    #begin add_filter
        filter_key = (function, data_key(user_data))
        filter_value = \
            {
                "function" : DBUS.HandleMessageFunction(wrap_function),
                "free_data" : (lambda : None, lambda : DBUS.FreeFunction(wrap_free_data))[free_data != None](),
            }
        # pass user_data id because libdbus identifies filter entry by both function address and user data address
        if not dbus.dbus_connection_add_filter(self._dbobj, filter_value["function"], filter_key[1], filter_value["free_data"]) :
            raise CallFailed("dbus_connection_add_filter")
        #end if
        self._filters[filter_key] = filter_value
          # need to ensure wrapped functions don’t disappear prematurely
    #end add_filter

    def remove_filter(self, function, user_data) :
        "removes a message filter added by add_filter. The filter is identified" \
        " by both the function object and the user_data that was passed."
        filter_key = (function, data_key(user_data))
        if filter_key not in self._filters :
            raise KeyError("removing nonexistent Connection filter")
        #end if
        filter_value = self._filters[filter_key]
        # pass user_data id because libdbus identifies filter entry by both function address and user data address
        dbus.dbus_connection_remove_filter(self._dbobj, filter_value["function"], filter_key[1])
        del self._filters[filter_key]
    #end remove_filter

    def register_object_path(self, path, vtable, user_data, error = None) :
        "registers an ObjectPathVTable as a dispatch handler for a specified" \
        " path within your object hierarchy."
        if not isinstance(vtable, ObjectPathVTable) :
            raise TypeError("vtable must be an ObjectPathVTable")
        #end if
        self._object_paths[path] = {"vtable" : vtable, "user_data" : user_data} # ensure it doesn’t disappear prematurely
        error, my_error = _get_error(error)
        if user_data != None :
            c_user_data = id(user_data)
            self._user_data[c_user_data] = user_data
        else :
            c_user_data = None
        #end if
        dbus.dbus_connection_try_register_object_path(self._dbobj, path.encode(), vtable._dbobj, c_user_data, error._dbobj)
        my_error.raise_if_set()
    #end register_object_path

    def register_fallback(self, path, vtable, user_data, error = None) :
        "registers an ObjectPathVTable as a dispatch handler for an entire specified" \
        " subtree within your object hierarchy."
        if not isinstance(vtable, ObjectPathVTable) :
            raise TypeError("vtable must be an ObjectPathVTable")
        #end if
        self._object_paths[path] = {"vtable" : vtable, "user_data" : user_data} # ensure it doesn’t disappear prematurely
        error, my_error = _get_error(error)
        if user_data != None :
            c_user_data = id(user_data)
            self._user_data[c_user_data] = user_data
        else :
            c_user_data = None
        #end if
        dbus.dbus_connection_try_register_fallback(self._dbobj, path.encode(), vtable._dbobj, c_user_data, error._dbobj)
        my_error.raise_if_set()
    #end register_fallback

    def unregister_object_path(self, path) :
        "removes a previously-registered ObjectPathVTable handler at a specified" \
        " point (single object or entire subtree) within your object hierarchy."
        if path not in self._object_paths :
            raise KeyError("unregistering unregistered path")
        #end if
        if not dbus.dbus_connection_unregister_object_path(self._dbobj, path.encode()) :
            raise CallFailed("dbus_connection_unregister_object_path")
        #end if
        user_data = self._object_paths[path]["user_data"]
        c_user_data = id(user_data)
        nr_remaining_refs = sum(int(self._object_paths[p]["user_data"] == user_data) for p in self._object_paths if p != path)
        if nr_remaining_refs == 0 :
            try :
                del self._user_data[c_user_data]
            except KeyError :
                pass
            #end try
        #end if
        del self._object_paths[path]
    #end unregister_object_path

    def get_object_path_data(self, path) :
        "returns the user_data you passed when previously registering an ObjectPathVTable" \
        " that covers this path in your object hierarchy, or None if no suitable match" \
        " could be found."
        c_data_p = ct.c_void_p()
        if not dbus.dbus_connection_get_object_path_data(self._dbobj, path.encode(), ct.byref(c_data_p)) :
            raise CallFailed("dbus_connection_get_object_path_data")
        #end if
        return \
            self._user_data.get(c_data_p.value)
    #end get_object_path_data

    def list_registered(self, parent_path) :
        "lists all the object paths for which you have ObjectPathVTable handlers registered."
        child_entries = ct.POINTER(ct.c_char_p)()
        if not dbus.dbus_connection_list_registered(self._dbobj, parent_path.encode(), ct.byref(child_entries)) :
            raise CallFailed("dbus_connection_list_registered")
        #end if
        result = []
        i = 0
        while True :
            entry = child_entries[i]
            if entry == None :
                break
            result.append(entry.decode())
            i += 1
        #end while
        dbus.dbus_free_string_array(child_entries)
        return \
            result
    #end list_registered

    @staticmethod
    def _queue_received_message(self, message, _) :
        # message filter which queues messages as appropriate for receive_message_async.
        # Must be static so same function object can be passed to all add_filter/remove_filter
        # calls.
        queueit = message.type in self._receive_queue_enabled
        if queueit :
            self._receive_queue.append(message)
            while len(self._awaiting_receive) != 0 :
                # wake them all up, because I don’t know what message types
                # each might be waiting for
                waiting = self._awaiting_receive.pop(0)
                waiting.set_result(True) # result actually ignored
            #end while
        #end if
        return \
            (DBUS.HANDLER_RESULT_NOT_YET_HANDLED, DBUS.HANDLER_RESULT_HANDLED)[queueit]
    #end _queue_received_message

    def enable_receive_message(self, queue_types) :
        "enables/disables message types for reception via receive_message_async." \
        " queue_types is a set or sequence of DBUS.MESSAGE_TYPE_XXX values for" \
        " the types of messages to be put into the receive queue, or None to" \
        " disable all message types; this replaces queue_types passed to" \
        " any prior enable_receive_message_async call on this Connection."
        assert self.loop != None, "no event loop to attach coroutines to"
        enable = queue_types != None and len(queue_types) != 0
        if (
                enable
            and
                not all
                  (
                        m
                    in
                        (
                            DBUS.MESSAGE_TYPE_METHOD_CALL,
                            DBUS.MESSAGE_TYPE_METHOD_RETURN,
                            DBUS.MESSAGE_TYPE_ERROR,
                            DBUS.MESSAGE_TYPE_SIGNAL,
                        )
                    for m in queue_types
                  )
        ) :
            raise TypeError("invalid message type in queue_types: %s" % repr(queue_types))
        #end if
        if enable :
            if self._receive_queue == None :
                self.add_filter(self._queue_received_message, None)
                self._receive_queue = []
            #end if
            self._receive_queue_enabled.clear()
            self._receive_queue_enabled.update(queue_types)
        else :
            if self._receive_queue != None :
                self._flush_awaiting_receive()
                self.remove_filter(self._queue_received_message, None)
                self._receive_queue = None
            #end if
        #end if
    #end enable_receive_message

    async def receive_message_async(self, want_types = None, timeout = DBUS.TIMEOUT_INFINITE) :
        "receives the first available queued message of an appropriate type, blocking" \
        " if none is available and timeout is nonzero. Returns None if the timeout" \
        " elapses without a suitable message becoming available. want_types can be" \
        " None to receive any of the previously-enabled message types, or a set or" \
        " sequence of DBUS.MESSAGE_TYPE_XXX values to look only for messages of those" \
        " types.\n" \
        "\n" \
        "You must have previously made a call to enable_receive_message to enable" \
        " queueing of one or more message types on this Connection."
        assert self._receive_queue != None, "receive_message_async not enabled"
        # should I check if want_types contains anything not in self._receive_queue_enabled?
        if timeout == DBUS.TIMEOUT_USE_DEFAULT :
            timeout = DBUSX.DEFAULT_TIMEOUT
        #end if
        if timeout != DBUS.TIMEOUT_INFINITE :
            finish_time = self.loop.time() + timeout
        else :
            finish_time = None
        #end if
        result = ... # indicates “watch this space”
        while True :
            # keep rescanning queue until got something or timeout
            index = 0 # start next queue scan
            while True :
                if index == len(self._receive_queue) :
                    # nothing currently suitable on queue
                    if (
                            timeout == 0
                        or
                                finish_time != None
                            and
                                self.loop.time() > finish_time
                    ) :
                        # waited too long, give up
                        result = None
                        break
                    #end if
                    if not self.is_connected :
                        raise BrokenPipeError("Connection has been disconnected")
                    #end if
                    # wait and see if something turns up
                    awaiting = self.loop.create_future()
                    self._awaiting_receive.append(awaiting)
                    if finish_time != None :
                        wait_timeout = finish_time - self.loop.time()
                    else :
                        wait_timeout = None
                    #end if
                    await self.wait \
                      (
                        (awaiting,),
                        timeout = wait_timeout
                      )
                        # ignore done & pending results because they
                        # don’t match up with future I’m waiting for
                    try :
                        self._awaiting_receive.remove(awaiting)
                    except ValueError :
                        pass
                    #end try
                    awaiting.cancel()
                      # just to avoid “Future exception was never retrieved” message
                    break # start new queue scan
                #end if
                # check next queue item
                msg = self._receive_queue[index]
                if want_types == None or msg.type in want_types :
                    # caller wants this one
                    result = msg
                    self._receive_queue.pop(index) # remove msg from queue
                    break
                #end if
                index += 1
            #end while
            if result != ... :
                # either got something or given up
                break
        #end while
        return \
            result
    #end receive_message_async

    def iter_messages_async(self, want_types = None, stop_on = None, timeout = DBUS.TIMEOUT_INFINITE) :
        "wrapper around receive_message_async() to allow use with an async-for statement." \
        " Lets you write\n" \
        "\n" \
        "    async for message in «conn».iter_messages_async(«want_types», «stop_on», «timeout») :" \
        "        «process message»\n" \
        "    #end for\n" \
        "\n" \
        "to receive and process messages in a loop. stop_on is an optional set of" \
        " STOP_ON.xxx values indicating the conditions under which the iterator will" \
        " raise StopAsyncIteration to terminate the loop."
        if stop_on == None :
            stop_on = frozenset()
        elif (
                not isinstance(stop_on, (set, frozenset))
            or
                not all(isinstance(elt, STOP_ON) for elt in stop_on)
        ) :
            raise TypeError("stop_on must be None or set of STOP_ON")
        #end if
        assert self._receive_queue != None, "receive_message_async not enabled"
        return \
            _MsgAiter(self, want_types, stop_on, timeout)
    #end iter_messages_async

    # TODO: allocate/free data slot -- staticmethods
    # TODO: get/set data

    def set_change_sigpipe(self, will_modify_sigpipe) :
        dbus.dbus_connection_set_change_sigpipe(self._dbobj, will_modify_sigpipe)
    #end set_change_sigpipe

    @property
    def max_message_size(self) :
        return \
            dbus.dbus_connection_get_max_message_size(self._dbobj)
    #end max_message_size

    @max_message_size.setter
    def max_message_size(self, size) :
        dbus.dbus_connection_set_max_message_size(self._dbobj, size)
    #end max_message_size

    @property
    def max_received_size(self) :
        return \
            dbus.dbus_connection_get_max_received_size(self._dbobj)
    #end max_received_size

    @max_received_size.setter
    def max_received_size(self, size) :
        dbus.dbus_connection_set_max_received_size(self._dbobj, size)
    #end max_received_size

    @property
    def max_message_unix_fds(self) :
        return \
            dbus.dbus_connection_get_max_message_unix_fds(self._dbobj)
    #end max_message_unix_fds

    @max_message_unix_fds.setter
    def max_message_unix_fds(self, size) :
        dbus.dbus_connection_set_max_message_unix_fds(self._dbobj, size)
    #end max_message_unix_fds

    @property
    def max_received_unix_fds(self) :
        return \
            dbus.dbus_connection_get_max_received_unix_fds(self._dbobj)
    #end max_received_unix_fds

    @max_received_unix_fds.setter
    def max_received_unix_fds(self, size) :
        dbus.dbus_connection_set_max_received_unix_fds(self._dbobj, size)
    #end max_received_unix_fds

    @property
    def outgoing_size(self) :
        return \
            dbus.dbus_connection_get_outgoing_size(self._dbobj)
    #end outgoing_size

    @property
    def outgoing_unix_fds(self) :
        return \
            dbus.dbus_connection_get_outgoing_unix_fds(self._dbobj)
    #end outgoing_unix_fds

    @property
    def has_messages_to_send(self) :
        return \
            dbus.dbus_connection_has_messages_to_send(self._dbobj) != 0
    #end has_messages_to_send

    # message bus APIs
    # <https://dbus.freedesktop.org/doc/api/html/group__DBusBus.html>

    @classmethod
    def bus_get(celf, type, private, error = None) :
        "returns a Connection to one of the predefined D-Bus buses; type is a BUS_xxx value."
        error, my_error = _get_error(error)
        result = (dbus.dbus_bus_get, dbus.dbus_bus_get_private)[private](type, error._dbobj)
        my_error.raise_if_set()
        if result != None :
            result = celf(result)
        #end if
        return \
            result
    #end bus_get

    @classmethod
    async def bus_get_async(celf, type, private, error = None, loop = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        if loop == None :
            loop = get_running_loop()
        #end if
        assert type in (DBUS.BUS_SESSION, DBUS.BUS_SYSTEM, DBUS.BUS_STARTER), \
            "bus type must be BUS_SESSION, BUS_SYSTEM or BUS_STARTER"
        if type == DBUS.BUS_STARTER :
            starter_type = os.environ.get(DBUSX.STARTER_BUS_ADDRESS_TYPE)
            is_system_bus = starter_type != None and starter_type == DBUSX.BUS_TYPE_SYSTEM
            addr = os.environ.get(DBUSX.STARTER_BUS_ADDRESS_VAR)
        else :
            is_system_bus = type == DBUS.BUS_SYSTEM
            addr = os.environ.get \
              (
                (DBUSX.SESSION_BUS_ADDRESS_VAR, DBUSX.SYSTEM_BUS_ADDRESS_VAR)[is_system_bus]
              )
        #end if
        if not private and celf._shared_connections[is_system_bus] != None :
            result = celf._shared_connections[is_system_bus]
        else :
            if addr == None :
                addr = (DBUSX.SESSION_BUS_ADDRESS, DBUSX.SYSTEM_BUS_ADDRESS)[is_system_bus]
            #end if
            try :
                result = await celf.open_async(addr, private, error, loop, timeout)
                if error != None and error.is_set :
                    raise _Abort
                #end if
                await result.bus_register_async(error = error, timeout = timeout)
                if error != None and error.is_set :
                    raise _Abort
                #end if
                if not private :
                    celf._shared_connections[is_system_bus] = result
                #end if
            except _Abort :
                result = None
            #end try
        #end if
        return \
            result
    #end bus_get_async

    def bus_register(self, error = None) :
        "Only to be used if you created the Connection with open() instead of bus_get();" \
        " sends a “Hello” message to the D-Bus daemon to get a unique name assigned." \
        " Can only be called once."
        error, my_error = _get_error(error)
        dbus.dbus_bus_register(self._dbobj, error._dbobj)
        my_error.raise_if_set()
    #end bus_register

    async def bus_register_async(self, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "Only to be used if you created the Connection with open() instead of bus_get();" \
        " sends a “Hello” message to the D-Bus daemon to get a unique name assigned." \
        " Can only be called once."
        assert self.loop != None, "no event loop to attach coroutine to"
        assert self.bus_unique_name == None, "bus already registered"
        message = Message.new_method_call \
          (
            destination = DBUS.SERVICE_DBUS,
            path = DBUS.PATH_DBUS,
            iface = DBUS.INTERFACE_DBUS,
            method = "Hello"
          )
        reply = await self.send_await_reply(message, timeout = timeout)
        if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR :
            reply.set_error(error)
        else :
            self.bus_unique_name = reply.expect_return_objects("s")[0]
        #end if
    #end bus_register_async

    @property
    def bus_unique_name(self) :
        "returns None if the bus connection has not been registered. Note that the" \
        " unique_name can only be set once."
        result = dbus.dbus_bus_get_unique_name(self._dbobj)
        if result != None :
            result = result.decode()
        #end if
        return \
            result
    #end bus_unique_name

    @bus_unique_name.setter
    def bus_unique_name(self, unique_name) :
        if not dbus.dbus_bus_set_unique_name(self._dbobj, unique_name.encode()) :
            raise CallFailed("dbus_bus_set_unique_name")
        #end if
    #end bus_unique_name

    #+
    # Calls to D-Bus Daemon
    #-

    @property
    def bus_id(self) :
        my_error = Error()
        c_result = dbus.dbus_bus_get_id(self._dbobj, my_error._dbobj)
        my_error.raise_if_set()
        result = ct.cast(c_result, ct.c_char_p).value.decode()
        dbus.dbus_free(c_result)
        return \
            result
    #end bus_id

    @property
    async def bus_id_async(self) :
        message = Message.new_method_call \
          (
            destination = DBUS.SERVICE_DBUS,
            path = DBUS.PATH_DBUS,
            iface = DBUS.INTERFACE_DBUS,
            method = "GetId"
          )
        reply = await self.send_await_reply(message)
        return \
            reply.expect_return_objects("s")[0]
    #end bus_id_async

    def bus_get_unix_user(self, name, error = None) :
        error, my_error = _get_error(error)
        result = dbus.dbus_bus_get_unix_user(self._dbobj, name.encode(), error._dbobj)
        my_error.raise_if_set()
        return \
            result
    #end bus_get_unix_user

    async def bus_get_unix_user_async(self, name, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        message = Message.new_method_call \
          (
            destination = DBUS.SERVICE_DBUS,
            path = DBUS.PATH_DBUS,
            iface = DBUS.INTERFACE_DBUS,
            method = "GetConnectionUnixUser"
          )
        message.append_objects("s", name)
        reply = await self.send_await_reply(message, timeout = timeout)
        if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR :
            reply.set_error(error)
            result = None
        else :
            result = reply.expect_return_objects("u")[0]
        #end if
        return \
            result
    #end bus_get_unix_user_async

    def bus_request_name(self, name, flags, error = None) :
        "asks the D-Bus daemon to register the specified bus name on your behalf," \
        " blocking the thread until the reply is received. flags is a combination of" \
        " NAME_FLAG_xxx bits. Result will be a REQUEST_NAME_REPLY_xxx value or -1 on error."
        error, my_error = _get_error(error)
        result = dbus.dbus_bus_request_name(self._dbobj, name.encode(), flags, error._dbobj)
        my_error.raise_if_set()
        return \
            result
    #end bus_request_name

    async def bus_request_name_async(self, name, flags, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "asks the D-Bus daemon to register the specified bus name on your behalf. flags is" \
        " a combination of NAME_FLAG_xxx bits. Result will be a REQUEST_NAME_REPLY_xxx value" \
        " or None on error."
        message = Message.new_method_call \
          (
            destination = DBUS.SERVICE_DBUS,
            path = DBUS.PATH_DBUS,
            iface = DBUS.INTERFACE_DBUS,
            method = "RequestName"
          )
        message.append_objects("su", name, flags)
        reply = await self.send_await_reply(message, timeout = timeout)
        if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR :
            reply.set_error(error)
            result = None
        else :
            result = reply.expect_return_objects("u")[0]
        #end if
        return \
            result
    #end bus_request_name_async

    def bus_release_name(self, name, error = None) :
        "asks the D-Bus daemon to release your registration of the specified bus name," \
        " blocking the thread until the reply is received."
        error, my_error = _get_error(error)
        result = dbus.dbus_bus_release_name(self._dbobj, name.encode(), error._dbobj)
        my_error.raise_if_set()
        return \
            result
    #end bus_release_name

    async def bus_release_name_async(self, name, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "asks the D-Bus daemon to release your registration of the specified bus name."
        message = Message.new_method_call \
          (
            destination = DBUS.SERVICE_DBUS,
            path = DBUS.PATH_DBUS,
            iface = DBUS.INTERFACE_DBUS,
            method = "ReleaseName"
          )
        message.append_objects("s", name)
        reply = await self.send_await_reply(message, timeout = timeout)
        if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR :
            reply.set_error(error)
            result = None
        else :
            result = reply.expect_return_objects("u")[0]
        #end if
        return \
            result
    #end bus_release_name_async

    def bus_name_has_owner(self, name, error = None) :
        "asks the D-Bus daemon if anybody has claimed the specified bus name, blocking" \
        " the thread until the reply is received."
        error, my_error = _get_error(error)
        result = dbus.dbus_bus_name_has_owner(self._dbobj, name.encode(), error._dbobj)
        my_error.raise_if_set()
        return \
            result
    #end bus_name_has_owner

    async def bus_name_has_owner_async(self, name, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "asks the D-Bus daemon if anybody has claimed the specified bus name."
        message = Message.new_method_call \
          (
            destination = DBUS.SERVICE_DBUS,
            path = DBUS.PATH_DBUS,
            iface = DBUS.INTERFACE_DBUS,
            method = "NameHasOwner"
          )
        message.append_objects("s", name)
        reply = await self.send_await_reply(message, timeout = timeout)
        if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR :
            reply.set_error(error)
            result = None
        else :
            result = reply.expect_return_objects("b")[0]
        #end if
        return \
            result
    #end bus_name_has_owner_async

    def bus_start_service_by_name(self, name, flags = 0, error = None) :
        error, my_error = _get_error(error)
        outflags = ct.c_uint()
        success = dbus.dbus_bus_start_service_by_name(self._dbobj, name.encode(), flags, ct.byref(outflags), error._dbobj)
        my_error.raise_if_set()
        return \
            outflags.value
    #end bus_start_service_by_name

    async def bus_start_service_by_name_async(self, name, flags = 0, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        message = Message.new_method_call \
          (
            destination = DBUS.SERVICE_DBUS,
            path = DBUS.PATH_DBUS,
            iface = DBUS.INTERFACE_DBUS,
            method = "StartServiceByName"
          )
        message.append_objects("su", name, flags)
        reply = await self.send_await_reply(message, timeout = timeout)
        if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR :
            reply.set_error(error)
            result = None
        else :
            result = reply.expect_return_objects("u")[0]
        #end if
        return \
            result
    #end bus_start_service_by_name

    def bus_add_match(self, rule, error = None) :
        "adds a match rule for messages you want to receive. By default you get all" \
        " messages addressed to your bus name(s); but you can use this, for example," \
        " to request notification of signals indicating useful events on the system."
        error, my_error = _get_error(error)
        dbus.dbus_bus_add_match(self._dbobj, format_rule(rule).encode(), error._dbobj)
        my_error.raise_if_set()
    #end bus_add_match

    async def bus_add_match_async(self, rule, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "adds a match rule for messages you want to receive. By default you get all" \
        " messages addressed to your bus name(s); but you can use this, for example," \
        " to request notification of signals indicating useful events on the system."
        message = Message.new_method_call \
          (
            destination = DBUS.SERVICE_DBUS,
            path = DBUS.PATH_DBUS,
            iface = DBUS.INTERFACE_DBUS,
            method = "AddMatch"
          )
        message.append_objects("s", format_rule(rule))
        reply = await self.send_await_reply(message, timeout = timeout)
        if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR :
            reply.set_error(error)
        else :
            reply.expect_return_objects("")
        #end if
    #end bus_add_match_async

    def bus_remove_match(self, rule, error = None) :
        "removes a previously-added match rule for messages you previously wanted" \
        " to receive."
        error, my_error = _get_error(error)
        dbus.dbus_bus_remove_match(self._dbobj, format_rule(rule).encode(), error._dbobj)
        my_error.raise_if_set()
    #end bus_remove_match

    async def bus_remove_match_async(self, rule, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "removes a previously-added match rule for messages you previously wanted" \
        " to receive."
        message = Message.new_method_call \
          (
            destination = DBUS.SERVICE_DBUS,
            path = DBUS.PATH_DBUS,
            iface = DBUS.INTERFACE_DBUS,
            method = "RemoveMatch"
          )
        message.append_objects("s", format_rule(rule))
        reply = await self.send_await_reply(message, timeout = timeout)
        if error != None and reply.type == DBUS.MESSAGE_TYPE_ERROR :
            reply.set_error(error)
        else :
            reply.expect_return_objects("")
        #end if
    #end bus_remove_match_async

    @staticmethod
    def _rule_action_match(self, message, _) :
        # installed as a message filter to invoke actions corresponding to rules
        # that the message matches. To avoid spurious method-not-handled errors
        # from eavesdropping on method calls not addressed to me, this routine
        # always returns a “handled” status. That means this same Connection
        # object should not be used for both eavesdropping and for normal
        # method calls.
        handled = False
        for entry in self._match_actions.values() :
            if matches_rule(message, entry.rule) :
                for action in entry.actions :
                    result = action.func(self, message, action.user_data)
                    if asyncio.iscoroutine(result) :
                        self.create_task(result)
                    #end if
                #end for
                handled = True # passed to at least one handler
            #end if
        #end for
        return \
            (DBUS.HANDLER_RESULT_NOT_YET_HANDLED, DBUS.HANDLER_RESULT_HANDLED)[handled]
    #end _rule_action_match

    def bus_add_match_action(self, rule, func, user_data, error = None) :
        "adds a message filter that invokes func(conn, message, user_data)" \
        " for each incoming message that matches the specified rule. Unlike" \
        " the underlying add_filter and bus_add_match calls, this allows you" \
        " to associate the action with the particular matching rule.\n" \
        "\n" \
        "Note that the message filter installed to process these rules always" \
        " returns a DBUS.HANDLER_RESULT_HANDLED status; so either only use this" \
        " to listen for signals, or do not use the same Connection object to" \
        " handle normal method calls."
        rulekey = format_rule(rule)
        rule = unformat_rule(rule)
        if rulekey not in self._match_actions :
            self.bus_add_match(rulekey, error) # could fail here with bad rule
            if error == None or not error.is_set :
                if len(self._match_actions) == 0 :
                    self.add_filter(self._rule_action_match, None)
                #end if
                self._match_actions[rulekey] = _MatchActionEntry(rule)
            #end if
        #end if
        if error == None or not error.is_set :
            self._match_actions[rulekey].actions.add(_MatchActionEntry._Action(func, user_data))
        #end if
    #end bus_add_match_action

    def bus_remove_match_action(self, rule, func, user_data, error = None) :
        "removes a message filter previously installed with bus_add_match_action."
        rulekey = format_rule(rule)
        rule = unformat_rule(rule)
        self._match_actions[rulekey].actions.remove(_MatchActionEntry._Action(func, user_data))
        if len(self._match_actions[rulekey].actions) == 0 :
            self.bus_remove_match(rulekey, error) # shouldn’t fail!
            del self._match_actions[rulekey]
            if len(self._match_actions) == 0 :
                self.remove_filter(self._rule_action_match, None)
            #end if
        #end if
    #end bus_remove_match_action

    async def bus_add_match_action_async(self, rule, func, user_data, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "adds a message filter that invokes func(conn, message, user_data)" \
        " for each incoming message that matches the specified rule. Unlike" \
        " the underlying add_filter and bus_add_match calls, this allows you" \
        " to associate the action with the particular matching rule.\n" \
        "\n" \
        "Note that the message filter installed to process these rules always" \
        " returns a DBUS.HANDLER_RESULT_HANDLED status; so either only use this" \
        " to listen for signals, or do not use the same Connection object to" \
        " handle normal method calls."
        rulekey = format_rule(rule)
        rule = unformat_rule(rule)
        if rulekey not in self._match_actions :
            await self.bus_add_match_async(rulekey, error, timeout) # could fail here with bad rule
            if error == None or not error.is_set :
                if len(self._match_actions) == 0 :
                    self.add_filter(self._rule_action_match, None)
                #end if
                self._match_actions[rulekey] = _MatchActionEntry(rule)
            #end if
        #end if
        if error == None or not error.is_set :
            self._match_actions[rulekey].actions.add(_MatchActionEntry._Action(func, user_data))
        #end if
    #end bus_add_match_action_async

    async def bus_remove_match_action_async(self, rule, func, user_data, error = None, timeout = DBUS.TIMEOUT_USE_DEFAULT) :
        "removes a message filter previously installed with bus_add_match_action."
        rulekey = format_rule(rule)
        rule = unformat_rule(rule)
        self._match_actions[rulekey].actions.remove(_MatchActionEntry._Action(func, user_data))
        if len(self._match_actions[rulekey].actions) == 0 :
            await self.bus_remove_match_async(rulekey, error, timeout) # shouldn’t fail!
            del self._match_actions[rulekey]
            if len(self._match_actions) == 0 :
                self.remove_filter(self._rule_action_match, None)
            #end if
        #end if
    #end bus_remove_match_action_async

    def become_monitor(self, rules) :
        "turns the connection into one that can only receive monitoring messages."
        message = Message.new_method_call \
          (
            destination = DBUS.SERVICE_DBUS,
            path = DBUS.PATH_DBUS,
            iface = DBUS.INTERFACE_MONITORING,
            method = "BecomeMonitor"
          )
        message.append_objects("asu", (list(format_rule(rule) for rule in rules)), 0)
        self.send(message)
    #end become_monitor

    #+
    # End calls to D-Bus Daemon
    #-

    def attach_asyncio(self, loop = None) :
        "attaches this Connection object to an asyncio event loop. If none is" \
        " specified, the default event loop (as returned from asyncio.get_event_loop()" \
        " is used."

        w_self = weak_ref(self)
          # to avoid a reference cycle

        def dispatch() :
            return \
                _wderef(w_self, "connection").dispatch()
        #end dispatch

    #begin attach_asyncio
        assert self.loop == None, "already attached to an event loop"
        _loop_attach(self, loop, dispatch)
    #end attach_asyncio

#end Connection

class _MsgAiter :
    # internal class for use by Connection.iter_messages_async (above).

    def __init__(self, conn, want_types, stop_on, timeout) :
        self.conn = conn
        self.want_types = want_types
        self.stop_on = stop_on
        self.timeout = timeout
    #end __init__

    def __aiter__(self) :
        # I’m my own iterator.
        return \
            self
    #end __aiter__

    async def __anext__(self) :
        stop_iter = False
        try :
            result = await self.conn.receive_message_async(self.want_types, self.timeout)
            if result == None and STOP_ON.TIMEOUT in self.stop_on :
                stop_iter = True
            #end if
        except BrokenPipeError :
            if STOP_ON.CLOSED not in self.stop_on :
                raise
            #end if
            stop_iter = True
        #end try
        if stop_iter :
            raise StopAsyncIteration("Connection.receive_message_async terminating")
        #end if
        return \
            result
    #end __anext__

#end _MsgAiter

class Server(TaskKeeper) :
    "wrapper around a DBusServer object. Do not instantiate directly; use" \
    " the listen method.\n" \
    "\n" \
    "You only need this if you want to use D-Bus as a communication mechanism" \
    " separate from the system/session buses provided by the D-Bus daemon: you" \
    " create a Server object listening on a specified address, and clients can" \
    " use Connection.open() to connect to you on that address."
    # <https://dbus.freedesktop.org/doc/api/html/group__DBusServer.html>

    # Doesn’t really need services of TaskKeeper for now, but might be
    # useful in future

    __slots__ = \
      (
        "_dbobj",
        "_new_connections",
        "_await_new_connections",
        "max_new_connections",
        "autoattach_new_connections",
        # need to keep references to ctypes-wrapped functions
        # so they don't disappear prematurely:
        "_new_connection_function",
        "_free_new_connection_data",
        "_add_watch_function",
        "_remove_watch_function",
        "_toggled_watch_function",
        "_free_watch_data",
        "_add_timeout_function",
        "_remove_timeout_function",
        "_toggled_timeout_function",
        "_free_timeout_data",
      ) # to forestall typos

    _instances = WeakValueDictionary()

    def __new__(celf, _dbobj) :
        self = celf._instances.get(_dbobj)
        if self == None :
            self = super().__new__(celf)
            super()._init(self)
            self._dbobj = _dbobj
            self._new_connections = None
            self._await_new_connections = None
            self.max_new_connections = None
            self.autoattach_new_connections = True
            self._new_connection_function = None
            self._free_new_connection_data = None
            self._add_watch_function = None
            self._remove_watch_function = None
            self._toggled_watch_function = None
            self._free_watch_data = None
            self._add_timeout_function = None
            self._remove_timeout_function = None
            self._toggled_timeout_function = None
            self._free_timeout_data = None
            celf._instances[_dbobj] = self
        else :
            dbus.dbus_server_unref(self._dbobj)
              # lose extra reference created by caller
        #end if
        return \
            self
    #end __new__

    def __del__(self) :
        if self._dbobj != None :
            if self.loop != None :
                # remove via direct low-level libdbus calls
                dbus.dbus_server_set_watch_functions(self._dbobj, None, None, None, None, None)
                dbus.dbus_server_set_timeout_functions(self._dbobj, None, None, None, None, None)
                self.loop = None
            #end if
            dbus.dbus_server_unref(self._dbobj)
            self._dbobj = None
        #end if
    #end __del__

    @classmethod
    def listen(celf, address, error = None) :
        error, my_error = _get_error(error)
        result = dbus.dbus_server_listen(address.encode(), error._dbobj)
        my_error.raise_if_set()
        if result != None :
            result = celf(result)
        #end if
        return \
            result
    #end listen

    def _flush_awaiting_connect(self) :
        if self._await_new_connections != None :
            while len(self._await_new_connections) != 0 :
                waiting = self._await_new_connections.pop(0)
                waiting.set_exception(BrokenPipeError("async listens have been disabled"))
            #end while
        #end if
    #end _flush_awaiting_connect

    def disconnect(self) :
        self._flush_awaiting_connect()
        dbus.dbus_server_disconnect(self._dbobj)
    #end disconnect

    @property
    def is_connected(self) :
        return \
            dbus.dbus_server_get_is_connected(self._dbobj) != 0
    #end is_connected

    @property
    def address(self) :
        c_result = dbus.dbus_server_get_address(self._dbobj)
        if c_result == None :
            raise CallFailed("dbus_server_get_address")
        #end if
        result = ct.cast(c_result, ct.c_char_p).value.decode()
        dbus.dbus_free(c_result)
        return \
            result
    #end address

    @property
    def id(self) :
        c_result = dbus.dbus_server_get_id(self._dbobj)
        if c_result == None :
            raise CallFailed("dbus_server_get_id")
        #end if
        result = ct.cast(c_result, ct.c_char_p).value.decode()
        dbus.dbus_free(c_result)
        return \
            result
    #end id

    def set_new_connection_function(self, function, data, free_data = None) :
        "sets the callback for libdbus to notify you of a new incoming connection." \
        " It is up to you to save the Connection object for later processing of" \
        " messages, or close it to reject the connection attempt."

        w_self = weak_ref(self)

        def wrap_function(c_self, c_conn, _data) :
            function(_wderef(w_self, "server"), Connection(dbus.dbus_connection_ref(c_conn)), data)
              # even though this is a new connection, I still have to reference it
        #end wrap_function

        def wrap_free_data(_data) :
            free_data(data)
        #end wrap_free_data

    #begin set_new_connection_function
        assert self.loop == None, "new connections are being managed by an event loop"
        self._new_connection_function = DBUS.NewConnectionFunction(wrap_function)
        if free_data != None :
            self._free_new_connection_data = DBUS.FreeFunction(wrap_free_data)
        else :
            self._free_new_connection_data = None
        #end if
        dbus.dbus_server_set_new_connection_function(self._dbobj, self._new_connection_function, None, self._free_new_connection_data)
    #end set_new_connection_function

    def set_watch_functions(self, add_function, remove_function, toggled_function, data, free_data = None) :
        "sets the callbacks for libdbus to use to notify you of Watch objects it wants" \
        " you to manage."

        def wrap_add_function(c_watch, _data) :
            return \
                add_function(Watch(c_watch), data)
        #end wrap_add_function

        def wrap_remove_function(c_watch, _data) :
            return \
                remove_function(Watch(c_watch), data)
        #end wrap_remove_function

        def wrap_toggled_function(c_watch, _data) :
            return \
                toggled_function(Watch(c_watch), data)
        #end wrap_toggled_function

        def wrap_free_data(_data) :
            free_data(data)
        #end wrap_free_data

    #begin set_watch_functions
        self._add_watch_function = DBUS.AddWatchFunction(wrap_add_function)
        self._remove_watch_function = DBUS.RemoveWatchFunction(wrap_remove_function)
        if toggled_function != None :
            self._toggled_watch_function = DBUS.WatchToggledFunction(wrap_toggled_function)
        else :
            self._toggled_watch_function = None
        #end if
        if free_data != None :
            self._free_watch_data = DBUS.FreeFunction(wrap_free_data)
        else :
            self._free_watch_data = None
        #end if
        if not dbus.dbus_server_set_watch_functions(self._dbobj, self._add_watch_function, self._remove_watch_function, self._toggled_watch_function, None, self._free_watch_data) :
            raise CallFailed("dbus_server_set_watch_functions")
        #end if
    #end set_watch_functions

    def set_timeout_functions(self, add_function, remove_function, toggled_function, data, free_data = None) :
        "sets the callbacks for libdbus to use to notify you of Timeout objects it wants" \
        " you to manage."

        def wrap_add_function(c_timeout, _data) :
            return \
                add_function(Timeout(c_timeout), data)
        #end wrap_add_function

        def wrap_remove_function(c_timeout, _data) :
            return \
                remove_function(Timeout(c_timeout), data)
        #end wrap_remove_function

        def wrap_toggled_function(c_timeout, _data) :
            return \
                toggled_function(Timeout(c_timeout), data)
        #end wrap_toggled_function

        def wrap_free_data(_data) :
            free_data(data)
        #end wrap_free_data

    #begin set_timeout_functions
        self._add_timeout_function = DBUS.AddTimeoutFunction(wrap_add_function)
        self._remove_timeout_function = DBUS.RemoveTimeoutFunction(wrap_remove_function)
        if toggled_function != None :
            self._toggled_timeout_function = DBUS.TimeoutToggledFunction(wrap_toggled_function)
        else :
            self._toggled_timeout_function = None
        #end if
        if free_data != None :
            self._free_timeout_data = DBUS.FreeFunction(wrap_free_data)
        else :
            self._free_timeout_data = None
        #end if
        if not dbus.dbus_server_set_timeout_functions(self._dbobj, self._add_timeout_function, self._remove_timeout_function, self._toggled_timeout_function, None, self._free_timeout_data) :
            raise CallFailed("dbus_server_set_timeout_functions")
        #end if
    #end set_timeout_functions

    def set_auth_mechanisms(self, mechanisms) :
        nr_mechanisms = len(mechanisms)
        c_mechanisms = (ct.c_char_p * (nr_mechanisms + 1))()
        for i in range(nr_mechanisms) :
            c_mechanisms[i] = mechanisms[i].encode()
        #end if
        c_mechanisms[nr_mechanisms] = None # marks end of array
        if not dbus.dbus_server_set_auth_mechanisms(self._dbobj, c_mechanisms) :
            raise CallFailed("dbus_server_set_auth_mechanisms")
        #end if
    #end set_auth_mechanisms

    # TODO: allocate/free slot (static methods)
    # TODO: get/set/data

    def attach_asyncio(self, loop = None) :
        "attaches this Server object to an asyncio event loop. If none is" \
        " specified, the default event loop (as returned from asyncio.get_event_loop()" \
        " is used.\n" \
        "\n" \
        "This call will also automatically attach a new_connection callback. You then use" \
        " the await_new_connection coroutine to obtain new connections. If" \
        " self.autoattach_new_connections, then Connection.attach_asyncio() will" \
        " automatically be called to handle events for the new connection."

        def new_connection(self, conn, user_data) :
            if len(self._await_new_connections) != 0 :
                awaiting = self._await_new_connections.pop(0)
                awaiting.set_result(conn)
            else :
                # put it in _new_connections queue
                if (
                        self.max_new_connections != None
                    and
                        len(self._new_connections) >= self.max_new_connections
                ) :
                    # too many connections pending, reject
                    conn.close()
                else :
                    self._new_connections.append(conn)
                #end if
            #end if
        #end new_connection

    #begin attach_asyncio
        assert self.loop == None, "already attached to an event loop"
        assert self._new_connection_function == None, "already set a new-connection function"
        self._new_connections = []
        self._await_new_connections = []
        self.set_new_connection_function(new_connection, None)
        _loop_attach(self, loop, None)
    #end attach_asyncio

    async def await_new_connection(self, timeout = DBUS.TIMEOUT_INFINITE) :
        "retrieves the next new Connection, if there is one available, otherwise" \
        " suspends the current coroutine for up to the specified timeout duration" \
        " while waiting for one to appear. Returns None if there is no new connection" \
        " within that time."
        assert self.loop != None, "no event loop to attach coroutine to"
        if len(self._new_connections) != 0 :
            result = self._new_connections.pop(0)
        else :
            if not self.is_connected :
                raise BrokenPipeError("Server has been disconnected")
            #end if
            if timeout == 0 :
                # might as well short-circuit the whole waiting process
                result = None
            else :
                awaiting = self.loop.create_future()
                self._await_new_connections.append(awaiting)
                if timeout == DBUS.TIMEOUT_INFINITE :
                    timeout = None
                else :
                    if timeout == DBUS.TIMEOUT_USE_DEFAULT :
                        timeout = DBUSX.DEFAULT_TIMEOUT
                    #end if
                #end if
                await self.wait \
                  (
                    (awaiting,),
                    timeout = timeout
                  )
                    # ignore done & pending results because they
                    # don’t match up with future I’m waiting for
                if awaiting.done() :
                    result = awaiting.result()
                else :
                    self._await_new_connections.pop(self._await_new_connections.index(awaiting))
                    result = None
                #end if
            #end if
        #end if
        if result != None and self.autoattach_new_connections :
            result.attach_asyncio(self.loop)
        #end if
        return \
            result
    #end await_new_connection

    def iter_connections_async(self, stop_on = None, timeout = DBUS.TIMEOUT_INFINITE) :
        "wrapper around await_new_connection() to allow use with an async-for" \
        " statement. Lets you write\n" \
        "\n" \
        "    async for conn in «server».iter_connections_async(«timeout») :" \
        "        «accept conn»\n" \
        "    #end for\n" \
        "\n" \
        "to receive and process incoming connections in a loop. stop_on is an optional set of" \
        " STOP_ON.xxx values indicating the conditions under which the iterator will" \
        " raise StopAsyncIteration to terminate the loop."
        assert self.loop != None, "no event loop to attach coroutine to"
        if stop_on == None :
            stop_on = frozenset()
        elif (
                not isinstance(stop_on, (set, frozenset))
            or
                not all(isinstance(elt, STOP_ON) for elt in stop_on)
        ) :
            raise TypeError("stop_on must be None or set of STOP_ON")
        #end if
        return \
            _SrvAiter(self, stop_on, timeout)
    #end iter_connections_async

#end Server

class _SrvAiter :
    # internal class for use by Server.iter_connections_async (above).

    def __init__(self, srv, stop_on, timeout) :
        self.srv = srv
        self.stop_on = stop_on
        self.timeout = timeout
    #end __init__

    def __aiter__(self) :
        # I’m my own iterator.
        return \
            self
    #end __aiter__

    async def __anext__(self) :
        stop_iter = False
        try :
            result = await self.srv.await_new_connection(self.timeout)
            if result == None and STOP_ON.TIMEOUT in self.stop_on :
                stop_iter = True
            #end if
        except BrokenPipeError :
            if STOP_ON.CLOSED not in self.stop_on :
                raise
            #end if
            stop_iter = True
        #end try
        if stop_iter :
            raise StopAsyncIteration("Server.iter_connections_async terminating")
        #end if
        return \
            result
    #end __anext__

#end _SrvAiter

class PreallocatedSend :
    "wrapper around a DBusPreallocatedSend object. Do not instantiate directly;" \
    " get from Connection.preallocate_send method."
    # <https://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html>

    __slots__ = ("__weakref__", "_dbobj", "_w_parent", "_sent") # to forestall typos

    _instances = WeakValueDictionary()

    def __new__(celf, _dbobj, _parent) :
        self = celf._instances.get(_dbobj)
        if self == None :
            self = super().__new__(celf)
            self._dbobj = _dbobj
            self._w_parent = weak_ref(_parent)
            self._sent = False
            celf._instances[_dbobj] = self
        else :
            assert self._w_parent() == _parent
        #end if
        return \
            self
    #end __new__

    def __del__(self) :
        if self._dbobj != None :
            parent = self._w_parent()
            if parent != None and not self._sent :
                dbus.dbus_connection_free_preallocated_send(parent._dbobj, self._dbobj)
            #end if
            self._dbobj = None
        #end if
    #end __del__

    def send(self, message) :
        "alternative to Connection.send_preallocated."
        if not isinstance(message, Message) :
            raise TypeError("message must be a Message")
        #end if
        assert not self._sent, "preallocated has already been sent"
        parent = self._w_parent()
        assert parent != None, "parent Connection has gone away"
        serial = ct.c_uint()
        dbus.dbus_connection_send_preallocated(parent._dbobj, self._dbobj, message._dbobj, ct.byref(serial))
        self._sent = True
        return \
            serial.value
    #end send

#end PreallocatedSend

class Message :
    "wrapper around a DBusMessage object. Do not instantiate directly; use one of the" \
    " new_xxx or copy methods, or Connection.pop_message or Connection.borrow_message."
    # <https://dbus.freedesktop.org/doc/api/html/group__DBusMessage.html>

    __slots__ = ("__weakref__", "_dbobj", "_conn", "_borrowed") # to forestall typos

    _instances = WeakValueDictionary()

    def __new__(celf, _dbobj) :
        self = celf._instances.get(_dbobj)
        if self == None :
            self = super().__new__(celf)
            self._dbobj = _dbobj
            self._conn = None
            self._borrowed = False
            celf._instances[_dbobj] = self
        else :
            dbus.dbus_message_unref(self._dbobj)
              # lose extra reference created by caller
        #end if
        return \
            self
    #end __new__

    def __del__(self) :
        if self._dbobj != None :
            assert not self._borrowed, "trying to dispose of borrowed message"
            dbus.dbus_message_unref(self._dbobj)
            self._dbobj = None
        #end if
    #end __del__

    @classmethod
    def new(celf, type) :
        "type is one of the DBUS.MESSAGE_TYPE_xxx codes. Using one of the type-specific" \
        " calls--new_error, new_method_call, new_method_return, new_signal--is probably" \
        " more convenient."
        result = dbus.dbus_message_new(type)
        if result == None :
            raise CallFailed("dbus_message_new")
        #end if
        return \
            celf(result)
    #end new

    def new_error(self, name, message) :
        "creates a new DBUS.MESSAGE_TYPE_ERROR message that is a reply to this Message."
        result = dbus.dbus_message_new_error(self._dbobj, name.encode(), (lambda : None, lambda : message.encode())[message != None]())
        if result == None :
            raise CallFailed("dbus_message_new_error")
        #end if
        return \
            type(self)(result)
    #end new_error

    # probably not much point trying to use new_error_printf

    @classmethod
    def new_method_call(celf, destination, path, iface, method) :
        "creates a new DBUS.MESSAGE_TYPE_METHOD_CALL message."
        result = dbus.dbus_message_new_method_call \
          (
            (lambda : None, lambda : destination.encode())[destination != None](),
            path.encode(),
            (lambda : None, lambda : iface.encode())[iface != None](),
            method.encode(),
          )
        if result == None :
            raise CallFailed("dbus_message_new_method_call")
        #end if
        return \
            celf(result)
    #end new_method_call

    def new_method_return(self) :
        "creates a new DBUS.MESSAGE_TYPE_METHOD_RETURN that is a reply to this Message."
        result = dbus.dbus_message_new_method_return(self._dbobj)
        if result == None :
            raise CallFailed("dbus_message_new_method_return")
        #end if
        return \
            type(self)(result)
    #end new_method_return

    @classmethod
    def new_signal(celf, path, iface, name) :
        "creates a new DBUS.MESSAGE_TYPE_SIGNAL message."
        result = dbus.dbus_message_new_signal(path.encode(), iface.encode(), name.encode())
        if result == None :
            raise CallFailed("dbus_message_new_signal")
        #end if
        return \
            celf(result)
    #end new_signal

    def copy(self) :
        "creates a copy of this Message."
        result = dbus.dbus_message_copy(self._dbobj)
        if result == None :
            raise CallFailed("dbus_message_copy")
        #end if
        return \
            type(self)(result)
    #end copy

    @property
    def type(self) :
        "returns the DBUS.MESSAGE_TYPE_XXX code for this Message."
        return \
            dbus.dbus_message_get_type(self._dbobj)
    #end type

    # NYI append_args, get_args -- probably not useful, use my
    # objects and append_objects convenience methods (below) instead

    class ExtractIter :
        "for iterating over the arguments in a Message for reading. Do not" \
        " instantiate directly; get from Message.iter_init or ExtractIter.recurse.\n" \
        "\n" \
        "You can use this as a Python iterator, in a for-loop, passing" \
        " it to the next() built-in function etc. Do not mix such usage with calls to" \
        " the has_next() and next() methods."

        __slots__ = ("_dbobj", "_parent", "_nulliter", "_startiter") # to forestall typos

        def __init__(self, _parent) :
            self._dbobj = DBUS.MessageIter()
            self._parent = _parent
            self._nulliter = False
            self._startiter = True
        #end __init__

        @property
        def has_next(self) :
            return \
                dbus.dbus_message_iter_has_next(self._dbobj)
        #end has_next

        def next(self) :
            if self._nulliter or not dbus.dbus_message_iter_next(self._dbobj) :
                raise StopIteration("end of message iterator")
            #end if
            self._startiter = False
            return \
                self
        #end next

        def __iter__(self) :
            return \
                self
        #end __iter__

        def __next__(self) :
            if self._nulliter :
                raise StopIteration("empty message iterator")
            else :
                if self._startiter :
                    self._startiter = False
                else :
                    self.next()
                #end if
            #end if
            return \
                self
        #end __next__

        @property
        def arg_type(self) :
            "the type code for this argument."
            return \
                dbus.dbus_message_iter_get_arg_type(self._dbobj)
        #end arg_type

        @property
        def element_type(self) :
            "the contained element type of this argument, assuming it is of a container type."
            return \
                dbus.dbus_message_iter_get_element_type(self._dbobj)
        #end element_type

        def recurse(self) :
            "creates a sub-iterator for recursing into a container argument."
            subiter = type(self)(self)
            dbus.dbus_message_iter_recurse(self._dbobj, subiter._dbobj)
            return \
                subiter
        #end recurse

        @property
        def signature(self) :
            c_result = dbus.dbus_message_iter_get_signature(self._dbobj)
            if c_result == None :
                raise CallFailed("dbus_message_iter_get_signature")
            #end if
            result = ct.cast(c_result, ct.c_char_p).value.decode()
            dbus.dbus_free(c_result)
            return \
                result
        #end signature

        @property
        def basic(self) :
            "returns the argument value, assuming it is of a non-container type."
            argtype = self.arg_type
            c_result_type = DBUS.basic_to_ctypes[argtype]
            c_result = c_result_type()
            dbus.dbus_message_iter_get_basic(self._dbobj, ct.byref(c_result))
            if c_result_type == ct.c_char_p :
                result = c_result.value.decode()
            else :
                result = c_result.value
            #end if
            if argtype in DBUS.basic_subclasses :
                result = DBUS.basic_subclasses[argtype](result)
            #end if
            return \
                result
        #end basic

        @property
        def object(self) :
            "returns the current iterator item as a Python object. Will recursively" \
            " process container objects."
            argtype = self.arg_type
            if argtype in DBUS.basic_to_ctypes :
                result = self.basic
            elif argtype == DBUS.TYPE_ARRAY :
                if self.element_type == DBUS.TYPE_DICT_ENTRY :
                    result = {}
                    subiter = self.recurse()
                    while True :
                        entry = next(subiter, None)
                        if entry == None or entry.arg_type == DBUS.TYPE_INVALID :
                          # TYPE_INVALID can be returned for an empty dict
                            break
                        if entry.arg_type != DBUS.TYPE_DICT_ENTRY :
                            raise RuntimeError("invalid dict entry type %d" % entry.arg_type)
                        #end if
                        key, value = tuple(x.object for x in entry.recurse())
                        result[key] = value
                    #end while
                elif type_is_fixed_array_elttype(self.element_type) :
                    result = self.fixed_array
                else :
                    result = list(x.object for x in self.recurse())
                    if len(result) != 0 and result[-1] == None :
                        # fudge for iterating into an empty array
                        result = result[:-1]
                    #end if
                #end if
            elif argtype == DBUS.TYPE_STRUCT :
                result = list(x.object for x in self.recurse())
            elif argtype == DBUS.TYPE_VARIANT :
                subiter = self.recurse()
                subiter = next(subiter)
                result = (DBUS.Signature(subiter.signature), subiter.object)
            elif argtype == DBUS.TYPE_INVALID :
                # fudge for iterating into an empty array
                result = None
            else :
                raise RuntimeError("unrecognized argtype %d" % argtype)
            #end if
            return \
                result
        #end object

        if hasattr(dbus, "dbus_message_iter_get_element_count") :

            @property
            def element_count(self) :
                "returns the count of contained elements, assuming the current argument" \
                " is of a container type."
                return \
                    dbus.dbus_message_iter_get_element_count(self._dbobj)
            #end element_count

        #end if

        @property
        def fixed_array(self) :
            "returns the array elements, assuming the current argument is an array" \
            " with a non-container element type."
            c_element_type = DBUS.basic_to_ctypes[self.element_type]
            c_result = ct.POINTER(c_element_type)()
            c_nr_elts = ct.c_int()
            subiter = self.recurse()
            dbus.dbus_message_iter_get_fixed_array(subiter._dbobj, ct.byref(c_result), ct.byref(c_nr_elts))
            result = []
            for i in range(c_nr_elts.value) :
                elt = c_result[i]
                if c_element_type == ct.c_char_p :
                    elt = elt.value.decode()
                #end if
                result.append(elt)
            #end for
            return \
                result
        #end fixed_array

    #end ExtractIter

    class AppendIter :
        "for iterating over the arguments in a Message for appending." \
        " Do not instantiate directly; get from Message.iter_init_append or" \
        " AppendIter.open_container."

        __slots__ = ("_dbobj", "_parent") # to forestall typos

        def __init__(self, _parent) :
            self._dbobj = DBUS.MessageIter()
            self._parent = _parent
        #end __init__

        def append_basic(self, type, value) :
            "appends a single value of a non-container type."
            if type in DBUS.int_convert :
                value = DBUS.int_convert[type](value)
            #end if
            c_type = DBUS.basic_to_ctypes[type]
            if c_type == ct.c_char_p :
                if not isinstance(value, str) :
                    raise TypeError \
                      (
                        "expecting type %s, got %s" % (TYPE(type), builtins.type(value).__name__)
                      )
                #end if
                value = value.encode()
            #end if
            c_value = c_type(value)
            if not dbus.dbus_message_iter_append_basic(self._dbobj, type, ct.byref(c_value)) :
                raise CallFailed("dbus_message_iter_append_basic")
            #end if
            return \
                self
        #end append_basic

        def append_fixed_array(self, element_type, values) :
            "appends an array of elements of a non-container type."
            c_elt_type = DBUS.basic_to_ctypes[element_type]
            nr_elts = len(values)
            c_arr = (nr_elts * c_elt_type)()
            for i in range(nr_elts) :
                if c_elt_type == ct.c_char_p :
                    c_arr[i] = values[i].encode()
                else :
                    c_arr[i] = values[i]
                #end if
            #end for
            c_arr_ptr = ct.pointer(c_arr)
            if not dbus.dbus_message_iter_append_fixed_array(self._dbobj, element_type, ct.byref(c_arr_ptr), nr_elts) :
                raise CallFailed("dbus_message_iter_append_fixed_array")
            #end if
            return \
                self
        #end append_fixed_array

        def open_container(self, type, contained_signature) :
            "starts appending an argument of a container type, returning a sub-iterator" \
            " for appending the contents of the argument. Can be called recursively for" \
            " containers of containers etc."
            if contained_signature != None :
                c_sig = contained_signature.encode()
            else :
                c_sig = None
            #end if
            subiter = builtins.type(self)(self)
            if not dbus.dbus_message_iter_open_container(self._dbobj, type, c_sig, subiter._dbobj) :
                raise CallFailed("dbus_message_iter_open_container")
            #end if
            return \
                subiter
        #end open_container

        def close(self) :
            "closes a sub-iterator, indicating the completion of construction" \
            " of a container value."
            assert self._parent != None, "cannot close top-level iterator"
            if not dbus.dbus_message_iter_close_container(self._parent._dbobj, self._dbobj) :
                raise CallFailed("dbus_message_iter_close_container")
            #end if
            return \
                self._parent
        #end close

        def abandon(self) :
            "closes a sub-iterator, indicating the abandonment of construction" \
            " of a container value. The Message object is effectively unusable" \
            " after this point and should be discarded."
            assert self._parent != None, "cannot abandon top-level iterator"
            dbus.dbus_message_iter_abandon_container(self._parent._dbobj, self._dbobj)
            return \
                self._parent
        #end abandon

    #end AppendIter

    def iter_init(self) :
        "creates an iterator for extracting the arguments of the Message."
        iter = self.ExtractIter(None)
        if dbus.dbus_message_iter_init(self._dbobj, iter._dbobj) == 0 :
            iter._nulliter = True
        #end if
        return \
             iter
    #end iter_init

    @property
    def objects(self) :
        "yields the arguments of the Message as Python objects."
        for iter in self.iter_init() :
            yield iter.object
        #end for
    #end objects

    @property
    def all_objects(self) :
        "all the arguments of the Message as a list of Python objects."
        return \
            list(self.objects)
    #end all_objects

    def expect_objects(self, signature) :
        "expects the arguments of the Message to conform to the given signature," \
        " raising a TypeError if not. If they match, returns them as a list."
        signature = unparse_signature(signature)
        if self.signature != signature :
            raise TypeError("message args don’t match: expected “%s”, got “%s”" % (signature, self.signature))
        #end if
        return \
            self.all_objects
    #end expect_objects

    def expect_return_objects(self, signature) :
        "expects the Message to be of type DBUS.MESSAGE_TYPE_METHOD_RETURN and its" \
        " arguments to conform to the given signature. Raises the appropriate DBusError" \
        " if the Message is of type DBUS.MESSAGE_TYPE_ERROR."
        if self.type == DBUS.MESSAGE_TYPE_METHOD_RETURN :
            result = self.expect_objects(signature)
        elif self.type == DBUS.MESSAGE_TYPE_ERROR :
            raise DBusError(self.error_name, self.expect_objects("s")[0])
        else :
            raise ValueError("unexpected message type %d" % self.type)
        #end if
        return \
            result
    #end expect_return_objects

    def iter_init_append(self) :
        "creates a Message.AppendIter for appending arguments to the Message."
        iter = self.AppendIter(None)
        dbus.dbus_message_iter_init_append(self._dbobj, iter._dbobj)
        return \
            iter
    #end iter_init_append

    def append_objects(self, signature, *args) :
        "interprets Python values args according to signature and appends" \
        " converted item(s) to the message args."

        def append_sub(siglist, eltlist, appenditer) :
            if len(siglist) != len(eltlist) :
                raise ValueError \
                  (
                        "mismatch between signature entries %s and number of sequence elements %s"
                    %
                        (repr(siglist), repr(eltlist))
                  )
            #end if
            for elttype, elt in zip(siglist, eltlist) :
                if isinstance(elttype, BasicType) :
                    appenditer.append_basic(elttype.code.value, elt)
                elif isinstance(elttype, DictType) :
                    if not isinstance(elt, dict) :
                        raise TypeError("dict expected for %s" % repr(elttype))
                    #end if
                    subiter = appenditer.open_container(DBUS.TYPE_ARRAY, elttype.entry_signature)
                    for key in sorted(elt) : # might as well insert in some kind of predictable order
                        value = elt[key]
                        subsubiter = subiter.open_container(DBUS.TYPE_DICT_ENTRY, None)
                        append_sub([elttype.keytype, elttype.valuetype], [key, value], subsubiter)
                        subsubiter.close()
                    #end for
                    subiter.close()
                elif isinstance(elttype, ArrayType) :
                    # append 0 or more elements matching elttype.elttype
                    arrelttype = elttype.elttype
                    if type_is_fixed_array_elttype(arrelttype.code.value) :
                        subiter = appenditer.open_container(DBUS.TYPE_ARRAY, arrelttype.signature)
                        subiter.append_fixed_array(arrelttype.code.value, elt)
                        subiter.close()
                    else :
                        subiter = appenditer.open_container(DBUS.TYPE_ARRAY, arrelttype.signature)
                        if not isinstance(elt, (tuple, list)) :
                            raise TypeError("expecting sequence of values for array")
                        #end if
                        for subval in elt :
                            append_sub([arrelttype], [subval], subiter)
                        #end for
                        subiter.close()
                    #end if
                elif isinstance(elttype, StructType) :
                    if not isinstance(elt, (tuple, list)) :
                        raise TypeError("expecting sequence of values for struct")
                    #end if
                    subiter = appenditer.open_container(DBUS.TYPE_STRUCT, None)
                    append_sub(elttype.elttypes, elt, subiter)
                    subiter.close()
                elif isinstance(elttype, VariantType) :
                    if not isinstance(elt, (list, tuple)) or len(elt) != 2 :
                        raise TypeError("sequence of 2 elements expected for variant: %s" % repr(elt))
                    #end if
                    actual_type = parse_single_signature(elt[0])
                    subiter = appenditer.open_container(DBUS.TYPE_VARIANT, actual_type.signature)
                    append_sub([actual_type], [elt[1]], subiter)
                    subiter.close()
                else :
                    raise RuntimeError("unrecognized type %s" % repr(elttype))
                #end if
            #end for
        #end append_sub

    #begin append_objects
        append_sub(parse_signature(signature), args, self.iter_init_append())
        return \
            self
    #end append_objects

    @property
    def no_reply(self) :
        "whether the Message is not expecting a reply."
        return \
            dbus.dbus_message_get_no_reply(self._dbobj) != 0
    #end no_reply

    @no_reply.setter
    def no_reply(self, no_reply) :
        dbus.dbus_message_set_no_reply(self._dbobj, no_reply)
    #end no_reply

    @property
    def auto_start(self) :
        return \
            dbus.dbus_message_get_auto_start(self._dbobj) != 0
    #end auto_start

    @auto_start.setter
    def auto_start(self, auto_start) :
        dbus.dbus_message_set_auto_start(self._dbobj, auto_start)
    #end auto_start

    @property
    def path(self) :
        "the object path for a DBUS.MESSAGE_TYPE_METHOD_CALL or DBUS.DBUS.MESSAGE_TYPE_SIGNAL" \
        " message."
        result = dbus.dbus_message_get_path(self._dbobj)
        if result != None :
            result = DBUS.ObjectPath(result.decode())
        #end if
        return \
            result
    #end path

    @path.setter
    def path(self, object_path) :
        if not dbus.dbus_message_set_path(self._dbobj, (lambda : None, lambda : object_path.encode())[object_path != None]()) :
            raise CallFailed("dbus_message_set_path")
        #end if
    #end path

    @property
    def path_decomposed(self) :
        "the object path for a DBUS.MESSAGE_TYPE_METHOD_CALL or DBUS.DBUS.MESSAGE_TYPE_SIGNAL" \
        " message, decomposed into a list of the slash-separated components without the slashes."
        path = ct.POINTER(ct.c_char_p)()
        if not dbus.dbus_message_get_path_decomposed(self._dbobj, ct.byref(path)) :
            raise CallFailed("dbus_message_get_path_decomposed")
        #end if
        if bool(path) :
            result = []
            i = 0
            while True :
                entry = path[i]
                if entry == None :
                    break
                result.append(entry.decode())
                i += 1
            #end while
            dbus.dbus_free_string_array(path)
        else :
            result = None
        #end if
        return \
            result
    #end path_decomposed

    @property
    def interface(self) :
        "the interface name for a DBUS.MESSAGE_TYPE_METHOD_CALL or DBUS.MESSAGE_TYPE_SIGNAL" \
        " message."
        result = dbus.dbus_message_get_interface(self._dbobj)
        if result != None :
            result = result.decode()
        #end if
        return \
            result
    #end interface

    @interface.setter
    def interface(self, iface) :
        if not dbus.dbus_message_set_interface(self._dbobj, (lambda : None, lambda : iface.encode())[iface != None]()) :
            raise CallFailed("dbus_message_set_interface")
        #end if
    #end interface

    def has_interface(self, iface) :
        return \
            dbus.dbus_message_has_interface(self._dbobj, iface.encode()) != 0
    #end has_interface

    @property
    def member(self) :
        "the method name for a DBUS.MESSAGE_TYPE_METHOD_CALL message or the signal" \
        " name for DBUS.MESSAGE_TYPE_SIGNAL."
        result = dbus.dbus_message_get_member(self._dbobj)
        if result != None :
            result = result.decode()
        #end if
        return \
            result
    #end member

    @member.setter
    def member(self, member) :
        if not dbus.dbus_message_set_member(self._dbobj, (lambda : None, lambda : member.encode())[member != None]()) :
            raise CallFailed("dbus_message_set_member")
        #end if
    #end member

    def has_member(self, member) :
        return \
            dbus.dbus_message_has_member(self._dbobj, member.encode()) != 0
    #end has_member

    @property
    def error_name(self) :
        "the error name for a DBUS.MESSAGE_TYPE_ERROR message."
        result = dbus.dbus_message_get_error_name(self._dbobj)
        if result != None :
            result = result.decode()
        #end if
        return \
            result
    #end error_name

    @error_name.setter
    def error_name(self, error_name) :
        if not dbus.dbus_message_set_error_name(self._dbobj, (lambda : None, lambda : error_name.encode())[error_name != None]()) :
            raise CallFailed("dbus_message_set_error_name")
        #end if
    #end error_name

    @property
    def destination(self) :
        "the bus name that the message is to be sent to."
        result = dbus.dbus_message_get_destination(self._dbobj)
        if result != None :
            result = result.decode()
        #end if
        return \
            result
    #end destination

    @destination.setter
    def destination(self, destination) :
        if not dbus.dbus_message_set_destination(self._dbobj, (lambda : None, lambda : destination.encode())[destination != None]()) :
            raise CallFailed("dbus_message_set_destination")
        #end if
    #end destination

    @property
    def sender(self) :
        result = dbus.dbus_message_get_sender(self._dbobj)
        if result != None :
            result = result.decode()
        #end if
        return \
            result
    #end sender

    @sender.setter
    def sender(self, sender) :
        if not dbus.dbus_message_set_sender(self._dbobj, (lambda : None, lambda : sender.encode())[sender != None]()) :
            raise CallFailed("dbus_message_set_sender")
        #end if
    #end sender

    @property
    def signature(self) :
        result = dbus.dbus_message_get_signature(self._dbobj)
        if result != None :
            result = DBUS.Signature(result.decode())
        #end if
        return \
            result
    #end signature

    def is_method_call(self, iface, method) :
        return \
            dbus.dbus_message_is_method_call(self._dbobj, iface.encode(), method.encode()) != 0
    #end is_method_call

    def is_signal(self, iface, signal_name) :
        return \
            dbus.dbus_message_is_signal(self._dbobj, iface.encode(), signal_name.encode()) != 0
    #end is_signal

    def is_error(self, iface, error_name) :
        return \
            dbus.dbus_message_is_error(self._dbobj, error_name.encode()) != 0
    #end is_error

    def has_destination(self, iface, destination) :
        return \
            dbus.dbus_message_has_destination(self._dbobj, destination.encode()) != 0
    #end has_destination

    def has_sender(self, iface, sender) :
        return \
            dbus.dbus_message_has_sender(self._dbobj, sender.encode()) != 0
    #end has_sender

    def has_signature(self, iface, signature) :
        return \
            dbus.dbus_message_has_signature(self._dbobj, signature.encode()) != 0
    #end has_signature

    def set_error(self, error) :
        "fills in error if this is an error message, else does nothing. Returns" \
        " whether it was an error message or not."
        if not isinstance(error, Error) :
            raise TypeError("error must be an Error")
        #end if
        return \
            dbus.dbus_set_error_from_message(error._dbobj, self._dbobj) != 0
    #end set_error

    @property
    def contains_unix_fds(self) :
        return \
            dbus.dbus_message_contains_unix_fds(self._dbobj) != 0
    #end contains_unix_fds

    @property
    def serial(self) :
        "the serial number of the Message, to be referenced in replies."
        return \
            dbus.dbus_message_get_serial(self._dbobj)
    #end serial

    @serial.setter
    def serial(self, serial) :
        dbus.dbus_message_set_serial(self._dbobj, serial)
    #end serial

    @property
    def reply_serial(self) :
        "the serial number of the original Message that that this" \
        " DBUS.MESSAGE_TYPE_METHOD_RETURN message is a reply to."
        return \
            dbus.dbus_message_get_reply_serial(self._dbobj)
    #end reply_serial

    @reply_serial.setter
    def reply_serial(self, serial) :
        if not dbus.dbus_message_set_reply_serial(self._dbobj, serial) :
            raise CallFailed("dbus_message_set_reply_serial")
        #end if
    #end serial

    def lock(self) :
        dbus.dbus_message_lock(self._dbobj)
    #end lock

    def return_borrowed(self) :
        assert self._borrowed and self._conn != None
        dbus.dbus_connection_return_message(self._conn._dbobj, self._dbobj)
        self._borrowed = False
    #end return_borrowed

    def steal_borrowed(self) :
        assert self._borrowed and self._conn != None
        dbus.dbus_connection_steal_borrowed_message(self._conn._dbobj, self._dbobj)
        self._borrowed = False
        return \
            self
    #end steal_borrowed

    # TODO: allocate/free data slot -- static methods
    #    (freeing slot can set passed-in var to -1 on actual free; do I care?)
    # TODO: set/get data

    @staticmethod
    def type_from_string(type_str) :
        "returns a MESSAGE_TYPE_xxx value."
        return \
            dbus.dbus_message_type_from_string(type_str.encode())
    #end type_from_string

    @staticmethod
    def type_to_string(type) :
        "type is a MESSAGE_TYPE_xxx value."
        return \
            dbus.dbus_message_type_to_string(type).decode()
    #end type_to_string

    def marshal(self) :
        "serializes this Message into the wire protocol format and returns a bytes object."
        buf = ct.POINTER(ct.c_ubyte)()
        nr_bytes = ct.c_int()
        if not dbus.dbus_message_marshal(self._dbobj, ct.byref(buf), ct.byref(nr_bytes)) :
            raise CallFailed("dbus_message_marshal")
        #end if
        result = bytearray(nr_bytes.value)
        ct.memmove \
          (
            ct.addressof((ct.c_ubyte * nr_bytes.value).from_buffer(result)),
            buf,
            nr_bytes.value
          )
        dbus.dbus_free(buf)
        return \
            result
    #end marshal

    @classmethod
    def demarshal(celf, buf, error = None) :
        "deserializes a bytes or array-of-bytes object from the wire protocol" \
        " format into a Message object."
        error, my_error = _get_error(error)
        if isinstance(buf, bytes) :
            baseadr = ct.cast(buf, ct.c_void_p).value
        elif isinstance(buf, bytearray) :
            baseadr = ct.addressof((ct.c_ubyte * len(buf)).from_buffer(buf))
        elif isinstance(buf, array.array) and buf.typecode == "B" :
            baseadr = buf.buffer_info()[0]
        else :
            raise TypeError("buf is not bytes, bytearray or array.array of bytes")
        #end if
        msg = dbus.dbus_message_demarshal(baseadr, len(buf), error._dbobj)
        my_error.raise_if_set()
        if msg != None :
            msg = celf(msg)
        #end if
        return \
            msg
    #end demarshal

    @classmethod
    def demarshal_bytes_needed(celf, buf) :
        "the number of bytes needed to deserialize a bytes or array-of-bytes" \
        " object from the wire protocol format."
        if isinstance(buf, bytes) :
            baseadr = ct.cast(buf, ct.c_void_p).value
        elif isinstance(buf, bytearray) :
            baseadr = ct.addressof((ct.c_ubyte * len(buf)).from_buffer(buf))
        elif isinstance(buf, array.array) and buf.typecode == "B" :
            baseadr = buf.buffer_info()[0]
        else :
            raise TypeError("buf is not bytes, bytearray or array.array of bytes")
        #end if
        return \
            dbus.dbus_message_demarshal_bytes_needed(baseadr, len(buf))
    #end demarshal_bytes_needed

    @property
    def interactive_authorization(self) :
        return \
            dbus.dbus_message_get_interactive_authorization(self._dbobj)
    #end interactive_authorization

    @interactive_authorization.setter
    def interactive_authorization(self, allow) :
        dbus.dbus_message_set_interactive_authorization(self._dbobj, allow)
    #end interactive_authorization

#end Message

class PendingCall :
    "wrapper around a DBusPendingCall object. This represents a pending reply" \
    " message that hasn’t been received yet. Do not instantiate directly; libdbus" \
    " creates these as the result from calling send_with_reply() on a Message."
    # <https://dbus.freedesktop.org/doc/api/html/group__DBusPendingCall.html>

    __slots__ = \
      (
        "__weakref__",
        "_dbobj",
        "_w_conn",
        "_wrap_notify",
        "_wrap_free",
        "_awaiting",
      ) # to forestall typos

    _instances = WeakValueDictionary()

    def __new__(celf, _dbobj, _conn) :
        self = celf._instances.get(_dbobj)
        if self == None :
            self = super().__new__(celf)
            self._dbobj = _dbobj
            self._w_conn = weak_ref(_conn)
            self._wrap_notify = None
            self._wrap_free = None
            self._awaiting = None
            celf._instances[_dbobj] = self
        else :
            dbus.dbus_pending_call_unref(self._dbobj)
              # lose extra reference created by caller
        #end if
        return \
            self
    #end __new__

    def __del__(self) :
        if self._dbobj != None :
            dbus.dbus_pending_call_unref(self._dbobj)
            self._dbobj = None
        #end if
    #end __del__

    def set_notify(self, function, user_data, free_user_data = None) :
        "sets the callback for libdbus to notify you that the pending message" \
        " has become available. Note: it appears to be possible for your notifier" \
        " to be called spuriously before the message is actually available."

        w_self = weak_ref(self)

        def wrap_notify(c_pending, c_user_data) :
            function(_wderef(w_self, "pending call"), user_data)
        #end _wrap_notify

        def wrap_free(c_user_data) :
            free_user_data(user_data)
        #end _wrap_free

    #begin set_notify
        if function != None :
            self._wrap_notify = DBUS.PendingCallNotifyFunction(wrap_notify)
        else :
            self._wrap_notify = None
        #end if
        if free_user_data != None :
            self._wrap_free = DBUS.FreeFunction(wrap_free)
        else :
            self._wrap_free = None
        #end if
        if not dbus.dbus_pending_call_set_notify(self._dbobj, self._wrap_notify, None, self._wrap_free) :
            raise CallFailed("dbus_pending_call_set_notify")
        #end if
    #end set_notify

    def cancel(self) :
        "tells libdbus you no longer care about the pending incoming message."
        dbus.dbus_pending_call_cancel(self._dbobj)
        if self._awaiting != None :
            # This probably shouldn’t occur. Looking at the source of libdbus,
            # it doesn’t keep track of any “cancelled” state for the PendingCall,
            # it just detaches it from any notifications about an incoming reply.
            self._awaiting.cancel()
        #end if
    #end cancel

    @property
    def completed(self) :
        "checks whether the pending message is available."
        return \
            dbus.dbus_pending_call_get_completed(self._dbobj) != 0
    #end completed

    def steal_reply(self) :
        "retrieves the Message, assuming it is actually available." \
        " You should check PendingCall.completed returns True first."
        result = dbus.dbus_pending_call_steal_reply(self._dbobj)
        if result != None :
            result = Message(result)
        #end if
        return \
            result
    #end steal_reply

    async def await_reply(self) :
        "retrieves the Message. If it is not yet available, suspends the" \
        " coroutine (letting the event loop do other things) until it becomes" \
        " available. On a timeout, libdbus will construct and return an error" \
        " return message."
        conn = self._w_conn()
        assert conn != None, "parent Connection has gone away"
        assert conn.loop != None, "no event loop on parent Connection to attach coroutine to"
        if self._wrap_notify != None or self._awaiting != None :
            raise asyncio.InvalidStateError("there is already a notify set on this PendingCall")
        #end if
        done = conn.loop.create_future()
        self._awaiting = done

        def pending_done(pending, wself) :
            if not done.done() : # just in case of self.cancel() being called
                self = wself()
                # Note it seems to be possible for callback to be triggered spuriously
                if self != None and self.completed :
                    done.set_result(self.steal_reply())
                #end if
            #end if
        #end pending_done

        self.set_notify(pending_done, weak_ref(self))
          # avoid reference circularity self → pending_done → self
        reply = await done
        return \
            reply
    #end await_reply

    def block(self) :
        "blocks the current thread until the pending message has become available."
        dbus.dbus_pending_call_block(self._dbobj)
    #end block

    # TODO: data slots (static methods), get/set data

#end PendingCall

class Error :
    "wrapper around a DBusError object. You can create one by calling the init method."
    # <https://dbus.freedesktop.org/doc/api/html/group__DBusErrors.html>

    __slots__ = ("_dbobj",) # to forestall typos

    def __init__(self) :
        dbobj = DBUS.Error()
        dbus.dbus_error_init(dbobj)
        self._dbobj = dbobj
    #end __init__

    def __del__(self) :
        if self._dbobj != None :
            dbus.dbus_error_free(self._dbobj)
            self._dbobj = None
        #end if
    #end __del__

    @classmethod
    def init(celf) :
        "for consistency with other classes that don’t want caller to instantiate directly."
        return \
            celf()
    #end init

    def set(self, name, msg) :
        "fills in the error name and message."
        dbus.dbus_set_error(self._dbobj, name.encode(), b"%s", msg.encode())
    #end set

    @property
    def is_set(self) :
        "has the Error been filled in."
        return \
            dbus.dbus_error_is_set(self._dbobj) != 0
    #end is_set

    def has_name(self, name) :
        "has the Error got the specified name."
        return \
            dbus.dbus_error_has_name(self._dbobj, name.encode()) != 0
    #end has_name

    @property
    def name(self) :
        "the name of the Error, if it has been filled in."
        return \
            (lambda : None, lambda : self._dbobj.name.decode())[self._dbobj.name != None]()
    #end name

    @property
    def message(self) :
        "the message string for the Error, if it has been filled in."
        return \
            (lambda : None, lambda : self._dbobj.message.decode())[self._dbobj.message != None]()
    #end message

    def raise_if_set(self) :
        "raises a DBusError exception if this Error has been filled in."
        if self.is_set :
            raise DBusError(self.name, self.message)
        #end if
    #end raise_if_set

    def set_from_message(self, message) :
        "fills in this Error object from message if it is an error message." \
        " Returns whether it was or not."
        if not isinstance(message, Message) :
            raise TypeError("message must be a Message")
        #end if
        return \
            dbus.dbus_set_error_from_message(self._dbobj, message._dbobj) != 0
    #end set_from_message

#end Error

class AddressEntries :
    "wrapper for arrays of DBusAddressEntry values. Do not instantiate directly;" \
    " get from AddressEntries.parse. This object behaves like an array; you can obtain" \
    " the number of elements with len(), and use array subscripting to access the elements."
    # <https://dbus.freedesktop.org/doc/api/html/group__DBusAddress.html>

    __slots__ = ("__weakref__", "_dbobj", "_nrelts") # to forestall typos

    def __init__(self, _dbobj, _nrelts) :
        self._dbobj = _dbobj
        self._nrelts = _nrelts
    #end __init__

    def __del__(self) :
        if self._dbobj != None :
            dbus.dbus_address_entries_free(self._dbobj)
            self._dbobj = None
        #end if
    #end __del__

    class Entry :
        "a single AddressEntry. Do not instantiate directly; get from AddressEntries[]." \
        " This object behaves like a dictionary in that you can use keys to get values;" \
        " however, there is no libdbus API to check what keys are present; unrecognized" \
        " keys return a value of None."

        __slots__ = ("_dbobj", "_parent", "_index") # to forestall typos

        def __init__(self, _parent, _index) :
            self._dbobj = _parent._dbobj
            self._parent = weak_ref(_parent)
            self._index = _index
        #end __init__

        @property
        def method(self) :
            assert self._parent() != None, "AddressEntries object has gone"
            result = dbus.dbus_address_entry_get_method(self._dbobj[self._index])
            if result != None :
                result = result.decode()
            #end if
            return \
                result
        #end method

        def get_value(self, key) :
            assert self._parent() != None, "AddressEntries object has gone"
            c_result = dbus.dbus_address_entry_get_value(self._dbobj[self._index], key.encode())
            if c_result != None :
                result = c_result.decode()
            else :
                result = None
            #end if
            return \
                result
        #end get_value
        __getitem__ = get_value

    #end Entry

    @classmethod
    def parse(celf, address, error = None) :
        error, my_error = _get_error(error)
        c_result = ct.POINTER(ct.c_void_p)()
        nr_elts = ct.c_int()
        if not dbus.dbus_parse_address(address.encode(), ct.byref(c_result), ct.byref(nr_elts), error._dbobj) :
            c_result.contents = None
            nr_elts.value = 0
        #end if
        my_error.raise_if_set()
        if c_result.contents != None :
            result = celf(c_result, nr_elts.value)
        else :
            result = None
        #end if
        return \
            result
    #end parse

    def __len__(self) :
        return \
            self._nrelts
    #end __len__

    def __getitem__(self, index) :
        if not isinstance(index, int) or index < 0 or index >= self._nrelts :
            raise IndexError("AddressEntries[%d] out of range" % index)
        #end if
        return \
            type(self).Entry(self, index)
    #end __getitem__

#end AddressEntries

def address_escape_value(value) :
    c_result = dbus.dbus_address_escape_value(value.encode())
    if c_result == None :
        raise CallFailed("dbus_address_escape_value")
    #end if
    result = ct.cast(c_result, ct.c_char_p).value.decode()
    dbus.dbus_free(c_result)
    return \
        result
#end address_escape_value

def address_unescape_value(value, error = None) :
    error, my_error = _get_error(error)
    c_result = dbus.dbus_address_unescape_value(value.encode(), error._dbobj)
    my_error.raise_if_set()
    if c_result != None :
        result = ct.cast(c_result, ct.c_char_p).value.decode()
        dbus.dbus_free(c_result)
    elif not error.is_set :
        raise CallFailed("dbus_address_unescape_value")
    else :
        result = None
    #end if
    return \
        result
#end address_unescape_value

def format_rule(rule) :
    "convenience routine to allow a match rule to be expressed as either" \
    " a dict of {key : value} or the usual string \"key='value'\", automatically" \
    " converting the former to the latter."

    def escape_val(val) :
        if "," in val :
            if "'" in val :
                out = "'"
                in_quotes = True
                for ch in val :
                    if ch == "'" :
                        if in_quotes :
                            out += "'"
                            in_quotes = False
                        #end if
                        out += "\\'"
                    else :
                        if not in_quotes :
                            out += "'"
                            in_quotes = True
                        #end if
                        out += ch
                    #end if
                #end for
                if in_quotes :
                    out += "'"
                #end if
            else :
                out = "'" + val + "'"
            #end if
        else :
            out = ""
            for ch in val :
                if ch in ("\\", "'") :
                    out += "\\"
                #end if
                out += ch
            #end for
        #end if
        return \
            out
    #end escape_val

#begin format_rule
    if isinstance(rule, str) :
        pass
    elif isinstance(rule, dict) :
        rule = ",".join("%s=%s" % (k, escape_val(rule[k])) for k in sorted(rule))
          # sort to ensure some kind of consistent ordering, just for
          # appearance’s sake
    else :
        raise TypeError("rule “%s” must be a dict or string" % repr(rule))
    #end if
    return \
        rule
#end format_rule

class _RuleParser :
    # internal definitions for rule parsing.

    class PARSE(enum.Enum) :
        EXPECT_NAME = 1
        EXPECT_UNQUOTED_VALUE = 2
        EXPECT_ESCAPED = 3
        EXPECT_QUOTED_VALUE = 4
    #end PARSE

    @classmethod
    def unformat_rule(celf, rule) :
        "converts a match rule string from the standard syntax to a dict of {key : value} entries."
        if isinstance(rule, dict) :
            pass
        elif isinstance(rule, str) :
            PARSE = celf.PARSE
            parsed = {}
            chars = iter(rule)
            state = PARSE.EXPECT_NAME
            curname = None
            curval = None
            while True :
                ch = next(chars, None)
                if ch == None :
                    if state == PARSE.EXPECT_ESCAPED :
                        raise SyntaxError("missing character after backslash")
                    elif state == PARSE.EXPECT_QUOTED_VALUE :
                        raise SyntaxError("missing closing apostrophe")
                    else : # state in (PARSE.EXPECT_NAME, PARSE.EXPECT_UNQUOTED_VALUE)
                        if curname != None :
                            if curval != None :
                                if curname in parsed :
                                    raise SyntaxError("duplicated attribute “%s”" % curname)
                                #end if
                                parsed[curname] = curval
                            else :
                                raise SyntaxError("missing value for attribute “%s”" % curname)
                            #end if
                        #end if
                    #end if
                    break
                #end if
                if state == PARSE.EXPECT_ESCAPED :
                    if ch == "'" :
                        usech = ch
                        nextch = None
                    else :
                        usech = "\\"
                        nextch = ch
                    #end if
                    ch = usech
                    if curval == None :
                        curval = ch
                    else :
                        curval += ch
                    #end if
                    ch = nextch # None indicates already processed
                    state = PARSE.EXPECT_UNQUOTED_VALUE
                #end if
                if ch != None :
                    if ch == "," and state != PARSE.EXPECT_QUOTED_VALUE :
                        if state == PARSE.EXPECT_UNQUOTED_VALUE :
                            if curname in parsed :
                                raise SyntaxError("duplicated attribute “%s”" % curname)
                            #end if
                            if curval == None :
                                curval = ""
                            #end if
                            parsed[curname] = curval
                            curname = None
                            curval = None
                            state = PARSE.EXPECT_NAME
                        else :
                            raise SyntaxError("unexpected comma")
                        #end if
                    elif ch == "\\" and state != PARSE.EXPECT_QUOTED_VALUE :
                        if state == PARSE.EXPECT_UNQUOTED_VALUE :
                            state = PARSE.EXPECT_ESCAPED
                        else :
                            raise SyntaxError("unexpected backslash")
                        #end if
                    elif ch == "=" and state != PARSE.EXPECT_QUOTED_VALUE :
                        if curname == None :
                            raise SyntaxError("empty attribute name")
                        #end if
                        if state == PARSE.EXPECT_NAME :
                            state = PARSE.EXPECT_UNQUOTED_VALUE
                        else :
                            raise SyntaxError("unexpected equals sign")
                        #end if
                    elif ch == "'" :
                        if state == PARSE.EXPECT_UNQUOTED_VALUE :
                            state = PARSE.EXPECT_QUOTED_VALUE
                        elif state == PARSE.EXPECT_QUOTED_VALUE :
                            state = PARSE.EXPECT_UNQUOTED_VALUE
                        else :
                            raise SyntaxError("unexpected apostrophe")
                        #end if
                    else :
                        if state == PARSE.EXPECT_NAME :
                            if curname == None :
                                curname = ch
                            else :
                                curname += ch
                            #end if
                        elif state in (PARSE.EXPECT_QUOTED_VALUE, PARSE.EXPECT_UNQUOTED_VALUE) :
                            if curval == None :
                                curval = ch
                            else :
                                curval += ch
                            #end if
                        else :
                            raise AssertionError("shouldn’t occur: parse state %s" % repr(state))
                        #end if
                    #end if
                #end if
            #end while
            rule = parsed
        else :
            raise TypeError("rule “%s” must be a dict or string" % repr(rule))
        #end if
        return \
            rule
    #end unformat_rule

#end _RuleParser

unformat_rule = _RuleParser.unformat_rule
del _RuleParser

def matches_rule(message, rule, destinations = None) :
    "does Message message match against the specified rule."
    if not isinstance(message, Message) :
        raise TypeError("message must be a Message")
    #end if
    rule = unformat_rule(rule)
    eavesdrop = rule.get("eavesdrop", "false") == "true"

    def match_message_type(expect, actual) :
        return \
            actual == Message.type_from_string(expect)
    #end match_message_type

    def match_path_namespace(expect, actual) :
        return \
            (
                actual != None
            and
                (
                    expect == actual
                or
                    actual.startswith(expect) and (expect == "/" or actual[len(expect)] == "/")
                )
            )
    #end match_path_namespace

    def match_dotted_namespace(expect, actual) :
        return \
            (
                actual != None
            and
                (
                    expect == actual
                or
                    actual.startswith(expect) and actual[len(expect)] == "."
                )
            )
    #end match_dotted_namespace

    def get_nth_arg(msg, n, expect_types) :
        msg_signature = parse_signature(msg.signature)
        if n >= len(msg_signature) :
            raise IndexError("arg nr %d beyond nr args %d" % (n, len(msg_signature)))
        #end if
        val = msg.all_objects[n]
        valtype = msg_signature[n]
        if valtype not in expect_types :
            if False :
                raise TypeError \
                  (
                        "expecting one of types %s, not %s for arg %d val %s"
                    %
                        ((repr(expect_types), repr(valtype), n, repr(val)))
                  )
            #end if
            val = None # never match
        #end if
        return \
            val
    #end get_nth_arg

    def get_arg_0_str(message) :
        return \
            get_nth_arg(message, 0, [BasicType(TYPE.STRING)])
    #end get_arg_0_str

    def match_arg_paths(expect, actual) :
        return \
            (
                actual != None
            and
                (
                    expect == actual
                or
                    expect.endswith("/") and actual.startswith(expect)
                or
                    actual.endswith("/") and expect.startswith(actual)
                )
            )
    #end match_arg_paths

    match_types = \
        ( # note that message attribute value of None will fail to match
          # any expected string value, which is exactly what we want
            ("type", None, match_message_type, None),
            ("sender", None, operator.eq, None),
            ("interface", None, operator.eq, None),
            ("member", None, operator.eq, None),
            ("path", None, operator.eq, None),
            ("destination", None, operator.eq, None),
            ("path_namespace", "path", match_path_namespace, None),
            ("arg0namespace", None, match_dotted_namespace, get_arg_0_str),
            # “arg«n»path” handled specially below
        )

#begin matches_rule
    keys_used = set(rule.keys()) - {"eavesdrop"}
    matches = \
        (
            eavesdrop
        or
            destinations == None
        or
            message.destination == None
        or
            message.destination in destinations
        )
    if matches :
        try_matching = iter(match_types)
        while True :
            try_rule = next(try_matching, None)
            if try_rule == None :
                break
            rulekey, attrname, action, accessor = try_rule
            if attrname == None :
                attrname = rulekey
            #end if
            if rulekey in rule :
                if accessor != None :
                    val = accessor(message)
                else :
                    val = getattr(message, attrname)
                #end if
                keys_used.remove(rulekey)
                if not action(rule[rulekey], val) :
                    matches = False
                    break
                #end if
            #end if
        #end while
    #end if
    if matches :
        try_matching = iter(rule.keys())
        while True :
            try_key = next(try_matching, None)
            if try_key == None :
                break
            if try_key.startswith("arg") and not try_key.endswith("namespace") :
                argnr = try_key[3:]
                is_path = argnr.endswith("path")
                if is_path :
                    argnr = argnr[:-4]
                #end if
                argnr = int(argnr)
                if not (0 <= argnr < 64) :
                    raise ValueError("argnr %d out of range" % argnr)
                #end if
                argval = get_nth_arg \
                  (
                    message,
                    argnr,
                    [BasicType(TYPE.STRING)] + ([], [BasicType(TYPE.OBJECT_PATH)])[is_path]
                  )
                keys_used.remove(try_key)
                if not (operator.eq, match_arg_paths)[is_path](rule[try_key], argval) :
                    matches = False
                    break
                #end if
            #end if
        #end while
    #end if
    if matches and len(keys_used) != 0 :
        # fixme: not checking for unrecognized rule keys if I didn’t try matching them all
        raise KeyError("unrecognized rule keywords: %s" % ", ".join(sorted(keys_used)))
    #end if
    return \
        matches
#end matches_rule

class SignatureIter :
    "wraps a DBusSignatureIter object. Do not instantiate directly; use the init" \
    " and recurse methods."
    # <https://dbus.freedesktop.org/doc/api/html/group__DBusSignature.html>

    __slots__ = ("_dbobj", "_signature", "_startiter") # to forestall typos

    @classmethod
    def init(celf, signature) :
        self = celf()
        self._signature = ct.c_char_p(signature.encode()) # need to ensure storage stays valid
        dbus.dbus_signature_iter_init(self._dbobj, self._signature)
        return \
            self
    #end init

    def __init__(self) :
        self._dbobj = DBUS.SignatureIter()
        self._signature = None # caller will set as necessary
        self._startiter = True
    #end __init__

    def __iter__(self) :
        return \
            self
    #end __iter__

    def __next__(self) :
        if self._startiter :
            self._startiter = False
        else :
            self.next()
        #end if
        return \
            self
    #end __next__

    def next(self) :
        if dbus.dbus_signature_iter_next(self._dbobj) == 0 :
            raise StopIteration("end of signature iterator")
        #end if
        self._startiter = False
        return \
            self
    #end next

    def recurse(self) :
        subiter = type(self)()
        dbus.dbus_signature_iter_recurse(self._dbobj, subiter._dbobj)
        return \
            subiter
    #end recurse

    @property
    def current_type(self) :
        return \
            dbus.dbus_signature_iter_get_current_type(self._dbobj)
    #end current_type

    @property
    def signature(self) :
        c_result = dbus.dbus_signature_iter_get_signature(self._dbobj)
        result = ct.cast(c_result, ct.c_char_p).value.decode()
        dbus.dbus_free(c_result)
        return \
            result
    #end signature

    @property
    def parsed_signature(self) :
        return \
            parse_single_signature(self.signature)
    #end parsed_signature

    @property
    def element_type(self) :
        return \
            dbus.dbus_signature_iter_get_element_type(self._dbobj)
    #end element_type

#end SignatureIter

def signature_validate(signature, error = None) :
    "is signature a valid sequence of zero or more complete types."
    error, my_error = _get_error(error)
    result = dbus.dbus_signature_validate(signature.encode(), error._dbobj) != 0
    my_error.raise_if_set()
    return \
        result
#end signature_validate

def parse_signature(signature) :
    "convenience routine for parsing a signature string into a list of Type()" \
    " instances."

    def process_subsig(sigelt) :
        elttype = sigelt.current_type
        if elttype in DBUS.basic_to_ctypes :
            result = BasicType(TYPE(elttype))
        elif elttype == DBUS.TYPE_ARRAY :
            if sigelt.element_type == DBUS.TYPE_DICT_ENTRY :
                subsig = sigelt.recurse()
                subsubsig = subsig.recurse()
                keytype = process_subsig(next(subsubsig))
                valuetype = process_subsig(next(subsubsig))
                result = DictType(keytype, valuetype)
            else :
                subsig = sigelt.recurse()
                result = ArrayType(process_subsig(next(subsig)))
            #end if
        elif elttype == DBUS.TYPE_STRUCT :
            result = []
            subsig = sigelt.recurse()
            for subelt in subsig :
                result.append(process_subsig(subelt))
            #end for
            result = StructType(*result)
        elif elttype == DBUS.TYPE_VARIANT :
            result = VariantType()
        else :
            raise RuntimeError("unrecognized type %s" % bytes((elttype,)))
        #end if
        return \
            result
    #end process_subsig

#begin parse_signature
    if isinstance(signature, (tuple, list)) :
        if not all(isinstance(t, Type) for t in signature) :
            raise TypeError("signature is list containing non-Type objects")
        #end if
        result = signature
    elif isinstance(signature, Type) :
        result = [signature]
    elif isinstance(signature, str) :
        signature_validate(signature)
        result = []
        if len(signature) != 0 :
            sigiter = SignatureIter.init(signature)
            for elt in sigiter :
                result.append(process_subsig(elt))
            #end for
        #end if
    else :
        raise TypeError("signature must be list or str")
    #end if
    return \
        result
#end parse_signature

def parse_single_signature(signature) :
    result = parse_signature(signature)
    if len(result) != 1 :
        raise ValueError("only single type expected")
    #end if
    return \
        result[0]
#end parse_single_signature

def unparse_signature(signature) :
    "converts a signature from parsed form to string form."
    signature = parse_signature(signature)
    if not isinstance(signature, (tuple, list)) :
        signature = [signature]
    #end if
    return \
        DBUS.Signature("".join(t.signature for t in signature))
#end unparse_signature

def signature_validate_single(signature, error = None) :
    "is signature a single valid type."
    error, my_error = _get_error(error)
    result = dbus.dbus_signature_validate_single(signature.encode(), error._dbobj) != 0
    my_error.raise_if_set()
    return \
        result
#end signature_validate_single

def type_is_valid(typecode) :
    return \
        dbus.dbus_type_is_valid(typecode) != 0
#end type_is_valid

def type_is_basic(typecode) :
    return \
        dbus.dbus_type_is_basic(typecode) != 0
#end type_is_basic

def type_is_container(typecode) :
    return \
        dbus.dbus_type_is_container(typecode) != 0
#end type_is_container

def type_is_fixed(typecode) :
    return \
        dbus.dbus_type_is_fixed(typecode) != 0
#end type_is_fixed

def type_is_fixed_array_elttype(typecode) :
    "is typecode suitable as the element type of a fixed_array."
    return \
        type_is_fixed(typecode) and typecode != DBUS.TYPE_UNIX_FD
#end type_is_fixed_array_elttype

# syntax validation <https://dbus.freedesktop.org/doc/api/html/group__DBusSyntax.html>

def validate_path(path, error = None) :
    error, my_error = _get_error(error)
    result = dbus.dbus_validate_path(path.encode(), error._dbobj) != 0
    my_error.raise_if_set()
    return \
        result
#end validate_path

def valid_path(path) :
    "returns path if valid, raising appropriate exception if not."
    validate_path(path)
    return \
        path
#end valid_path

def split_path(path) :
    "convenience routine for splitting a path into a list of components."
    if isinstance(path, (tuple, list)) :
        result = path # assume already split
    elif path == "/" :
        result = []
    else :
        if not path.startswith("/") or path.endswith("/") :
            raise DBusError(DBUS.ERROR_INVALID_ARGS, "invalid path %s" % repr(path))
        #end if
        result = path.split("/")[1:]
    #end if
    return \
        result
#end split_path

def unsplit_path(path) :
    path = split_path(path)
    if len(path) != 0 :
        result = DBUS.ObjectPath("".join("/" + component for component in path))
    else :
        result = "/"
    #end if
    return \
        result
#end unsplit_path

def validate_interface(name, error = None) :
    error, my_error = _get_error(error)
    result = dbus.dbus_validate_interface(name.encode(), error._dbobj) != 0
    my_error.raise_if_set()
    return \
        result
#end validate_interface

def valid_interface(name) :
    "returns name if it is a valid interface name, raising appropriate exception if not."
    validate_interface(name)
    return \
        name
#end valid_interface

def validate_member(name, error = None) :
    error, my_error = _get_error(error)
    result = dbus.dbus_validate_member(name.encode(), error._dbobj) != 0
    my_error.raise_if_set()
    return \
        result
#end validate_member

def valid_member(name) :
    "returns name if it is a valid member name, raising appropriate exception if not."
    validate_member(name)
    return \
        name
#end valid_member

def validate_error_name(name, error = None) :
    error, my_error = _get_error(error)
    result = dbus.dbus_validate_error_name(name.encode(), error._dbobj) != 0
    my_error.raise_if_set()
    return \
        result
#end validate_error_name

def valid_error_name(name) :
    "returns name if it is a valid error name, raising appropriate exception if not."
    validate_error_name(name)
    return \
        name
#end valid_error_name

def validate_bus_name(name, error = None) :
    error, my_error = _get_error(error)
    result = dbus.dbus_validate_bus_name(name.encode(), error._dbobj) != 0
    my_error.raise_if_set()
    return \
        result
#end validate_bus_name

def valid_bus_name(name) :
    "returns name if it is a valid bus name, raising appropriate exception if not."
    validate_bus_name(name)
    return \
        name
#end valid_bus_name

def validate_utf8(alleged_utf8, error = None) :
    "alleged_utf8 must be null-terminated bytes."
    error, my_error = _get_error(error)
    result = dbus.dbus_validate_utf8(alleged_utf8, error._dbobj) != 0
    my_error.raise_if_set()
    return \
        result
#end validate_utf8

def valid_utf8(alleged_utf8) :
    "returns alleged_utf8 if it is a valid utf-8 bytes value, raising" \
    " appropriate exception if not."
    validate_utf8(alleged_utf8)
    return \
        alleged_utf8
#end valid_utf8

#+
# Introspection representation
#-

class _TagCommon :

    def get_annotation(self, name) :
        "returns the value of the annotation with the specified name, or None" \
        " if none could be found"
        annots = iter(self.annotations)
        while True :
            annot = next(annots, None)
            if annot == None :
                result = None
                break
            #end if
            if annot.name == name :
                result = annot.value
                break
            #end if
        #end while
        return \
            result
    #end get_annotation

    @property
    def is_deprecated(self) :
        "is this interface/method/signal etc deprecated."
        return \
            self.get_annotation("org.freedesktop.DBus.Deprecated") == "true"
    #end is_deprecated

    def __repr__(self) :
        celf = type(self)
        return \
            (
                    "%s(%s)"
                %
                    (
                        celf.__name__,
                        ", ".join
                          (
                                "%s = %s"
                            %
                                (name, repr(getattr(self, name)))
                            for name in celf.__slots__
                          ),
                    )
            )
    #end __repr__

#end _TagCommon

class Introspection(_TagCommon) :
    "high-level wrapper for the DBUS.INTERFACE_INTROSPECTABLE interface."

    __slots__ = ("name", "interfaces", "nodes", "annotations")

    tag_name = "node"
    tag_attrs = ("name",)
    tag_attrs_optional = {"name"}

    class DIRECTION(enum.Enum) :
        "argument direction."
        IN = "in" # client to server
        OUT = "out" # server to client
    #end DIRECTION

    class ACCESS(enum.Enum) :
        "property access."
        READ = "read"
        WRITE = "write"
        READWRITE = "readwrite"
    #end ACCESS

    class PROP_CHANGE_NOTIFICATION(enum.Enum) :
        "how/if a changed property emits a notification signal."
        NEW_VALUE = "true" # notification includes new value
        INVALIDATES = "invalidates" # notification does not include new value
        CONST = "const" # property shouldn’t change
        NONE = "false" # does not notify changes
    #end PROP_CHANGE_NOTIFICATION

    class Annotation(_TagCommon) :

        __slots__ = ("name", "value")
        tag_name = "annotation"
        tag_attrs = ("name", "value")
        tag_elts = {}

        def __init__(self, name, value) :
            self.name = name
            self.value = value
        #end __init__

    #end Annotation

    def _get_annotations(annotations) :
        # common validation of annotations arguments.
        if not all(isinstance(a, Introspection.Annotation) for a in annotations) :
            raise TypeError("annotations must be Annotation instances")
        #end if
        return \
            annotations
    #end _get_annotations

    class Interface(_TagCommon) :

        __slots__ = ("name", "methods", "signals", "properties", "annotations")
        tag_name = "interface"
        tag_attrs = ("name",)

        class Method(_TagCommon) :

            __slots__ = ("name", "args", "annotations")
            tag_name = "method"
            tag_attrs = ("name",)

            class Arg(_TagCommon) :

                __slots__ = ("name", "type", "direction", "annotations")
                tag_name = "arg"
                tag_attrs = ("name", "type", "direction")
                tag_attrs_optional = {"name"}
                tag_elts = {}
                attr_convert = {} # {"direction" : Introspection.DIRECTION} assigned below

                def __init__(self, *, name = None, type, direction, annotations = ()) :
                    if not isinstance(direction, Introspection.DIRECTION) :
                        raise TypeError("direction must be an Introspection.DIRECTION.xxx enum")
                    #end if
                    self.name = name
                    self.type = parse_single_signature(type)
                    self.direction = direction
                    self.annotations = Introspection._get_annotations(annotations)
                #end __init__

            #end Arg

            tag_elts = {"args" : Arg}

            def __init__(self, name, args = (), annotations = ()) :
                if not all(isinstance(a, self.Arg) for a in args) :
                    raise TypeError("args must be Arg instances")
                #end if
                self.name = name
                self.args = list(args)
                self.annotations = Introspection._get_annotations(annotations)
            #end __init__

            @property
            def in_signature(self) :
                return \
                    list(a.type for a in self.args if a.direction == Introspection.DIRECTION.IN)
            #end in_signature

            @property
            def out_signature(self) :
                return \
                    list \
                      (a.type for a in self.args if a.direction == Introspection.DIRECTION.OUT)
            #end out_signature

            @property
            def expect_reply(self) :
                "will there be replies to this request method."
                return \
                    self.get_annotation("org.freedesktop.DBus.Method.NoReply") != "true"
            #end expect_reply

        #end Method

        class Signal(_TagCommon) :

            __slots__ = ("name", "args", "annotations")
            tag_name = "signal"
            tag_attrs = ("name",)

            class Arg(_TagCommon) :

                __slots__ = ("name", "type", "direction", "annotations")
                tag_name = "arg"
                tag_attrs = ("name", "type", "direction")
                tag_attrs_optional = {"name", "direction"}
                tag_elts = {}
                attr_convert = {} # {"direction" : Introspection.DIRECTION} assigned below

                def __init__(self, *, name = None, type, direction = None, annotations = ()) :
                    if direction != None and direction != Introspection.DIRECTION.OUT :
                        raise ValueError("direction can only be Introspection.DIRECTION.OUT")
                    #end if
                    self.name = name
                    self.type = parse_single_signature(type)
                    self.direction = direction
                    self.annotations = Introspection._get_annotations(annotations)
                #end __init__

            #end Arg

            tag_elts = {"args" : Arg}

            def __init__(self, name, args = (), annotations = ()) :
                if not all(isinstance(a, self.Arg) for a in args) :
                    raise TypeError("args must be Arg instances")
                #end if
                self.name = name
                self.args = list(args)
                self.annotations = Introspection._get_annotations(annotations)
            #end __init__

            @property
            def in_signature(self) :
                return \
                    list(a.type for a in self.args)
            #end in_signature

        #end Signal

        class Property(_TagCommon) :

            __slots__ = ("name", "type", "access", "annotations")
            tag_name = "property"
            tag_attrs = ("name", "type", "access")
            tag_elts = {}
            attr_convert = {} # {"access" : Introspection.ACCESS} assigned below

            def __init__(self, name, type, access, annotations = ()) :
                if not isinstance(access, Introspection.ACCESS) :
                    raise TypeError("access must be an Introspection.ACCESS.xxx enum")
                #end if
                self.name = name
                self.type = parse_single_signature(type)
                self.access = access
                self.annotations = Introspection._get_annotations(annotations)
            #end __init__

        #end Property

        tag_elts = {"methods" : Method, "signals" : Signal, "properties" : Property}

        def __init__(self, name, methods = (), signals = (), properties = (), annotations = ()) :
            if not all(isinstance(m, self.Method) for m in methods) :
                raise TypeError("methods must be Method instances")
            #end if
            if not all(isinstance(s, self.Signal) for s in signals) :
                raise TypeError("signals must be Signal instances")
            #end if
            if not all(isinstance(p, self.Property) for p in properties) :
                raise TypeError("properties must be Property instances")
            #end if
            self.name = name
            self.methods = list(methods)
            self.signals = list(signals)
            self.properties = list(properties)
            self.annotations = Introspection._get_annotations(annotations)
        #end __init__

        @property
        def methods_by_name(self) :
            "returns a dict associating all the methods with their names."
            return \
                dict((method.name, method) for method in self.methods)
        #end methods_by_name

        @property
        def signals_by_name(self) :
            "returns a dict associating all the signals with their names."
            return \
                dict((signal.name, signal) for signal in self.signals)
        #end signals_by_name

        @property
        def properties_by_name(self) :
            "returns a dict associating all the properties with their names."
            return \
                dict((prop.name, prop) for prop in self.properties)
        #end properties_by_name

    #end Interface
    Interface.Method.Arg.attr_convert["direction"] = DIRECTION
    Interface.Signal.Arg.attr_convert["direction"] = lambda x : (lambda : None, lambda : Introspection.DIRECTION(x))[x != None]()
    Interface.Property.attr_convert["access"] = ACCESS

    class StubInterface(_TagCommon) :
        "use this as a replacement for an Interface that you don’t want" \
        " to see expanded, e.g. if it has already been seen."

        __slots__ = ("name", "annotations")
        tag_name = "interface"
        tag_attrs = ("name",)
        tag_elts = {}

        def __init__(self, name) :
            self.name = name
            self.annotations = ()
        #end __init__

    #end StubInterface

    class Node(_TagCommon) :

        __slots__ = ("name", "interfaces", "nodes", "annotations")
        tag_name = "node"
        tag_attrs = ("name",)

        def __init__(self, name, interfaces = (), nodes = (), annotations = ()) :
            if not all(isinstance(i, (Introspection.Interface, Introspection.StubInterface)) for i in interfaces) :
                raise TypeError("interfaces must be Interface or StubInterface instances")
            #end if
            if not all(isinstance(n, Introspection.Node) for n in nodes) :
                raise TypeError("nodes must be Node instances")
            #end if
            self.name = name
            self.interfaces = interfaces
            self.nodes = nodes
            self.annotations = Introspection._get_annotations(annotations)
        #end __init__

        @property
        def interfaces_by_name(self) :
            "returns a dict associating all the interfaces with their names."
            return \
                dict((iface.name, iface) for iface in self.interfaces)
        #end interfaces_by_name

        @property
        def nodes_by_name(self) :
            "returns a dict associating all the child nodes with their names."
            return \
                dict((node.name, node) for node in self.nodes)
        #end nodes_by_name

    #end Node
    Node.tag_elts = {"interfaces" : Interface, "nodes" : Node}

    tag_elts = {"interfaces" : Interface, "nodes" : Node}

    def __init__(self, name = None, interfaces = (), nodes = (), annotations = ()) :
        if not all(isinstance(i, self.Interface) for i in interfaces) :
            raise TypeError("interfaces must be Interface instances")
        #end if
        if not all(isinstance(n, self.Node) for n in nodes) :
            raise TypeError("nodes must be Node instances")
        #end if
        self.name = name
        self.interfaces = list(interfaces)
        self.nodes = list(nodes)
        self.annotations = Introspection._get_annotations(annotations)
    #end __init__

    @property
    def interfaces_by_name(self) :
        "returns a dict associating all the interfaces with their names."
        return \
            dict((iface.name, iface) for iface in self.interfaces)
    #end interfaces_by_name

    @property
    def nodes_by_name(self) :
        "returns a dict associating all the nodes with their names."
        return \
            dict((node.name, node) for node in self.nodes)
    #end nodes_by_name

    @classmethod
    def parse(celf, s) :
        "generates an Introspection tree from the given XML string description."

        def from_string_elts(celf, attrs, tree) :
            elts = dict((k, attrs[k]) for k in attrs)
            child_tags = dict \
              (
                (childclass.tag_name, childclass)
                for childclass in tuple(celf.tag_elts.values()) + (Introspection.Annotation,)
              )
            children = []
            for child in tree :
                if child.tag not in child_tags :
                    raise KeyError("unrecognized tag %s" % child.tag)
                #end if
                childclass = child_tags[child.tag]
                childattrs = {}
                for attrname in childclass.tag_attrs :
                    if hasattr(childclass, "tag_attrs_optional") and attrname in childclass.tag_attrs_optional :
                        childattrs[attrname] = child.attrib.get(attrname, None)
                    else :
                        if attrname not in child.attrib :
                            raise ValueError("missing %s attribute for %s tag" % (attrname, child.tag))
                        #end if
                        childattrs[attrname] = child.attrib[attrname]
                    #end if
                #end for
                if hasattr(childclass, "attr_convert") :
                    for attr in childclass.attr_convert :
                        if attr in childattrs :
                            childattrs[attr] = childclass.attr_convert[attr](childattrs[attr])
                        #end if
                    #end for
                #end if
                children.append(from_string_elts(childclass, childattrs, child))
            #end for
            for child_tag, childclass in tuple(celf.tag_elts.items()) + ((), (("annotations", Introspection.Annotation),))[tree.tag != "annotation"] :
                for child in children :
                    if isinstance(child, childclass) :
                        if child_tag not in elts :
                            elts[child_tag] = []
                        #end if
                        elts[child_tag].append(child)
                    #end if
                #end for
            #end for
            return \
                celf(**elts)
        #end from_string_elts

    #begin parse
        tree = XMLElementTree.fromstring(s)
        assert tree.tag == "node", "root of introspection tree must be <node> tag"
        return \
            from_string_elts(Introspection, {}, tree)
    #end parse

    def unparse(self, indent_step = 4, max_linelen = 72) :
        "returns an XML string description of this Introspection tree."

        out = io.StringIO()

        def to_string(obj, indent) :
            tag_name = obj.tag_name
            attrs = []
            for attrname in obj.tag_attrs :
                attr = getattr(obj, attrname)
                if attr != None :
                    if isinstance(attr, enum.Enum) :
                        attr = attr.value
                    elif isinstance(attr, Type) :
                        attr = unparse_signature(attr)
                    elif not isinstance(attr, str) :
                        raise TypeError("unexpected attribute type %s for %s" % (type(attr).__name__, repr(attr)))
                    #end if
                    attrs.append("%s=%s" % (attrname, quote_xml_attr(attr)))
                #end if
            #end for
            has_elts = \
              (
                    sum
                      (
                        len(getattr(obj, attrname))
                        for attrname in
                                tuple(obj.tag_elts.keys())
                            +
                                ((), ("annotations",))
                                    [not isinstance(obj, Introspection.Annotation)]
                      )
                !=
                    0
              )
            out.write(" " * indent + "<" + tag_name)
            if (
                    max_linelen != None
                and
                            indent
                        +
                            len(tag_name)
                        +
                            sum((len(s) + 1) for s in attrs)
                        +
                            2
                        +
                            int(has_elts)
                    >
                        max_linelen
            ) :
                out.write("\n")
                for attr in attrs :
                    out.write(" " * (indent + indent_step))
                    out.write(attr)
                    out.write("\n")
                #end for
                out.write(" " * indent)
            else :
                for attr in attrs :
                    out.write(" ")
                    out.write(attr)
                #end for
            #end if
            if not has_elts :
                out.write("/")
            #end if
            out.write(">\n")
            if has_elts :
                for attrname in sorted(obj.tag_elts.keys()) + ["annotations"] :
                    for elt in getattr(obj, attrname) :
                        to_string(elt, indent + indent_step)
                    #end for
                #end for
                out.write(" " * indent + "</" + tag_name + ">\n")
            #end if
        #end to_string

    #begin unparse
        out.write(DBUS.INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE)
        out.write("<node")
        if self.name != None :
            out.write(" name=%s" % quote_xml_attr(self.name))
        #end if
        out.write(">\n")
        for elt in self.interfaces :
            to_string(elt, indent_step)
        #end for
        for elt in self.nodes :
            to_string(elt, indent_step)
        #end for
        out.write("</node>\n")
        return \
            out.getvalue()
    #end unparse

#end Introspection

del _TagCommon

#+
# Standard interfaces
#-

standard_interfaces = \
    {
        DBUS.INTERFACE_PEER :
            # note implementation of this is hard-coded inside libdbus
            Introspection.Interface
              (
                name = DBUS.INTERFACE_PEER,
                methods =
                    [
                        Introspection.Interface.Method(name = "Ping"),
                        Introspection.Interface.Method
                          (
                            name = "GetMachineId",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        name = "machine_uuid",
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                    ],
              ),
        DBUS.INTERFACE_LOCAL :
            # note implementation of this is hard-coded inside, and specific to, libdbus
            Introspection.Interface
              (
                name = DBUS.INTERFACE_LOCAL,
                signals =
                    [
                        Introspection.Interface.Signal(name = "Disconnected"),
                          # auto-generated by libdbus with path = DBUS.PATH_LOCAL
                          # when connection is closed; cannot be explicitly sent by
                          # clients. Documented here:
                          # <https://lists.freedesktop.org/archives/dbus/2018-October/017587.html>
                    ],
              ),
        DBUS.INTERFACE_DBUS :
            Introspection.Interface
              (
                name = DBUS.INTERFACE_DBUS,
                methods =
                    [
                        Introspection.Interface.Method
                          (
                            name = "Hello",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.OUT,
                                      ), # returned unique name
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "RequestName",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ), # name
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.UINT32),
                                        direction = Introspection.DIRECTION.IN,
                                      ), # flags DBUS.NAME_FLAG_xxx
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.UINT32),
                                        direction = Introspection.DIRECTION.OUT,
                                      ), # result DBUS.REQUEST_NAME_REPLY_xxx
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "ReleaseName",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.UINT32),
                                        direction = Introspection.DIRECTION.OUT,
                                      ), # result DBUS.RELEASE_NAME_REPLY_xxx
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "StartServiceByName",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ), # name
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.UINT32),
                                        direction = Introspection.DIRECTION.IN,
                                      ), # flags (currently unused)
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.UINT32),
                                        direction = Introspection.DIRECTION.OUT,
                                      ), # result DBUS.START_REPLY_xxx
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "UpdateActivationEnvironment",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = DictType
                                          (
                                            keytype = BasicType(TYPE.STRING),
                                            valuetype = BasicType(TYPE.STRING)
                                          ),
                                        direction = Introspection.DIRECTION.IN,
                                      ), # environment
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "NameHasOwner",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ), # name
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.BOOLEAN),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "ListNames",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = ArrayType(BasicType(TYPE.STRING)),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "ListActivatableNames",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = ArrayType(BasicType(TYPE.STRING)),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "AddMatch",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "RemoveMatch",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "GetNameOwner",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "ListQueuedOwners",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = ArrayType(BasicType(TYPE.STRING)),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "GetConnectionUnixUser",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.UINT32),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "GetConnectionUnixProcessID",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.UINT32),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "GetAdtAuditSessionData",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = ArrayType(BasicType(TYPE.BYTE)),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "GetConnectionSELinuxSecurityContext",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = ArrayType(BasicType(TYPE.BYTE)),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "ReloadConfig",
                          ),
                        Introspection.Interface.Method
                          (
                            name = "GetId",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                        Introspection.Interface.Method
                          (
                            name = "GetConnectionCredentials",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = DictType(BasicType(TYPE.STRING), VariantType()),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                    ],
                signals =
                    [
                        Introspection.Interface.Signal
                          (
                            name = "NameOwnerChanged",
                            args =
                                [
                                    Introspection.Interface.Signal.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                      ), # bus name
                                    Introspection.Interface.Signal.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                      ), # old owner, empty if none
                                    Introspection.Interface.Signal.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                      ), # new owner, empty if none
                                ]
                          ),
                        Introspection.Interface.Signal
                          (
                            name = "NameLost", # sent to previous owner of name
                            args =
                                [
                                    Introspection.Interface.Signal.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                      ),
                                ]
                          ),
                        Introspection.Interface.Signal
                          (
                            name = "NameAcquired", # sent to new owner of name
                            args =
                                [
                                    Introspection.Interface.Signal.Arg
                                      (
                                        type = BasicType(TYPE.STRING),
                                      ),
                                ]
                          ),
                    ],
              ),
        DBUS.INTERFACE_INTROSPECTABLE :
            Introspection.Interface
              (
                name = DBUS.INTERFACE_INTROSPECTABLE,
                methods =
                    [
                        Introspection.Interface.Method
                          (
                            name = "Introspect",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        name = "data",
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ]
                          ),
                    ],
              ),
        DBUS.INTERFACE_PROPERTIES :
            Introspection.Interface
              (
                name = DBUS.INTERFACE_PROPERTIES,
                methods =
                    [
                        Introspection.Interface.Method
                          (
                            name = "Get",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        name = "interface_name",
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        name = "property_name",
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        name = "value",
                                        type = VariantType(),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ],
                          ),
                        Introspection.Interface.Method
                          (
                            name = "Set",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        name = "interface_name",
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        name = "property_name",
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        name = "value",
                                        type = VariantType(),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                ],
                          ),
                        Introspection.Interface.Method
                          (
                            name = "GetAll",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        name = "interface_name",
                                        type = BasicType(TYPE.STRING),
                                        direction = Introspection.DIRECTION.IN,
                                      ),
                                    Introspection.Interface.Method.Arg
                                      (
                                        name = "values",
                                        type = DictType(BasicType(TYPE.STRING), VariantType()),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ],
                          ),
                    ],
                signals =
                    [
                        Introspection.Interface.Signal
                          (
                            name = "PropertiesChanged",
                            args =
                                [
                                    Introspection.Interface.Signal.Arg
                                      (
                                        name = "interface_name",
                                        type = BasicType(TYPE.STRING),
                                      ),
                                    Introspection.Interface.Signal.Arg
                                      (
                                        name = "changed_properties",
                                        type = DictType(BasicType(TYPE.STRING), VariantType()),
                                      ),
                                    Introspection.Interface.Signal.Arg
                                      (
                                        name = "invalidated_properties",
                                        type = ArrayType(BasicType(TYPE.STRING)),
                                      ),
                                ],
                          ),
                    ],
              ),
        DBUS.INTERFACE_MONITORING :
            Introspection.Interface
              (
                name = DBUS.INTERFACE_MONITORING,
                methods =
                    [
                        Introspection.Interface.Method
                          (
                            name = "BecomeMonitor",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = ArrayType(BasicType(TYPE.STRING)),
                                        direction = Introspection.DIRECTION.IN,
                                      ), # match rules to add to the connection
                                    Introspection.Interface.Method.Arg
                                      (
                                        type = BasicType(TYPE.UINT32),
                                        direction = Introspection.DIRECTION.IN,
                                      ), # flags (currently unused)
                                ],
                          ),
                    ],
              ),
        DBUSX.INTERFACE_OBJECT_MANAGER :
            Introspection.Interface
              (
                name = DBUSX.INTERFACE_OBJECT_MANAGER,
                methods =
                    [
                        Introspection.Interface.Method
                          (
                            name = "GetManagedObjects",
                            args =
                                [
                                    Introspection.Interface.Method.Arg
                                      (
                                        name = "objpath_interfaces_and_properties",
                                        type = DictType
                                          (
                                            BasicType(TYPE.OBJECT_PATH),
                                            DictType
                                              (
                                                BasicType(TYPE.STRING), # interface
                                                DictType(BasicType(TYPE.STRING), VariantType())
                                                  # properties and values
                                              )
                                          ),
                                        direction = Introspection.DIRECTION.OUT,
                                      ),
                                ],
                          ),
                    ],
                signals =
                    [
                        Introspection.Interface.Signal
                          (
                            name = "InterfacesAdded",
                            args =
                                [
                                    Introspection.Interface.Signal.Arg
                                      (
                                        name = "object_path",
                                        type = BasicType(TYPE.OBJECT_PATH),
                                      ),
                                    Introspection.Interface.Signal.Arg
                                      (
                                        name = "interfaces_and_properties",
                                        type = DictType
                                          (
                                            BasicType(TYPE.STRING), # interface added/changed
                                            DictType(BasicType(TYPE.STRING), VariantType())
                                              # properties and values added
                                          ),
                                      ),
                                ],
                          ),
                        Introspection.Interface.Signal
                          (
                            name = "InterfacesRemoved",
                            args =
                                [
                                    Introspection.Interface.Signal.Arg
                                      (
                                        name = "object_path",
                                        type = BasicType(TYPE.OBJECT_PATH),
                                      ),
                                    Introspection.Interface.Signal.Arg
                                      (
                                        name = "interfaces",
                                        type = ArrayType(BasicType(TYPE.STRING)),
                                          # interfaces removed
                                      ),
                                ],
                          ),
                    ],
              ),
    }

#+
# Cleanup
#-

def _atexit() :
    # disable all __del__ methods at process termination to avoid segfaults
    for cls in Connection, Server, PreallocatedSend, Message, PendingCall, Error, AddressEntries :
        delattr(cls, "__del__")
    #end for
#end _atexit
atexit.register(_atexit)
del _atexit