summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore75
-rw-r--r--.gitlab-ci.yml132
-rw-r--r--.reuse/dep539
-rw-r--r--AUTHORS35
-rw-r--r--CONTRIBUTING.md130
-rw-r--r--COPYING33
-rw-r--r--ChangeLog2
-rw-r--r--LICENSES/GPL-3.0-or-later.txt232
-rw-r--r--LICENSES/LicenseRef-AutoconfArchiveException.txt12
-rw-r--r--Makefile.am497
-rw-r--r--NEWS918
-rw-r--r--README35
-rwxr-xr-xautogen.sh41
-rw-r--r--configure.ac167
-rw-r--r--dbus-python.pc.in14
-rw-r--r--dbus/__init__.py93
-rw-r--r--dbus/_compat.py15
-rw-r--r--dbus/_dbus.py229
-rw-r--r--dbus/_expat_introspect_parser.py87
-rw-r--r--dbus/bus.py434
-rw-r--r--dbus/connection.py651
-rw-r--r--dbus/decorators.py362
-rw-r--r--dbus/exceptions.py133
-rw-r--r--dbus/gi_service.py87
-rw-r--r--dbus/glib.py53
-rw-r--r--dbus/lowlevel.py38
-rw-r--r--dbus/mainloop/__init__.py64
-rw-r--r--dbus/mainloop/glib.py43
-rw-r--r--dbus/proxies.py567
-rw-r--r--dbus/server.py119
-rw-r--r--dbus/service.py840
-rw-r--r--dbus/types.py15
-rw-r--r--dbus_bindings/abstract.c698
-rw-r--r--dbus_bindings/bus.c195
-rw-r--r--dbus_bindings/bytes.c288
-rw-r--r--dbus_bindings/compat-internal.h34
-rw-r--r--dbus_bindings/conn-internal.h69
-rw-r--r--dbus_bindings/conn-methods.c1073
-rw-r--r--dbus_bindings/conn.c498
-rw-r--r--dbus_bindings/containers.c771
-rw-r--r--dbus_bindings/dbus_bindings-internal.h272
-rw-r--r--dbus_bindings/debug.c98
-rw-r--r--dbus_bindings/exceptions.c106
-rw-r--r--dbus_bindings/float.c156
-rw-r--r--dbus_bindings/generic.c63
-rw-r--r--dbus_bindings/int.c771
-rw-r--r--dbus_bindings/libdbusconn.c128
-rw-r--r--dbus_bindings/mainloop.c209
-rw-r--r--dbus_bindings/message-append.c1225
-rw-r--r--dbus_bindings/message-get-args.c524
-rw-r--r--dbus_bindings/message-internal.h51
-rw-r--r--dbus_bindings/message.c1142
-rw-r--r--dbus_bindings/module.c414
-rw-r--r--dbus_bindings/pending-call.c296
-rw-r--r--dbus_bindings/server.c616
-rw-r--r--dbus_bindings/signature.c251
-rw-r--r--dbus_bindings/string.c267
-rw-r--r--dbus_bindings/types-internal.h97
-rw-r--r--dbus_bindings/unixfd.c281
-rw-r--r--dbus_bindings/validation.c247
-rw-r--r--dbus_glib_bindings/module.c200
-rw-r--r--doc/API_CHANGES.txt124
-rw-r--r--doc/PY3PORT.txt222
-rw-r--r--doc/_static/.gitignore0
-rw-r--r--doc/conf.py165
-rw-r--r--doc/dbus.bus.rst8
-rw-r--r--doc/dbus.connection.rst8
-rw-r--r--doc/dbus.decorators.rst8
-rw-r--r--doc/dbus.exceptions.rst8
-rw-r--r--doc/dbus.gi_service.rst8
-rw-r--r--doc/dbus.glib.rst8
-rw-r--r--doc/dbus.gobject_service.rst36
-rw-r--r--doc/dbus.lowlevel.rst8
-rw-r--r--doc/dbus.mainloop.rst20
-rw-r--r--doc/dbus.proxies.rst8
-rw-r--r--doc/dbus.rst35
-rw-r--r--doc/dbus.server.rst8
-rw-r--r--doc/dbus.service.rst8
-rw-r--r--doc/dbus.types.rst8
-rw-r--r--doc/index.rst59
-rw-r--r--doc/news.rst5
-rw-r--r--doc/redirects140
-rw-r--r--doc/redirects.py46
-rw-r--r--doc/tutorial.txt716
-rwxr-xr-xexamples/example-async-client.py124
-rwxr-xr-xexamples/example-client.py82
-rwxr-xr-xexamples/example-service.py87
-rwxr-xr-xexamples/example-signal-emitter.py73
-rwxr-xr-xexamples/example-signal-recipient.py103
-rwxr-xr-xexamples/gconf-proxy-client.py41
-rwxr-xr-xexamples/gconf-proxy-service2.py96
-rwxr-xr-xexamples/list-system-services.py73
-rwxr-xr-xexamples/unix-fd-client.py80
-rwxr-xr-xexamples/unix-fd-service.py79
-rw-r--r--include/dbus/dbus-python.h106
-rw-r--r--m4/ax_python_devel.m4416
-rwxr-xr-xsetup.py112
-rw-r--r--subprojects/dbus-gmain/.editorconfig (renamed from .editorconfig)0
-rw-r--r--subprojects/dbus-gmain/.gitignore22
-rw-r--r--subprojects/dbus-gmain/.gitlab-ci.yml34
-rw-r--r--subprojects/dbus-gmain/.reuse/dep520
-rw-r--r--subprojects/dbus-gmain/AUTHORS15
-rw-r--r--subprojects/dbus-gmain/CONTRIBUTING.md110
-rw-r--r--subprojects/dbus-gmain/COPYING7
-rw-r--r--subprojects/dbus-gmain/LICENSES/AFL-2.1.txt (renamed from LICENSES/AFL-2.1.txt)0
-rw-r--r--subprojects/dbus-gmain/LICENSES/GPL-2.0-or-later.txt (renamed from LICENSES/GPL-2.0-or-later.txt)0
-rw-r--r--subprojects/dbus-gmain/LICENSES/MIT.txt9
-rw-r--r--subprojects/dbus-gmain/Makefile.am101
-rw-r--r--subprojects/dbus-gmain/README.md (renamed from README.md)0
-rw-r--r--subprojects/dbus-gmain/dbus-gmain.c (renamed from dbus-gmain.c)0
-rw-r--r--subprojects/dbus-gmain/dbus-gmain/dbus-gmain.h (renamed from dbus-gmain/dbus-gmain.h)0
-rw-r--r--subprojects/dbus-gmain/meson.build (renamed from meson.build)0
-rw-r--r--subprojects/dbus-gmain/meson_options.txt (renamed from meson_options.txt)0
-rw-r--r--subprojects/dbus-gmain/tests/30574.c (renamed from tests/30574.c)0
-rw-r--r--subprojects/dbus-gmain/tests/meson.build (renamed from tests/meson.build)0
-rw-r--r--subprojects/dbus-gmain/tests/test-thread-client.c (renamed from tests/test-thread-client.c)0
-rw-r--r--subprojects/dbus-gmain/tests/test-thread-server.c (renamed from tests/test-thread-server.c)0
-rw-r--r--subprojects/dbus-gmain/tests/test-thread.h (renamed from tests/test-thread.h)0
-rw-r--r--subprojects/dbus-gmain/tests/use-as-subproject.py (renamed from tests/use-as-subproject.py)0
-rw-r--r--subprojects/dbus-gmain/tests/use-as-subproject/.gitignore (renamed from tests/use-as-subproject/.gitignore)0
-rw-r--r--subprojects/dbus-gmain/tests/use-as-subproject/README (renamed from tests/use-as-subproject/README)0
-rw-r--r--subprojects/dbus-gmain/tests/use-as-subproject/meson.build (renamed from tests/use-as-subproject/meson.build)0
-rw-r--r--subprojects/dbus-gmain/tests/use-as-subproject/use-dbus-gmain.c (renamed from tests/use-as-subproject/use-dbus-gmain.c)0
-rw-r--r--subprojects/dbus-gmain/tests/util.c (renamed from tests/util.c)0
-rw-r--r--subprojects/dbus-gmain/tests/util.h (renamed from tests/util.h)0
-rw-r--r--test/TestSuitePythonService.service.in3
-rwxr-xr-xtest/cross-test-client.py409
-rwxr-xr-xtest/cross-test-server.py334
-rw-r--r--test/crosstest.py46
-rw-r--r--test/dbus_py_test.c138
-rw-r--r--test/dbus_test_utils.py53
-rw-r--r--test/import-repeatedly.c29
-rwxr-xr-xtest/run-test.sh145
-rwxr-xr-xtest/test-client.py659
-rwxr-xr-xtest/test-exception-py3.py36
-rwxr-xr-xtest/test-p2p.py117
-rwxr-xr-xtest/test-server.py80
-rwxr-xr-xtest/test-service.py416
-rwxr-xr-xtest/test-signals.py167
-rwxr-xr-xtest/test-standalone.py587
-rwxr-xr-xtest/test-unusable-main-loop.py42
-rw-r--r--test/tmp-session-bus.conf.in23
-rwxr-xr-xtest/wait-for-name.py59
-rw-r--r--tools/check-c-style.sh19
-rw-r--r--tools/check-coding-style.mk28
-rw-r--r--tools/check-py-style.sh20
-rw-r--r--tools/check-whitespace.sh19
-rw-r--r--tools/ci-Dockerfile.in12
-rwxr-xr-xtools/ci-build.sh98
-rwxr-xr-xtools/ci-install.sh217
150 files changed, 24267 insertions, 167 deletions
diff --git a/.gitignore b/.gitignore
index 1d15bff..8d80ea7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,22 +1,67 @@
-# Copyright 2006-2022 Collabora Ltd.
+# Copyright 2006-2021 Collabora Ltd.
# SPDX-License-Identifier: MIT
-*.a
-*.gcda
-*.gcno
-*.lineno
+*.html
+*.la
*.lo
*.o
-*~
+*.orig
+*.pyc
+*.pyo
+*.rej
+.*.sw?
.deps/
+.dirstamp
.libs/
-/Makefile
-/Makefile.in
-/libdbus-gmain.la
+/INSTALL
+/aclocal.m4
+/api/
+/autom4te.cache/
+/build-aux/
+/build/
+/compile
+/config.cache
+/config.guess
+/config.h
+/config.h.in*
+/config.log
+/config.status
+/config.sub
+/configure
+/configure~
+/configure.ac~
+/dbus-python-*.tar.gz
+/dbus-python-*.tar.gz.asc
+/dbus-python.pc
+/dbus/.doc-needs-rebuild-stamp
+/dbus/_version.py
+/dbus_python.egg-info/
+/depcomp
+/dist/
+/doc/_build/
+/install-sh
+/libtool
+/ltmain.sh
+/m4/ax_*.m4
+/m4/libtool.m4
+/m4/ltoptions.m4
+/m4/ltsugar.m4
+/m4/ltversion.m4
+/m4/lt~obsolete.m4
+/m4/pkg.m4
+/missing
+/py-compile
+/stamp-h1
/test-suite.log
-/tests/*.log
-/tests/*.trs
-/tests/libtest.la
-/tests/test-30574
-/tests/test-thread-client
-/tests/test-thread-server
+/test/*.trs
+/test/TestSuitePythonService.service
+/test/cross-*.log
+/test/monitor.log
+/test/run-test.log
+/test/test-*.log
+/test/test-import-repeatedly
+/test/tmp-session-bus.conf
+Makefile
+Makefile.in
+__pycache__/
+test-service.log
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c063317..38ce8ce 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,34 +1,108 @@
# Copyright © 2015-2022 Collabora Ltd.
# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
-image: debian:bullseye-slim
+image: debian:bookworm-slim
stages:
- - build
-
-build:
- stage: build
- image: "debian:bullseye-slim"
- script:
- - |
- apt-get -y update
- apt-get -y install \
- build-essential \
- dbus-daemon \
- libdbus-1-dev \
- libglib2.0-dev \
- meson \
- pkg-config \
- python3 \
- ${NULL+}
- meson _build
- meson compile -C _build -v
- meson test -C _build -v
-
-reuse:
- stage: build
- image:
- name: fsfe/reuse:latest
- entrypoint: [""]
- script:
- - reuse lint
+ - build
+
+before_script:
+ - ./tools/ci-install.sh
+
+variables:
+ dbus_ci_configure_flags: ''
+ ci_in_docker: "yes"
+ ci_parallel: "2"
+ ci_sudo: "yes"
+ ci_distro: "debian"
+ ci_suite: "bookworm"
+
+build:python3.5:
+ stage: build
+ image: "debian:stretch-slim"
+ variables:
+ ci_suite: stretch
+ dbus_ci_system_python: python3
+ script: &script
+ - chown -R user .
+ - runuser -u user -- env PATH="/usr/lib/ccache:$PATH" ./tools/ci-build.sh $dbus_ci_configure_flags
+
+build:python3.5-dbg:
+ stage: build
+ image: "debian:stretch-slim"
+ variables:
+ ci_suite: stretch
+ dbus_ci_system_python: python3-dbg
+ script: *script
+
+build:python3.7:
+ stage: build
+ image: "debian:buster-slim"
+ variables:
+ ci_suite: buster
+ dbus_ci_system_python: python3
+ script: *script
+
+build:python3.7-dbg:
+ stage: build
+ image: "debian:buster-slim"
+ variables:
+ ci_suite: buster
+ dbus_ci_system_python: python3-dbg
+ script: *script
+
+build:python3.9:
+ stage: build
+ image: "debian:bullseye-slim"
+ variables:
+ ci_suite: bullseye
+ dbus_ci_system_python: python3.9
+ script: *script
+
+build:python3.9-dbg:
+ stage: build
+ image: "debian:bullseye-slim"
+ variables:
+ ci_suite: bullseye
+ dbus_ci_system_python: python3.9-dbg
+ dbus_ci_system_python_module_suffix: -dbg
+ script: *script
+
+build:python3.10:
+ stage: build
+ image: "debian:bookworm-slim"
+ variables:
+ ci_suite: bookworm
+ dbus_ci_system_python: python3.10
+ script: *script
+
+build:python3.10-dbg:
+ stage: build
+ image: "debian:bookworm-slim"
+ variables:
+ ci_suite: bookworm
+ dbus_ci_system_python: python3.10-dbg
+ dbus_ci_system_python_module_suffix: -dbg
+ script: *script
+
+# vim:set sw=2 sts=2 et:
diff --git a/.reuse/dep5 b/.reuse/dep5
index 5985ce4..b01ad35 100644
--- a/.reuse/dep5
+++ b/.reuse/dep5
@@ -1,20 +1,31 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: dbus-gmain
-Upstream-Contact: https://gitlab.freedesktop.org/dbus/dbus-glib/-/issues
-Source: https://gitlab.freedesktop.org/dbus/dbus-glib
+Upstream-Name: dbus-python
+Upstream-Contact: https://gitlab.freedesktop.org/dbus/dbus-python/-/issues
+Source: https://gitlab.freedesktop.org/dbus/dbus-python
-Files: AUTHORS
+Files:
+ AUTHORS
+ ChangeLog
+ CONTRIBUTING.md
+ NEWS
+ README
+ doc/*
+ test/TestSuitePythonService.service.in
+ test/tmp-session-bus.conf.in
Copyright:
- Copyright 2006 Red Hat, Inc.
- Copyright 2007 Codethink Ltd.
+ 2006-2022 Collabora Ltd.
+ 2006-2007 Red Hat, Inc.
+ 2012 Barry Warsaw
+ 2008-2019 Wayland contributors
License: MIT
-Files: CONTRIBUTING.md
+Files: m4/ax_python_devel.m4
Copyright:
- 2008-2018 Wayland contributors
- 2018 Collabora Ltd.
-License: MIT
-
-Files: README.md
-Copyright: 2018 Collabora Ltd.
-License: MIT
+ 2009 Sebastian Huber
+ 2009 Alan W. Irwin
+ 2009 Rafael Laboissiere
+ 2009 Andrew Collier
+ 2009 Matteo Settenvini
+ 2009 Horst Knorr
+ 2013 Daniel Mullner
+License: GPL-3.0-or-later WITH LicenseRef-AutoconfArchiveException
diff --git a/AUTHORS b/AUTHORS
index 83c7766..aa914eb 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,15 +1,20 @@
-Alexander Larsson
-Anders Carlsson
-Carlos Garnacho Parro
-Christian Dywan
-Colin Walters
-Havoc Pennington
-James Willcox
-Kristian Hogsberg
-Marc-Andre Lureau
-Mikael Hallendal
-Mike Gorse
-Richard Hult
-Ross Burton
-Steve Frécinaux
-Tobias Mueller
+Olivier Andrieu <oliv__a@users.sourceforge.net>
+Philip Blundell <pb@nexus.co.uk>
+Anders Carlsson <andersca@gnome.org>
+Kristian Hogsberg <krh@redhat.com>
+Alex Larsson <alexl@redhat.com>
+Robert McQueen <robot101@debian.org>
+Simon McVittie <simon.mcvittie@collabora.co.uk>
+Michael Meeks <michael@ximian.com>
+Osvaldo Santana Neto <osvaldo.santana@indt.org.br>
+Seth Nickell <seth@gnome.org>
+John (J5) Palmieri <johnp@redhat.com>
+Havoc Pennington <hp@redhat.com>
+Harri Porten <porten@kde.org>
+Matthew Rickard <mjricka@epoch.ncsc.mil>
+Zack Rusin <zack@kde.org>
+Joe Shaw <joe@assbarn.com>
+Colin Walters <walters@verbum.org>
+David Zeuthen <david@fubar.dk>
+
+(See also dbus-gmain/AUTHORS.)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5bbcee1..4e5acfc 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,9 +1,10 @@
-# Contributing to dbus-gmain
+# Contributing to dbus-python
-dbus-gmain is hosted by freedesktop.org. The source code repository,
+## Source code repository and issue tracking
+
+dbus-python is hosted by freedesktop.org. The source code repository,
issue tracking and merge requests are provided by freedesktop.org's
-Gitlab installation, as a branch in the dbus-glib project:
-<https://gitlab.freedesktop.org/dbus/dbus-glib/tree/dbus-gmain>
+Gitlab installation: <https://gitlab.freedesktop.org/dbus/dbus-python>
## Making changes
@@ -44,32 +45,67 @@ We won't reject patches that lack S-o-b, but it is strongly recommended.
When you consider changes ready for merging to mainline:
-* create a personal fork of <https://gitlab.freedesktop.org/dbus/dbus-glib>
+* create a personal fork of <https://gitlab.freedesktop.org/dbus/dbus-python>
on freedesktop.org Gitlab
* push your changes to your personal fork as a branch
* create a merge request at
- <https://gitlab.freedesktop.org/dbus/dbus-glib/merge_requests>,
- and remember to specify `dbus-gmain` as the target branch
+ <https://gitlab.freedesktop.org/dbus/dbus-python/merge_requests>
## Automated tests
For nontrivial changes please try to extend the test suite to cover it.
-dbus-gmain uses GLib's test framework; tests are in the `tests/`
-directory.
Run `make check` to run the test suite.
## Coding style
-Please match the existing code style (Emacs: "gnu").
+Please match the existing coding style, which should be approximately
+[PEP8](https://www.python.org/dev/peps/pep-0008/) (with 4-space
+indentation and no hard tabs) for Python code, and
+[PEP7](https://www.python.org/dev/peps/pep-0007/) for C code.
+Docstrings etc. are reStructuredText.
+
+(The `dbus-gmain` subproject is maintained separately, and uses the
+same GNU/GNOME coding style as libdbus and GLib.)
+
+## Technical notes
+
+### Modules
+
+`dbus`, `dbus.service` and `dbus.mainloop` are core public API.
+
+`dbus.lowlevel` provides a lower-level public API for advanced use.
+
+`dbus.mainloop.glib` is the public API for the GLib main loop integration.
+
+`dbus.types` and `dbus.exceptions` are mainly for backwards
+compatibility - use `dbus` instead in new code. Ditto `dbus.glib`.
+
+`dbus._dbus`, `dbus.introspect_parser`, `dbus.proxies` are internal
+implementation details.
+
+`_dbus_bindings` is the real implementation of the Python/libdbus
+integration, while `_dbus_bindings` is the real implementation of
+Python/libdbus-glib integration. Neither is public API, although some
+of the classes and functions are exposed as public API in other modules.
+
+### Threading/locking model
+
+All Python functions must be called with the GIL (obviously).
+
+Before calling into any D-Bus function that can block, release the GIL;
+as well as the usual "be nice to other threads", D-Bus does its own
+locking and we don't want to deadlock with it. Most Connection methods
+can block.
## Licensing
-Please match the existing licensing (a dual-license: AFL-2.1 or GPL-2+,
-recipient's choice). Entirely new modules can be placed under a more
-permissive license: to avoid license proliferation, our preferred
-permissive license is the variant of the MIT/X11 license used by the
-Expat XML library (for example see the top of tools/ci-build.sh).
+Please match the existing licensing. This is the variant of the MIT/X11
+license used by the Expat XML library ("MIT" in the SPDX license
+vocabulary).
+
+(The `dbus-gmain` subproject is maintained separately, and uses the
+same AFL-2.1/GPL-2.0-or-later license as libdbus.)
## Conduct
@@ -81,24 +117,41 @@ interacting with community members on mailing lists, IRC, or bug
trackers. The community represents the project as a whole, and abusive
or bullying behaviour is not tolerated by the project.
-## (Lack of) versioning and releases
+## Versioning
+
+Version 1.Y.Z, where the micro version *Z* is even (divisible by 2),
+is a real release.
+
+Version 1.Y.(Z+1), where *Z* is even (divisible by 2), identifies a
+development snapshot leading to version 1.Y.(Z+2). Odd-numbered versions
+should never be used as releases.
+
+In the unlikely event that major feature work is done on dbus-python in
+future, the minor version *Y* should be set to an odd number (matching
+the versioning policy of libdbus) on the development branch, with bug
+fixes for the 1.2.x stable series cherry-picked to a `dbus-python-1.2`
+branch.
-dbus-gmain is currently set up to be a git subtree or git submodule,
-so it does not have releases in its own right. It gets merged or
-otherwise included in larger projects like dbus-glib and dbus-python
-instead.
+## Contributing to dbus-gmain
+
+The `dbus-gmain` subproject is shared by `dbus-python` and `dbus-glib`,
+and has its own contributing guidelines (which are similar to these).
+Please see
+[subprojects/dbus-gmain/CONTRIBUTING.md](subprojects/dbus-gmain/CONTRIBUTING.md)
+for details.
## Information for maintainers
This section is not directly relevant to infrequent contributors.
-### Updating the copies of dbus-gmain in dbus-glib and dbus-python
+### dbus-gmain
-dbus-gmain is maintained via `git subtree`. To update one of the dependent
-projects, assuming you have a checkout of the dbus-gmain branch of the
-dbus-glib repository in ../dbus-gmain:
+dbus-gmain is maintained via `git subtree`. To update, assuming you have
+a checkout of the `dbus-gmain` branch of the
+[dbus-glib](https://gitlab.freedesktop.org/dbus/dbus-glib) repository in
+the `../dbus-gmain` directory:
- git subtree pull -P dbus-gmain ../dbus-gmain HEAD
+ git subtree pull -P subprojects/dbus-gmain ../dbus-gmain HEAD
### Committing other people's patches
@@ -108,3 +161,30 @@ apply the patch and then use "git commit --author ..."
Nontrivial patches should always go through Gitlab for peer review,
so you should have an issue number or a merge request ID to refer to.
+
+### Making a release
+
+#### Pre-release steps
+
+* Make sure CI (currently Gitlab-CI) is passing
+* Update `NEWS` and the version number in `configure.ac`, and commit them
+
+#### Building and uploading the release
+
+If `${builddir}` is the path to a build directory and `${version}`
+is the new version:
+
+```
+make -C ${builddir} distcheck
+# do any final testing here, e.g. updating the Debian package
+git tag -m dbus-python-${version} -s dbus-python-${version}
+gpg --detach-sign -a ${builddir}/dbus-python-${version}.tar.gz
+make -C ${builddir} maintainer-upload
+make -C ${builddir} maintainer-update-website
+twine upload ${builddir}/dbus-python-${version}.tar.gz{,.asc}
+```
+
+#### Post-release steps
+
+* Announce the new release to the D-Bus mailing list
+* Update `NEWS` and the version number in `configure.ac`, and commit them
diff --git a/COPYING b/COPYING
index 72084a8..1e95c82 100644
--- a/COPYING
+++ b/COPYING
@@ -1,7 +1,32 @@
-The D-Bus GLib main loop bindings are licensed to you under your choice
-of the Academic Free License version 2.1, or the GNU General Public
-License version 2. Both licenses are available in the LICENSES directory.
-
This project's licensing is REUSE-compliant <https://reuse.software/>.
See individual files for full details of copyright and licensing,
and see LICENSES/*.txt for the license text.
+
+As of version 0.82.4, dbus-python itself is released under the following
+permissive non-copyleft license (the same one that was proposed for
+D-Bus core but wasn't achieved):
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use, copy,
+ modify, merge, publish, distribute, sublicense, and/or sell copies
+ of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+Copyright holders and licensing are indicated in the source files.
+
+The dbus-gmain/ subdirectory is under a different license: see
+dbus-gmain/COPYING for details.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..a8ccf81
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,2 @@
+In git versions of dbus-python, please use 'git-log' instead of referring to
+ChangeLog. A changelog is generated from the git history during 'make dist'.
diff --git a/LICENSES/GPL-3.0-or-later.txt b/LICENSES/GPL-3.0-or-later.txt
new file mode 100644
index 0000000..d41c0bd
--- /dev/null
+++ b/LICENSES/GPL-3.0-or-later.txt
@@ -0,0 +1,232 @@
+GNU GENERAL PUBLIC LICENSE
+Version 3, 29 June 2007
+
+Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>
+
+Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
+
+Preamble
+
+The GNU General Public License is a free, copyleft license for software and other kinds of works.
+
+The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
+
+To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.
+
+For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
+
+Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.
+
+For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.
+
+Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.
+
+Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.
+
+The precise terms and conditions for copying, distribution and modification follow.
+
+TERMS AND CONDITIONS
+
+0. Definitions.
+
+“This License” refers to version 3 of the GNU General Public License.
+
+“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
+
+“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.
+
+To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.
+
+A “covered work” means either the unmodified Program or a work based on the Program.
+
+To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
+
+To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
+
+An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
+
+1. Source Code.
+The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.
+
+A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
+
+The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
+
+The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
+
+The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
+
+The Corresponding Source for a work in source code form is that same work.
+
+2. Basic Permissions.
+All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
+
+You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
+
+Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
+
+3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
+
+When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
+
+4. Conveying Verbatim Copies.
+You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
+
+You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
+
+5. Conveying Modified Source Versions.
+You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”.
+
+ c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
+
+A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
+
+6. Conveying Non-Source Forms.
+You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
+
+ d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
+
+A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
+
+A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
+
+“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
+
+If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
+
+The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
+
+Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
+
+7. Additional Terms.
+“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
+
+When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
+
+Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
+
+All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
+
+If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
+
+Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
+
+8. Termination.
+You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
+
+However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
+
+Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
+
+Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
+
+9. Acceptance Not Required for Having Copies.
+You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
+
+10. Automatic Licensing of Downstream Recipients.
+Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
+
+An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
+
+You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
+
+11. Patents.
+A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”.
+
+A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
+
+Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
+
+In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
+
+If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
+
+If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
+
+A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
+
+Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
+
+12. No Surrender of Others' Freedom.
+If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
+
+13. Use with the GNU Affero General Public License.
+Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.
+
+14. Revised Versions of this License.
+The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.
+
+If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
+
+Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
+
+15. Disclaimer of Warranty.
+THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+16. Limitation of Liability.
+IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+17. Interpretation of Sections 15 and 16.
+If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”.
+
+You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <http://www.gnu.org/licenses/>.
+
+The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/LICENSES/LicenseRef-AutoconfArchiveException.txt b/LICENSES/LicenseRef-AutoconfArchiveException.txt
new file mode 100644
index 0000000..8b5b467
--- /dev/null
+++ b/LICENSES/LicenseRef-AutoconfArchiveException.txt
@@ -0,0 +1,12 @@
+As a special exception, the respective Autoconf Macro's copyright owner
+gives unlimited permission to copy, distribute and modify the configure
+scripts that are the output of Autoconf when processing the Macro. You
+need not follow the terms of the GNU General Public License when using
+or distributing such scripts, even though portions of the text of the
+Macro appear in them. The GNU General Public License (GPL) does govern
+all other use of the material that constitutes the Autoconf Macro.
+
+This special exception to the GPL applies to versions of the Autoconf
+Macro released by the Autoconf Archive. When you make and distribute a
+modified version of the Autoconf Macro, you may extend this special
+exception to the GPL to apply to your modified version as well.
diff --git a/Makefile.am b/Makefile.am
index a65244a..d7e5975 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,101 +1,468 @@
-# Copyright © 2002-2003 CodeFactory AB
-# Copyright © 2002-2010 Red Hat, Inc.
-# Copyright © 2003 James Willcox
-# Copyright © 2006 Marc-Andre Lureau
-# Copyright © 2006-2018 Collabora Ltd.
-# Copyright © 2010-2012 Mike Gorse
-#
-# SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later
+# Copyright 2006-2022 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+abs_top_srcdir = @abs_top_srcdir@
+abs_top_builddir = @abs_top_builddir@
+installed_testdir = ${libexecdir}/installed-tests/${PACKAGE_TARNAME}
+installed_test_testdir = ${installed_testdir}/test
+installed_test_metadir = ${datadir}/installed-tests/${PACKAGE_TARNAME}
+
+AM_DISTCHECK_CONFIGURE_FLAGS = PYTHON=$(PYTHON) --with-python-prefix='$${prefix}' --with-python-exec-prefix='$${exec_prefix}'
+
+ACLOCAL_AMFLAGS = -I m4
+SUBDIRS = subprojects/dbus-gmain .
+
+CLEANFILES = \
+ test/test-service.log \
+ $(NULL)
EXTRA_DIST = \
+ .reuse/dep5 \
AUTHORS \
CONTRIBUTING.md \
COPYING \
- LICENSES/AFL-2.1.txt \
- LICENSES/GPL-2.0-or-later.txt \
+ LICENSES/GPL-3.0-or-later.txt \
+ LICENSES/LicenseRef-AutoconfArchiveException.txt \
LICENSES/MIT.txt \
- README.md \
- meson.build \
- meson_options.txt \
- tests/meson.build \
- tests/use-as-subproject.py \
- tests/use-as-subproject/README \
- tests/use-as-subproject/meson.build \
- tests/use-as-subproject/use-dbus-gmain.c \
+ dbus-python.pc.in \
+ doc/_static/.gitignore \
+ examples/example-async-client.py \
+ examples/example-client.py \
+ examples/example-service.py \
+ examples/example-signal-emitter.py \
+ examples/example-signal-recipient.py \
+ examples/gconf-proxy-client.py \
+ examples/gconf-proxy-service2.py \
+ examples/list-system-services.py \
+ examples/unix-fd-client.py \
+ examples/unix-fd-service.py \
+ setup.py \
+ test/TestSuitePythonService.service.in \
+ test/tmp-session-bus.conf.in \
+ tools/check-c-style.sh \
+ tools/check-coding-style.mk \
+ tools/check-py-style.sh \
+ tools/check-whitespace.sh
+ tools/ci-build.sh \
$(NULL)
+# === C code ===
+
AM_CPPFLAGS = \
- -I$(top_srcdir) \
+ -include config.h \
+ -I$(top_srcdir)/include \
-I$(top_srcdir)/subprojects \
- -I$(top_builddir) \
- -I$(top_builddir)/subprojects \
+ -I$(top_srcdir)/subprojects/dbus-gmain \
$(DBUS_CFLAGS) \
$(GLIB_CFLAGS) \
+ $(PYTHON_CPPFLAGS) \
+ $(NULL)
+AM_CFLAGS = \
+ $(WARN_CFLAGS) \
+ $(NULL)
+AM_LDFLAGS = \
+ $(WARN_LDFLAGS) \
+ $(NULL)
+
+pymod_ldflags = \
+ -module \
+ -avoid-version \
+ $(NULL)
+pymod_libadd = \
+ $(NULL)
+
+if WINDOWS
+# Win32 DLLs can't have undefined symbols (so this needs explicit linking
+# against the Python DLL), and Python expects extensions to be *.pyd
+# instead of *.dll
+pymod_ldflags += \
+ -no-undefined \
+ -shrext ".pyd" \
+ $(NULL)
+pymod_libadd += \
+ $(PYTHON_LIBS) \
+ $(NULL)
+endif
+
+pyexec_LTLIBRARIES = \
+ _dbus_bindings.la \
+ _dbus_glib_bindings.la \
+ $(NULL)
+
+if ENABLE_INSTALLED_TESTS
+nobase_installed_test_LTLIBRARIES = test/dbus_py_test.la
+else
+noinst_LTLIBRARIES = test/dbus_py_test.la
+endif
+
+_dbus_bindings_la_LDFLAGS = \
+ $(pymod_ldflags) \
+ -export-symbols-regex \(PyInit__\|init_\)dbus_bindings \
+ $(AM_LDFLAGS) \
+ $(NULL)
+_dbus_bindings_la_LIBADD = \
+ $(pymod_libadd) \
+ $(DBUS_LIBS) \
+ $(NULL)
+_dbus_bindings_la_SOURCES = \
+ dbus_bindings/abstract.c \
+ dbus_bindings/bus.c \
+ dbus_bindings/bytes.c \
+ dbus_bindings/compat-internal.h \
+ dbus_bindings/conn.c \
+ dbus_bindings/conn-internal.h \
+ dbus_bindings/conn-methods.c \
+ dbus_bindings/containers.c \
+ dbus_bindings/dbus_bindings-internal.h \
+ dbus_bindings/debug.c \
+ dbus_bindings/exceptions.c \
+ dbus_bindings/float.c \
+ dbus_bindings/generic.c \
+ dbus_bindings/int.c \
+ dbus_bindings/unixfd.c \
+ dbus_bindings/libdbusconn.c \
+ dbus_bindings/mainloop.c \
+ dbus_bindings/message-append.c \
+ dbus_bindings/message.c \
+ dbus_bindings/message-get-args.c \
+ dbus_bindings/message-internal.h \
+ dbus_bindings/module.c \
+ dbus_bindings/pending-call.c \
+ dbus_bindings/server.c \
+ dbus_bindings/signature.c \
+ dbus_bindings/string.c \
+ dbus_bindings/types-internal.h \
+ dbus_bindings/validation.c \
$(NULL)
-noinst_LTLIBRARIES = \
- libdbus-gmain.la \
- tests/libtest.la \
+subprojects/dbus-gmain/libdbus-gmain.la:
+ $(MAKE) -C subprojects/dbus-gmain
+
+_dbus_glib_bindings_la_LDFLAGS = \
+ $(pymod_ldflags) \
+ -export-symbols-regex \(PyInit__\|init_\)dbus_glib_bindings \
+ $(AM_LDFLAGS) \
+ $(NULL)
+_dbus_glib_bindings_la_LIBADD = \
+ subprojects/dbus-gmain/libdbus-gmain.la \
+ $(pymod_libadd) \
+ $(DBUS_LIBS) \
+ $(NULL)
+_dbus_glib_bindings_la_SOURCES = \
+ dbus_glib_bindings/module.c \
$(NULL)
-libdbus_gmain_la_SOURCES = \
- dbus-gmain.c \
- dbus-gmain/dbus-gmain.h \
+# unconditionally add an -rpath to force Libtool to build a shared library
+test_dbus_py_test_la_LDFLAGS = \
+ $(pymod_ldflags) \
+ $(AM_LDFLAGS) \
+ -rpath $(installed_testdir) \
+ $(NULL)
+test_dbus_py_test_la_LIBADD = $(DBUS_LIBS)
+test_dbus_py_test_la_SOURCES = \
+ include/dbus/dbus-python.h \
+ test/dbus_py_test.c \
$(NULL)
-libdbus_gmain_la_LIBADD = $(DBUS_LIBS) $(GLIB_LIBS)
-libdbus_gmain_la_LDFLAGS = -no-undefined
+# === dbus package ===
+
+nobase_python_PYTHON = \
+ dbus/bus.py \
+ dbus/connection.py \
+ dbus/_compat.py \
+ dbus/_dbus.py \
+ dbus/decorators.py \
+ dbus/exceptions.py \
+ dbus/_expat_introspect_parser.py \
+ dbus/gi_service.py \
+ dbus/glib.py \
+ dbus/__init__.py \
+ dbus/lowlevel.py \
+ dbus/mainloop/__init__.py \
+ dbus/mainloop/glib.py \
+ dbus/proxies.py \
+ dbus/server.py \
+ dbus/service.py \
+ dbus/types.py
+
+check_py_sources = $(nobase_python_PYTHON)
+include $(top_srcdir)/tools/check-coding-style.mk
+
+# === Devel stuff ===
-tests_libtest_la_SOURCES = \
- tests/util.c \
- tests/util.h \
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = dbus-python.pc
+
+dbusincludedir = $(includedir)/dbus-1.0/dbus
+dbusinclude_HEADERS = include/dbus/dbus-python.h
+
+# === Tests ===
+
+cross-test-compile: all
+
+cross-test-server:
+ $(AM_TESTS_ENVIRONMENT) $(PYTHON) $(top_srcdir)/test/cross-test-server.py
+cross-test-client:
+ $(AM_TESTS_ENVIRONMENT) $(PYTHON) $(top_srcdir)/test/cross-test-client.py
+
+AM_TESTS_ENVIRONMENT = \
+ export DBUS_TOP_SRCDIR="$(abs_top_srcdir)"; \
+ export DBUS_TOP_BUILDDIR="$(abs_top_builddir)"; \
+ export DBUS_TEST_TMPDIR="$(abs_top_builddir)/test"; \
+ export DBUS_TEST_UNINSTALLED=1; \
+ export DBUS_PYTHON_VERSION='$(PACKAGE_VERSION)'; \
+ export PYTHONPATH="$(abs_top_srcdir):$(abs_top_srcdir)/test:$(abs_top_builddir)/.libs:$(abs_top_builddir)/test/.libs"; \
+ export PYTHON='$(PYTHON)'; \
+ export DBUS_FATAL_WARNINGS=1; \
$(NULL)
-tests_libtest_la_LIBADD = $(DBUS_LIBS) $(GLIB_LIBS)
-tests_libtest_la_LDFLAGS = -no-undefined
+TEST_EXTENSIONS = .sh .py
+
+LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
+SH_LOG_DRIVER = $(LOG_DRIVER)
+PY_LOG_DRIVER = $(LOG_DRIVER)
+
+LOG_COMPILER = $(DBUS_RUN_SESSION) \
+ --config-file=$(top_builddir)/test/tmp-session-bus.conf \
+ --
+installed_log_compiler = $(DBUS_RUN_SESSION) \
+ --config-file=$(installed_testdir)/test/tmp-session-bus.conf \
+ --
+
+installed_log_compiler += \
+ env \
+ PYTHON=$(PYTHON) \
+ DBUS_TOP_SRCDIR=$(installed_testdir) \
+ DBUS_TOP_BUILDDIR=$(installed_testdir) \
+ $(NULL)
+
+SH_LOG_COMPILER = $(LOG_COMPILER) $(SHELL)
+PY_LOG_COMPILER = $(LOG_COMPILER) $(PYTHON)
+
+dist_test_sh = \
+ test/run-test.sh \
+ $(NULL)
+
+dist_test_py = \
+ test/test-client.py \
+ test/test-exception-py3.py \
+ test/test-p2p.py \
+ test/test-signals.py \
+ test/test-standalone.py \
+ test/test-unusable-main-loop.py \
+ $(NULL)
+
+dist_test_extra_python = \
+ test/crosstest.py \
+ test/dbus_test_utils.py \
+ $(NULL)
+
+dist_test_extra_scripts = \
+ test/cross-test-client.py \
+ test/cross-test-server.py \
+ test/test-service.py \
+ test/wait-for-name.py \
+ $(NULL)
+
+test_programs = \
+ test/test-import-repeatedly \
+ $(NULL)
+
+test_test_import_repeatedly_SOURCES = test/import-repeatedly.c
+test_test_import_repeatedly_CPPFLAGS = $(PYTHON_CPPFLAGS)
+test_test_import_repeatedly_LDADD = $(PYTHON_LIBS) $(PYTHON_EXTRA_LIBS)
+test_test_import_repeatedly_LDFLAGS = $(PYTHON_EXTRA_LDFLAGS)
TESTS = \
- tests/test-30574 \
+ $(dist_test_py) \
+ $(dist_test_sh) \
+ $(test_programs) \
$(NULL)
-noinst_PROGRAMS = \
- tests/test-30574 \
- tests/test-thread-server \
- tests/test-thread-client \
+noinst_DATA = \
+ test/TestSuitePythonService.service \
+ test/tmp-session-bus.conf \
$(NULL)
+CLEANFILES += $(noinst_DATA)
-tests_test_thread_server_SOURCES = \
- tests/test-thread-server.c \
- tests/test-thread.h \
+installed_test_test_data = \
+ test/installable/TestSuitePythonService.service \
+ test/installable/tmp-session-bus.conf \
$(NULL)
-tests_test_thread_server_LDADD = \
- libdbus-gmain.la \
- tests/libtest.la \
- $(GLIB_THREADS_LIBS) \
- $(GLIB_LIBS) \
- $(DBUS_LIBS) \
+CLEANFILES += $(installed_test_test_data)
+
+$(noinst_DATA): test/%: test/%.in
+ @$(MKDIR_P) $(dir $@)
+ $(AM_V_GEN)$(SED) \
+ -e 's|[@]PYTHON[@]|$(PYTHON)|g' \
+ -e 's|[@]G_TEST_SRCDIR[@]|$(abs_top_srcdir)|g' \
+ -e 's|[@]G_TEST_BUILDDIR[@]|$(abs_top_builddir)|g' \
+ $< > $@
+
+$(installed_test_test_data): test/installable/%: test/%.in
+ @$(MKDIR_P) $(dir $@)
+ $(AM_V_GEN)$(SED) \
+ -e 's|[@]PYTHON[@]|$(PYTHON)|g' \
+ -e 's|[@]G_TEST_SRCDIR[@]|$(installed_testdir)|g' \
+ -e 's|[@]G_TEST_BUILDDIR[@]|$(installed_testdir)|g' \
+ $< > $@
+
+if ENABLE_INSTALLED_TESTS
+nobase_installed_test_PROGRAMS = $(test_programs)
+nobase_dist_installed_test_SCRIPTS = \
+ $(dist_test_py) \
+ $(dist_test_sh) \
+ $(dist_test_extra_scripts) \
+ $(NULL)
+nobase_dist_installed_test_PYTHON = \
+ $(dist_test_extra_python) \
$(NULL)
+installed_test_test_DATA = $(installed_test_test_data)
+installed_test_meta_DATA = $(installed_test_metadata)
+else
+noinst_PROGRAMS = $(test_programs)
+dist_noinst_SCRIPTS = \
+ $(dist_test_py) \
+ $(dist_test_sh) \
+ $(dist_test_extra_scripts) \
+ $(NULL)
+endif
-tests_test_thread_client_SOURCES = \
- tests/test-thread-client.c \
- tests/test-thread.h \
+installed_test_metadata = \
+ $(patsubst %,%.test,$(dist_test_py)) \
+ $(patsubst %,%.test,$(dist_test_sh)) \
+ $(patsubst %,%.test,$(test_programs)) \
$(NULL)
-tests_test_thread_client_LDADD = \
- libdbus-gmain.la \
- tests/libtest.la \
- $(GLIB_THREADS_LIBS) \
- $(GLIB_LIBS) \
- $(DBUS_LIBS) \
+CLEANFILES += $(installed_test_metadata)
+
+$(patsubst %,%.test,$(dist_test_py)): %.test: Makefile
+ @$(MKDIR_P) $(dir $@)
+ $(AM_V_GEN) (echo '[Test]' > $@.tmp; \
+ echo 'Type=session' >> $@.tmp; \
+ echo 'Exec=$(installed_log_compiler) $(PYTHON) $(installed_testdir)/$*' >> $@.tmp; \
+ mv $@.tmp $@)
+
+$(patsubst %,%.test,$(dist_test_sh)): %.test: Makefile
+ @$(MKDIR_P) $(dir $@)
+ $(AM_V_GEN) (echo '[Test]' > $@.tmp; \
+ echo 'Type=session' >> $@.tmp; \
+ echo 'Exec=$(installed_log_compiler) $(installed_testdir)/$*' >> $@.tmp; \
+ mv $@.tmp $@)
+
+$(patsubst %,%.test,$(test_programs)): %.test: Makefile
+ @$(MKDIR_P) $(dir $@)
+ $(AM_V_GEN) (echo '[Test]' > $@.tmp; \
+ echo 'Type=session' >> $@.tmp; \
+ echo 'Exec=$(installed_log_compiler) $(installed_testdir)/$*$(EXEEXT)' >> $@.tmp; \
+ mv $@.tmp $@)
+
+# === Documentation ===
+
+dist-hook:
+ echo $(VERSION) > $(distdir)/.version
+ touch $(distdir)/MANIFEST
+ touch $(distdir)/MANIFEST.in
+ ( cd $(distdir) && $(PYTHON) setup.py egg_info )
+ cp $(distdir)/dbus_python.egg-info/PKG-INFO $(distdir)
+ ( cd $(distdir) && find -type d -o -print ) | \
+ LC_ALL=C sort | \
+ $(SED) -e 's|^\./||' \
+ > $(distdir)/MANIFEST
+ sed -e 's/.*/include &/' < $(distdir)/MANIFEST > $(distdir)/MANIFEST.in
+ cp $(distdir)/MANIFEST $(distdir)/dbus_python.egg-info/SOURCES.txt
+
+maintainer-upload:
+ rsync -tvpP --chmod=ugo=r $(DIST_ARCHIVES) $(DIST_ARCHIVES:%=%.asc) \
+ dbus.freedesktop.org:/srv/dbus.freedesktop.org/www/releases/dbus-python/
+
+sphinx_sources = \
+ doc/API_CHANGES.txt \
+ doc/conf.py \
+ doc/dbus.bus.rst \
+ doc/dbus.connection.rst \
+ doc/dbus.decorators.rst \
+ doc/dbus.exceptions.rst \
+ doc/dbus.gi_service.rst \
+ doc/dbus.glib.rst \
+ doc/dbus.gobject_service.rst \
+ doc/dbus.lowlevel.rst \
+ doc/dbus.mainloop.rst \
+ doc/dbus.proxies.rst \
+ doc/dbus.rst \
+ doc/dbus.server.rst \
+ doc/dbus.service.rst \
+ doc/dbus.types.rst \
+ doc/index.rst \
+ doc/news.rst \
+ doc/PY3PORT.txt \
+ doc/tutorial.txt \
+ NEWS \
+ README \
$(NULL)
+EXTRA_DIST += $(sphinx_sources)
-tests_test_30574_SOURCES = \
- tests/30574.c \
+# A hack used for the HTML documentation on dbus.freedesktop.org
+EXTRA_DIST += \
+ doc/redirects \
+ doc/redirects.py \
$(NULL)
-tests_test_30574_LDADD = \
- libdbus-gmain.la \
- tests/libtest.la \
- $(GLIB_LIBS) \
- $(DBUS_LIBS) \
+
+install-data-local: install-data-local-sphinx
+uninstall-local: uninstall-local-pycache
+uninstall-local: uninstall-local-sphinx
+
+uninstall-local-pycache:
+ rm -fr $(DESTDIR)$(pythondir)/dbus/__pycache__
+ rm -fr $(DESTDIR)$(pythondir)/dbus/mainloop/__pycache__
+
+if ENABLE_DOCUMENTATION
+all: doc/_build/.stamp
+
+doc/_build/.stamp: $(nobase_python_PYTHON) \
+ _dbus_bindings.la \
+ _dbus_glib_bindings.la \
+ $(sphinx_sources) \
+ Makefile \
+ $(NULL)
+ rm -rf doc/_build
+ $(MKDIR_P) doc/_build
+ abs_top_srcdir='$(abs_top_srcdir)' \
+ abs_top_builddir='$(abs_top_builddir)' \
+ DBUS_PYTHON_NO_DEPRECATED=1 \
+ $(PYTHON) -m sphinx -b html $(abs_top_srcdir)/doc doc/_build
+ touch $@
+
+maintainer-update-website: doc/_build/.stamp
+ DBUS_TOP_SRCDIR="$(abs_top_srcdir)" \
+ $(PYTHON) $(srcdir)/doc/redirects.py
+ rsync -rtvzPp --chmod=Dg+s,ug+rwX,o=rX doc/_build/ \
+ dbus.freedesktop.org:/srv/dbus.freedesktop.org/www/doc/dbus-python/
+
+install-data-local-sphinx: doc/_build/.stamp
+ $(mkinstalldirs) $(DESTDIR)$(htmldir)
+ cp -R doc/_build/* $(DESTDIR)$(htmldir)
+
+uninstall-local-sphinx:
+ rm -fr $(DESTDIR)$(htmldir)
+else
+maintainer-update-website:
+ @echo "*** Not updating the API docs on the website - install sphinx"
+ @echo "*** and configure with --enable-api-docs"
+install-data-local-sphinx:
+ @:
+uninstall-local-sphinx:
+ @:
+endif
+
+clean-local:
+ rm -rf doc/_build
+
+check_c_sources = \
+ $(_dbus_bindings_la_SOURCES) \
+ $(_dbus_glib_bindings_la_SOURCES) \
$(NULL)
-LOG_COMPILER = $(DBUS_RUN_SESSION) --
+include $(top_srcdir)/tools/check-coding-style.mk
+
+.PHONY: cross-test-compile cross-test-server cross-test-client \
+ always-rebuild maintainer-update-website \
+ maintainer-upload
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..0175f4e
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,918 @@
+dbus Python Bindings 1.2.20 (UNRELEASED)
+========================================
+
+Dependencies:
+
+• Python 3, version 3.5 or later, is required.
+ A security-supported version (currently 3.6 or later) is recommended.
+ Python 2 is no longer supported.
+
+API changes:
+
+• dbus.gobject_service, dbus.types.UTF8String and the utf8_strings
+ keyword argument were only usable with Python 2, and therefore have
+ been removed, along with the rest of the special cases for Python 2.
+ (dbus-python!115; Simon McVittie)
+
+Fixes:
+
+• Fix undefined escape sequences in docstrings
+ (dbus-python#40; Jason A. Yundt)
+
+• Make `distcheck` compatible with Automake 1.16.4, by forcing an
+ appropriate prefix to be used
+ (dbus-python!115; Simon McVittie)
+
+• Update AX_PYTHON_DEVEL from autoconf-archive for better
+ forwards-compatibility
+ (dbus-python!115; Simon McVittie)
+
+• Avoid various deprecation warnings in the build system
+ · distutils.sysconfig → sysconfig
+ · distutils.util → sysconfig
+ · AC_PROG_LIBTOOL → LT_INIT
+ · AC_HELP_STRING → AS_HELP_STRING
+ (dbus-python!115; Simon McVittie)
+
+• Fix CI for recent Python and Automake versions
+ (dbus-python!115; Simon McVittie)
+
+• Update bug reporting URL
+ (dbus-python!115; Simon McVittie)
+
+dbus Python Bindings 1.2.18 (2021-07-20)
+========================================
+
+Build-time configuration changes:
+
+• dbus-python will be built for python3 if neither PYTHON nor
+ PYTHON_VERSION is specified. Use a command like
+
+ ./configure PYTHON=$(command -v python2)
+
+ if installation for Python 2 (EOL 2020-01-01) is required.
+
+Dependencies:
+
+• Python 2 reached end-of-life on 2020-01-01. A future version of
+ dbus-python is likely to remove Python 2 support.
+
+Fixes:
+
+• Move from collections.Sequence to collections.abc.Sequence on
+ Python ≥ 3.3, for Python 3.10 compatibility
+ (dbus-python#37; Simon McVittie)
+
+• Avoid another deprecation warning for inspect.getargspec().
+ This is similar to the one fixed in 1.2.4, but for dbus.decorators.signal
+ rather than dbus.decorators.method. (dbus-python!8; Martin Stumpf)
+
+• Fix an unlikely fd leak if memory allocation fails for UnixFd
+ (dbus-python!9, Red Hat #1938703; David King)
+
+• Fix memory and fd leak if UnixFd is given an invalid negative
+ variant_level (Simon McVittie)
+
+• Avoid more deprecation warnings:
+ - gi.repository.GObject.MainLoop etc. (now used via gi.repository.GLib)
+ - gi.repository.GLib.threads_init (no longer necessary at all)
+ (Simon McVittie)
+
+• Disable -Wdeclaration-after-statement. Python 3.9 relies on intermixed
+ declarations and statements in its headers, so we can no longer
+ enforce this. (Simon McVittie)
+
+• Convert examples to Python 3 (Simon McVittie)
+
+• Use the same Python executable for build and dist/distcheck by default
+ (Simon McVittie)
+
+CI fixes:
+
+• Stop installing tap.py for Python 2. The latest version only supports
+ Python 3. (Simon McVittie)
+
+• Move from Python 3.8 on Debian unstable (no longer available) to
+ 3.9 on Debian 11 (Simon McVittie)
+
+• Also test with Python 3.10 on Ubuntu 21.10 (Simon McVittie)
+
+• Remove Travis-CI integration, only use Gitlab-CI (Simon McVittie)
+
+dbus Python Bindings 1.2.16 (2020-01-14)
+========================================
+
+The “bag of assorted swords” release.
+
+Enhancements:
+
+• All tests are run even if the tap.py module is not available, although
+ diagnostics for failing tests will be better if it is present.
+ (Simon McVittie)
+
+Fixes:
+
+• Forbid unexpanded AX-prefixed macros more selectively, similar to
+ dbus#249. (Simon McVittie)
+
+dbus Python Bindings 1.2.14 (2019-11-25)
+========================================
+
+The “don't stand in the fire” release.
+
+Fixes:
+
+• Ensure that the numeric types from dbus.types get the same str()
+ under Python 3.8 that they did under previous versions. Previously,
+ Python 3.8 used their repr() for the str(), which was not intended.
+ (dbus-python#31; matclab, Simon McVittie)
+
+• Disable -Winline (Simon McVittie)
+
+• Add Python 3.8 to CI (Simon McVittie)
+
+dbus Python Bindings 1.2.12 (2019-09-12)
+========================================
+
+The “spinal bap” release.
+
+Fixes:
+
+• Don't save and restore the exception indicator when called from C code.
+ This avoided "returned a result with an error set" errors under some
+ circumstances, but also caused a regression for code that relies on
+ being able to terminate the program by calling sys.exit() or raising
+ SystemExit from a dbus-python method. In particular, this broke one of
+ libsecret's unit tests. (Debian #940087; Simon McVittie)
+
+D-Bus Python Bindings 1.2.10 (2019-09-02)
+=========================================
+
+The “wingèd horse” release.
+
+dbus-python version control is now hosted on freedesktop.org's Gitlab
+installation, and bug reports and feature requests have switched from
+Bugzilla bugs (indicated by "fd.o #nnn") to Gitlab issues
+("dbus-python#nnn") and merge requests ("dbus-python!nnn"). See README
+and CONTRIBUTING.md for more details.
+
+Dependencies:
+
+• dbus 1.8 was already required, but is more strongly required now:
+ the workarounds that were used to run continuous integration with dbus
+ 1.6 on Ubuntu 14.04 'trusty' have been removed. (Note that dbus 1.8
+ has already reached end-of-life for security support, and newer dbus
+ stable branches are strongly recommended.)
+
+• When using Python 3, version 3.5 or later is strongly recommended.
+ Python 3.4 security support ended in March 2019. No specific
+ incompatibilities are known, but using dbus-python on Python 3.4 is
+ no longer tested or supported.
+
+Enhancements:
+
+• Rewrite CONTRIBUTING.md document, based on Wayland's equivalent
+ (Simon McVittie, with thanks to Ander Conselvan de Oliveira,
+ Bryce Harrington, Eric Engestrom, Pekka Paalanen and Daniel Stone
+ for their contributions to the equivalent file in Wayland)
+
+• A generated ChangeLog file is no longer included in source tarballs.
+ Please refer to the git repository at
+ https://gitlab.freedesktop.org/dbus/dbus-python for detailed change
+ history. (Simon McVittie)
+
+• Improve continuous integration to be run by GitLab in addition to
+ Travis-CI (Simon McVittie)
+
+• Add clearer license information using SPDX-License-Identifier
+ (Simon McVittie)
+
+• Improve test coverage (Simon McVittie)
+
+Fixes:
+
+• Don't set deprecated tp_print to NULL under Python 3, fixing build
+ warnings with Python 3.8 pre-releases (Simon McVittie)
+
+• Include inherited methods and properties when documenting objects,
+ which regressed when migrating from epydoc to sphinx
+ (Simon McVittie)
+
+• Add missing variant_level member to UnixFd type, for parity with the
+ other dbus.types types (dbus-python!3; John Baublitz)
+
+ - Note that this is a potentially incompatible change: unknown
+ keyword arguments were previously ignored (!) and are now an error.
+
+• Don't reply to method calls if they have the NO_REPLY_EXPECTED flag
+ (fd.o#32529, dbus-python#26; Simon McVittie)
+
+• Silence -Wcast-function-type with gcc 8 (Simon McVittie)
+
+• Fix distcheck with python3.7 by deleting __pycache__ during uninstall
+ (Simon McVittie)
+
+• Consistently save and restore the exception indicator when called
+ from C code (Simon McVittie)
+
+• Avoid a long-standing race condition in the automated tests
+ (Debian #898158; Simon McVittie)
+
+• Fix Qt website URL (Ralf Habacker)
+
+D-Bus Python Bindings 1.2.8 (2018-05-04)
+========================================
+
+The “cursed ice surface” release.
+
+Dependencies:
+
+• Documentation requires Sphinx and the readthedocs theme
+• Documentation no longer requires epydoc
+
+Enhancements:
+
+• Build documentation with Sphinx instead of epydoc
+
+• Remove obsolete COMPAT.txt, documenting compatibility breaks in
+ versions over a decade old
+
+Fixes:
+
+• Make sure $(builddir)/test exists before creating .test files there
+
+• Add PKG-INFO and egg_info to dist tarballs so they can be uploaded
+ to PyPI again
+
+D-Bus Python Bindings 1.2.6 (2018-01-29)
+========================================
+
+The “doppler radar” release.
+
+Dependencies:
+
+• When using Python 2, version 2.7 is now required. Python 2.6 security
+ support ended in 2013.
+• When using Python 3, version 3.4 or later is now required.
+ Python 3.2 security support ended in 2016, and Python 3.3 security
+ support ended in 2017.
+• Most unit tests now require the tap.py module from PyPI.
+• The deprecated dbus-glib library is no longer required. A bundled copy
+ of its main loop integration code is included instead.
+• GLib version 2.40 or later is required.
+• libdbus version 1.8 or later is required.
+
+Enhancements:
+
+• AX_PYTHON_DEVEL is now used to find the CPPFLAGS, LDFLAGS and libraries
+ needed to link test-import-repeatedly to libpython, which should reduce
+ the number of wheels reinvented here.
+
+• Remove workarounds for Python 2.6 limitations
+
+• All unit tests now produce structured output (TAP)
+
+Fixes:
+
+• Fix a NULL dereference in constructing a Server if the underlying C
+ function fails
+
+• Silence compiler warnings triggered by the Python headers under gcc 7
+
+• Avoid __gtype__ appearing in documentation, for reproducible builds
+
+• Rename _dbus_bindings/ and _dbus_glib_bindings/ source directories to
+ dbus_bindings/ and dbus_glib_bindings/ to avoid an ImportWarning
+ appearing in the API documentation, which made the documentation build
+ non-reproducible
+
+D-Bus Python Bindings 1.2.4 (2016-03-06)
+========================================
+
+The “75,000 microchips” release.
+
+Enhancements:
+
+• Continous integration metadata for travis-ci.org is now available.
+ Similar to dbus, this is split into .travis.yml (Travis-specifics)
+ and tools/ci-build.sh (intended to be useful for any CI framework,
+ although it does include various workarounds for travis-ci oddities).
+ (Simon McVittie)
+
+Fixes:
+
+• Make dbus.version a tuple again, not a list, for consistent sorting.
+ This was a regression in 1.2.2. (Debian #816729, Simon McVittie)
+
+• Use inspect.signature() instead of inspect.getargspec() on Python
+ versions that have it. inspect.getargspec() is deprecated in recent
+ Python 3 and seems to have disappeared from 3.6 nightly builds.
+ (Simon McVittie)
+
+• Make the tests pass in "narrow" Python builds where unicode objects
+ are UTF-16, rather than the UCS-4 used in Linux distributions.
+ (fd.o #57140, Simon McVittie)
+
+• Always include headers in a consistent order (Debian #749133, Simon McVittie)
+
+• Include config.h in all C code that we compile. This is necessary
+ on platforms where it might contain something like "#define _GNU_SOURCE"
+ or "#define inline __inline".
+ (Simon McVittie)
+
+D-Bus Python Bindings 1.2.2 (2016-02-22)
+========================================
+
+The “mind fray” release.
+
+Versioning changes:
+
+• dbus-python releases now have an even micro version (1.2.0, 1.2.2),
+ and snapshots from git have an odd micro version (1.2.1).
+
+Dependencies:
+
+• Building from git (but not from tarballs) now requires
+ macros from the GNU Autoconf Archive, for example the autoconf-archive
+ package in Debian or Fedora derivatives.
+
+• Building from git (but not from tarballs) now requires Automake 1.13
+ or later.
+
+• The automated tests and some examples now require PyGI (the gi module),
+ not PyGObject 2 (the deprecated glib and gobject modules).
+
+Enhancements:
+
+• There is now a setuptools setup.py, allowing dbus-python to be installed
+ into a virtualenv using pip from a standard Automake source release. This
+ requires pre-existing system-wide installations of the normal build
+ dependencies (pkg-config, libdbus, dbus-glib, a C compiler) and has
+ some limitations. For system-wide installations and development,
+ please use the Autoconf/Automake build system directly.
+ (fd.o #55439; Simon McVittie)
+
+• dbus-python now uses the common compiler warnings from AX_COMPILER_FLAGS
+ (Simon McVittie)
+
+• The automated tests can now be installed as GNOME-style "installed tests",
+ and should be somewhat more reliable (Simon McVittie)
+
+Fixes:
+
+• ``from dbus.service import *`` now imports FallbackObject
+ (fd.o #85720; Ben Longbons)
+
+• The GConf-related examples work again (fd.o #85720; Ben Longbons)
+
+• Consistently make examples executable, and install them all
+ (fd.o #85720; Ben Longbons)
+
+• Search PATH for an appropriately-versioned pythonX.Y-config, or as a last
+ resort python-config, if there isn't a ${PYTHON}-config in the
+ same directory as ${PYTHON} (fd.o #92085; Yamashita, Yuu)
+
+• Add support for the Automake 1.13 parallel test driver (Simon McVittie)
+
+• Skip building API documentation if "import epydoc" fails (Simon McVittie)
+
+D-Bus Python Bindings 1.2.0 (2013-05-07)
+========================================
+
+The "compile like it's 1998" release.
+
+Dependencies:
+
+• libdbus 1.6 or later is now required.
+
+Enhancements:
+
+• Unicode Corrigendum 9: when used with a suitable version of libdbus
+ (1.6.10 or later, or 1.7.2 or later), noncharacters in strings are
+ now accepted
+
+Fixes:
+
+• Support DBusException('something with non—ASCII') under Python 2
+ (Michael Vogt, smcv; fd.o #55899)
+
+• Correct some misleading wording in COPYING which was written under the
+ assumption that libdbus could actually be relicensed to MIT/X11
+ (Thiago Macieira)
+
+• Avoid variable-length arrays, because MSVC++ is still stuck in 1998
+ (based on patches from Christoph Höger, fd.o #51725)
+
+• Remove unnecessary uses of stdint.h (fd.o #51725)
+
+• Add support for Unix compilers not supporting 'inline', for completeness
+
+• Use GObject.__class__ instead of GObjectMeta, which can no longer be
+ imported from gi.repository.GObject in pygobject 3.8
+
+• Fix autoreconfiscation on Automake 1.13 (Marko Lindqvist, fd.o #59006)
+
+D-Bus Python Bindings 1.1.1 (2012-06-25)
+========================================
+
+The "Lemonade Sky" release.
+
+Dependencies:
+
+• libdbus 1.6 or later is now recommended. It is not strictly required yet.
+
+Fixes:
+
+• Validate UTF-8 according to the rules libdbus uses, falling back to our
+ own (inefficient) implementation if not compiled against dbus >= 1.6
+ (fd.o #40817)
+
+• Under Python 3, in the absence of introspection or signature='...',
+ pass dbus.ObjectPath or dbus.Signature arguments with the obvious
+ signature 'o' or 'g', not 's'. This previously only worked in Python 2.
+ (fd.o #50740)
+
+D-Bus Python Bindings 1.1.0 (2012-05-09)
+========================================
+
+The “eaten by spiders” release.
+
+Deprecations:
+
+• dbus.gobject_service is deprecated. Use dbus.gi_service and PyGI in new code.
+
+API changes:
+
+• dbus.gobject_service works in legacy PyGObject 2 applications again,
+ like it did before 1.0. The down side is that it doesn't work in all PyGI
+ applications any more, unlike 1.0. In PyGI applications, depend on
+ dbus-python >= 1.1 and use dbus.gi_service instead - its API is the same.
+ (fd.o #48904, Debian #670516)
+
+• dbus.gobject_service has been removed from Python 3 builds altogether.
+
+Enhancements:
+
+• Use DBusBasicValue from libdbus 1.5, if available, rather than reinventing it
+
+Fixes:
+
+• Put sockets for the regression tests in /tmp, not the builddir, fixing
+ test failures in a really long builddir (fd.o #46747)
+
+• Fix a reference leak in dbus_py_variant_level_set (fd.o #47108)
+
+• Modify AM_CHECK_PYTHON_HEADERS so the "another way" works with Python 3
+
+D-Bus Python Bindings 1.0.0 (2012-01-24)
+========================================
+
+The "never trust a .0 release?" release.
+
+Dependencies:
+
+* libdbus 1.4 or later is now required.
+
+* Python 2.6 or later is now required. If Python 3 is used, it must be
+ version 3.2 or later.
+
+* GNU make (or, at your own risk, another make with the GNU $(patsubst)
+ extension) is now required.
+
+API changes:
+
+* dbus_bindings, which was never meant to be public API and has been
+ deprecated for nearly 5 years, has finally been removed.
+
+* The repr() of every dbus-python object is now unicode.
+
+* The Python 3 API is not the same as the Python 2 API; see PY3PORT.rst
+ for details.
+
+• dbus.gobject_service uses PyGI, not PyGObject. (This was not meant to be
+ an incompatible change, but unfortunately, it was. It was reverted in 1.1.0.)
+
+Enhancements:
+
+* Python 3 compatibility (fd.o #26420, Barry Warsaw)
+
+* MethodCallMessage and SignalMessage now have a more useful repr()
+ (Barry Warsaw)
+
+Fixes:
+
+* OOM while appending a unicode object to a message no longer leaks a string
+ (Barry Warsaw)
+
+* If libdbus somehow gives us invalid UTF-8, don't crash (Barry Warsaw)
+
+* Fix rst2html failure in non-UTF-8 locales (Alexandre Rostovtsev)
+
+D-Bus Python Bindings 0.84.0 (2011-05-25)
+=========================================
+
+The "Comrade Bill Bartram's Egalitarian Anti-Imperialist Soviet Stout" release.
+
+Enhancements:
+
+* fd.o #30812: add the UnixFd type, and support Unix fd passing if
+ compiled against a new enough libdbus (Elvis Pfützenreuter)
+
+* fd.o #34342: add Connection.set_allow_anonymous(bool) (Scott Tsai)
+
+* fd.o #21017: add configure arguments PYTHON_INCLUDES and PYTHON_LIBS which
+ can be used to override $PYTHON-config (Simon McVittie, based on a patch from
+ Robert Schwebel)
+
+Fixes:
+
+* fd.o #35626: clear ProxyObject's pending introspection queue after
+ execution (Scott Tsai)
+
+* fd.o #22560: remove duplicate code from example-async-client (Simon McVittie)
+
+* fd.o #36206: allow signature='x' among ProxyObject method arguments
+ (Simon McVittie)
+
+D-Bus Python Bindings 0.83.2 (2010-12-02)
+=========================================
+
+Dependencies:
+
+* libdbus 1.2 is still supported, but libdbus >= 1.4 is recommended.
+
+Fixes:
+
+* Make BusConnection.list_activatable_names actually call ListActivatableNames,
+ not ListNames (Johan Sandelin)
+
+* Don't override CFLAGS when adding compiler warnings
+ (Louis-Francis Ratté-Boulianne)
+
+* Fix compilation on platforms where Py_ssize_t is larger than int, like x86-64
+ (Elvis Pfützenreuter)
+
+* fd.o #21831: deserialize empty byte arrays with byte_arrays=True as
+ ByteArray(''), not ByteArray('None') (Simon McVittie)
+
+* fd.o #23278, #25105: fix crashes when trying to append more struct entries
+ than the signature allows with libdbus 1.4 (Simon McVittie)
+
+* fd.o #23831: fix crashes when an embedded Python interpreter imports dbus,
+ is finalized, is re-initialized, and re-imports dbus (Simon McVittie)
+
+D-Bus Python Bindings 0.83.1 (2010-02-18)
+=========================================
+
+Fixes:
+
+* fd.o #21172: avoid some deprecation warnings in Python 2.6
+
+* fd.o #15013: add dbus.lowlevel.MESSAGE_TYPE_SIGNAL etc., for those who care
+ about message types at a low level
+
+* When removing signal matches, clean up internal state, avoiding a memory
+ leak in long-lived Python processes that connect to signals from arbitrarily
+ many object paths (fd.o #17551, thanks to Marco Pesenti Gritti)
+
+* When setting the sender of a message, allow it to be org.freedesktop.DBus
+ so you can implement a D-Bus daemon in pure Python (patch from Huang Peng)
+
+D-Bus Python Bindings 0.83.0 (2008-07-23)
+=========================================
+
+Features:
+
+* Add bindings for DBusServer (thanks to Mathias Hasselmann, Huang Peng;
+ fd.o #14322, #15514).
+
+* Omit the service's traceback from certain D-Bus errors: specifically, those
+ that were probably deliberately raised as part of an API. Subclasses
+ of DBusException that indicate programmer error can turn the traceback
+ back on if it seems likely to be useful.
+
+Fixes:
+
+* Don't emit spurious Error messages if libdbus gives object-path handlers
+ a message that isn't a method call (most likely because of binding to a
+ locally emitted signal, as in fd.o #14199).
+
+* Make multiple filters added by Connection.add_message_filter work
+ (fd.o #15547, thanks to Huang Peng).
+
+* Make the API docs build correctly when out-of-tree
+
+* Require dbus 1.0 so we can get rid of DBUS_API_SUBJECT_TO_CHANGE
+
+D-Bus Python Bindings 0.82.4 (2007-12-10)
+=========================================
+
+Fixes:
+
+* supplying reply_handler but not error_handler raises
+ MissingReplyHandlerException instead of MissingErrorHandlerException,
+ and vice versa (fd.o #12304, patch from René Neumann)
+* Using non-recursive make for dbus/ directory should fix builds in some
+ environments (fd.o #12741)
+
+Licensing:
+
+* Everything is now under the same MIT/X11 license used for Collabora code in
+ the previous release
+* Added copyright headers to some files that were still missing them
+
+D-Bus Python Bindings 0.82.3 (2007-09-27)
+=========================================
+
+Fixes:
+
+* Out-of-tree builds with an absolute $(srcdir) can now build docs and run tests
+* Closing private dbus.Bus no longer raises KeyError (fd.o #12096)
+* async_err_cb(MyException()) now works (fd.o #12403)
+* dbus.service.Object.remove_from_connection no longer claims that multiple
+ exports aren't possible (fd.o #12432)
+* Setting _dbus_error_name as a class attribute of DBusException subclasses
+ works again
+
+Deprecations:
+
+* dbus.Bus(private=True) (use dbus.bus.BusConnection in new code, dbus.Bus
+ basically just adds the shared-connection behaviour)
+
+Licensing:
+
+* Code for which Collabora is the only copyright holder is now under the
+ same permissive MIT/X11 license under which dbus core is being relicensed
+ (this allows everything the old license would have allowed, and more)
+
+D-Bus Python Bindings 0.82.2 (2007-08-01)
+=========================================
+
+Incompatibility with 0.82.1:
+
+* If you pass the timeout argument to call_async or an asynchronous proxy
+ method call and expect it to be in milliseconds, you should change the
+ argument to be in seconds, and require dbus-python >= 0.82.2.
+
+ This feature didn't work at all in versions prior to 0.82.1, so any code
+ that works with 0.82.0 or earlier is unaffected.
+
+Features:
+
+* @dbus.service.method supports a rel_path_keyword argument for the benefit
+ of fallback objects, which provides the method implementation with the path
+ of the object within the exported subtree. For instance, if you have a
+ fallback object exported at /Fallback, and you call a method that has
+ rel_path_keyword='rel_path' on /Fallback and on /Fallback/Some/Where, the
+ method implementation will be called with rel_path='/' and with
+ rel_path='/Some/Where' respectively. (fd.o #11623)
+
+* If you have epydoc version 3 (currently in beta), API documention is now
+ generated by default.
+
+Fixes:
+
+* As mentioned under "Incompatibilities" above, Connection.call_async()
+ measures timeouts in seconds, as was always intended.
+ This means that calls through a proxy object with a reply_handler and
+ error_handler will measure the timeout in seconds too.
+
+* Introspect() now works on objects exported in more than one location.
+ (fd.o #11794)
+
+* Building against Python 2.4 on non-Debian-derived distributions, or a
+ non-default Python version on Gentoo, should work again (revenge
+ of fd.o #11282, thanks Eyal Ben David).
+
+D-Bus Python Bindings 0.82.1 (2007-07-11)
+=========================================
+
+The "double precision" release.
+
+Fixes:
+
+* Parse the timeout correctly in send_message_with_reply() and
+ send_message_with_reply_and_block(), fixing the use of non-default timeouts
+ (bugs.fd.o #11489)
+* The tutorial no longer uses interactive-Python syntax, as it confused users.
+ (bugs.fd.o #11209)
+* When making a call via a proxy object with ignore_reply=True, also get the
+ necessary introspection data asynchronously. This can avoid deadlocks in
+ some cases, such as calling methods in the same process (though this is not
+ recommended, for efficiency and sanity reasons).
+* dbus.lowlevel exposes enough constants to write correct filter functions.
+* We don't use dbus_watch_get_fd() (deprecated in libdbus) unless our libdbus
+ is too old to have the modern replacement, dbus_watch_get_unix_fd().
+
+Deprecations:
+
+* Omitting the bus argument in the BusName constructor is deprecated.
+ The fact that it uses the globally shared connection to the session bus by
+ default is uncomfortably subtle.
+
+D-Bus Python Bindings 0.82.0 (2007-06-19)
+=========================================
+
+Features:
+
+* dbus.service.Object can start off with no Connection or object path, and
+ become exported later. If suitable class attributes are set, objects can
+ even be exported on multiple connections, or with multiple object-paths,
+ or both.
+
+* dbus.service.FallbackObject implements a whole subtree of object-path space
+ (fd.o #9295).
+
+* ``@method`` accepts a parameter ``connection_keyword`` so methods can find
+ out which connection to use for any follow-up actions.
+
+* ``@signal`` has a new parameter ``rel_path_keyword`` which gets the path at
+ which to emit the signal, relative to the path of the FallbackObject.
+ ``path_keyword`` is now deprecated, and will raise an exception if used
+ on an object with ``SUPPORTS_MULTIPLE_OBJECT_PATHS``, including any
+ ``FallbackObject``.
+
+Fixes:
+
+* In watch_name_owner, only the desired name is watched!
+
+* When cleaning up signal matches, errors are ignored. This avoids using up
+ scarce pending-call allowance on dbus-daemon < 1.1, and emitting error
+ messages if we get disconnected.
+
+* Signal handlers which are bound to a unique name are automatically
+ disconnected when the unique name goes away, reducing the likelihood that
+ applications will leak signal matches.
+
+* Some corrections were made to the tutorial (@service and @method take a
+ parameter dbus_interface, not just interface; fd.o #11209).
+
+* ${PYTHON}-config is used to get the Python include path (patch from
+ Sebastien Bacher/Ubuntu, fd.o #11282).
+
+D-Bus Python Bindings 0.81.1 (4 June 2007)
+==========================================
+
+Features:
+
+* When an Error message on the bus is represented as a DBusException, the
+ error name is copied into the exception and can be retrieved by
+ get_dbus_name(). Exception handlers should use this instead of looking at
+ the stringified form of the exception, unless backwards compatibility
+ is needed.
+* DBusException objects now get all arguments from the Error message, not
+ just the first (although there will usually only be one). Use the 'args'
+ attribute if you need to retrieve them.
+* The Connection, BusConnection and Bus classes have a method
+ list_exported_child_objects(path: str) -> list of str, which wraps
+ dbus_connection_list_registered()
+* You can remove objects from D-Bus before they become unreferenced, by
+ using dbus.service.Object.remove_from_connection()
+ (https://bugs.freedesktop.org/show_bug.cgi?id=10457)
+
+Bug fixes:
+
+* Don't deadlock when removing a signal match that tracks name-owner changes.
+ (http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=426412)
+* Include child nodes in introspection using list_exported_child_objects()
+
+D-Bus Python Bindings 0.81.0 (9 May 2007)
+=========================================
+
+The 'series of tubes' release
+-----------------------------
+
+This is a feature release with support for non-bus-daemon connections
+and improved GObject integration.
+
+Features:
+
+* Bus has a superclass dbus.bus.BusConnection (a connection to a bus daemon,
+ but without the shared-connection semantics or any deprecated API)
+ for the benefit of those wanting to subclass bus daemon connections
+
+* BusConnection has a superclass dbus.connection.Connection (a
+ connection without a bus daemon) for use in peer-to-peer situations,
+ or distributed pseudo-bus situations without a bus daemon such as
+ Telepathy's Tubes API
+
+* dbus.gobject_service.ExportedGObject is like dbus.service.Object, but
+ is also a subclass of GObject (with the necessary metaclass magic to
+ make this work). Until someone has verified that the GObject side of
+ things works as expected too, I consider this API to be potentially
+ subject to change!
+
+* Connection and BusConnection have gained a number of useful methods,
+ including watch_name_owner (track name owner changes asynchronously,
+ avoiding race conditions), call_blocking and call_async (blocking and
+ asynchronous method calls without going via a proxy - note that these
+ are semi-low-level interfaces which don't do introspection), and
+ list_names, list_activatable_names and get_name_owner which are
+ simple wrappers for the corresponding org.freedesktop.DBus methods
+
+* dbus.Interface (now also available at dbus.proxies.Interface)
+ and dbus.proxies.ProxyObject now have some reasonably obvious properties.
+
+Deprecations:
+
+* All keyword arguments called named_service are deprecated in favour of an
+ argument called bus_name (to be compatible with both older and newer
+ dbus-python, you should pass these positional arguments).
+
+* The bus keyword argument to dbus.proxies.ProxyObject is deprecated in
+ favour of an argument called conn, because proxies will work on non-bus
+ connections now (again, for maximum compatibility you should use a
+ positional argument for this).
+
+* No warning is raised for this, but I consider calling any remote method
+ on a ProxyObject or Interface whose name is either alllowercase or
+ lower_case_with_underscores to be deprecated, and reserve the right
+ to add properties or methods of this form in future releases - use
+ ProxyObject.get_dbus_method if you must call a remote method named in
+ this way. Methods named following TheUsualDBusConvention or
+ theJavaConvention are safe.
+
+Bugfixes:
+
+* Exceptions in signal handlers print a stack trace to stderr (this can
+ be redirected elsewhere with Python's logging framework). Partially
+ addresses fd.o #9980.
+
+* The reserved local interface and object path are properly checked for.
+
+* When you return a tuple that is not a Struct from a method with no
+ out_signature, it's interpreted as multiple return values, not a
+ single Struct (closes fd.o #10174).
+
+* If send_with_reply() returns TRUE but with pending call NULL, dbus-python
+ no longer crashes. This can happen when unexpectedly disconnected.
+
+* Arguments are not examined for functions declared METH_NOARGS (this is
+ unnecessary and can cause a crash).
+
+Other notable changes:
+
+* dbus-python uses the standard Python logging framework throughout.
+ The first time a WARNING or ERROR is generated, it will configure the
+ logging framework to output to stderr, unless you have already
+ configured logging in your application.
+
+* The tutorial now advocates the use of add_signal_receiver if all you
+ want to do is listen for signals: this avoids undesired activation,
+ e.g. of Listen or Rhythmbox (!). Addresses fd.o #10743, fd.o #10568.
+
+D-Bus Python Bindings 0.80.2 (13 February 2007)
+===============================================
+- Fix numerous memory and reference leaks
+- Only use -Werror if the user specifically asks for it
+- Audit tp_dealloc callbacks to make sure they correctly preserve the
+ exception state
+- Relicense files solely owned by Collabora Ltd. more permissively (LGPL/AFL
+ rather than GPL/AFL) - this includes the tutorial and all the C code
+
+D-Bus Python Bindings 0.80.1 (24 January 2007)
+==============================================
+- The "oops" release
+- Install dbus/_version.py, so dbus.__version__ exists again
+
+D-Bus Python Bindings 0.80.0 (24 January 2007)
+==============================================
+- The "everything changes" release
+- Rewrite dbus_bindings (Pyrex) as _dbus_bindings (C) - API changes!
+- Define what's public API
+- Move low-level but still public API to dbus.lowlevel
+- Remove Variant class, add variant_level property on all D-Bus types
+- Make signal matching keep working as expected when name ownership changes
+- Use unambiguous D-Bus types when transferring from D-Bus to Python
+- Follow well-defined rules when transferring from Python to D-Bus
+- Add utf8_strings and byte_arrays options in various places, so a user
+ can tweak the calling conventions to be more efficient
+- Raise RuntimeError if user tries to use a connection with no main loop
+ to do something that won't work without one
+- Make asynchronous method calls actually asynchronous when made before
+ introspection results come back
+- Redo main loop machinery so we can add pure-Python main loops later without
+ API breakage
+- Allow construction of a dbus.service.Object if you don't have a BusName
+ (or even a Bus)
+- Port introspection XML parser from libxml2 (external package) to expat
+ (included with Python)
+- Port build system from distutils to autoconf/automake/libtool
+- Install a header file for third-party main loop integration
+- Make compatible with Python 2.5, including on 64-bit platforms
+- Add docstrings throughout
+- Add more tests and examples
+- Add interoperability tests (which interoperate with Java)
+- Add copyright notices!
+
+D-Bus Python Bindings 0.71 (24 July 2006)
+==============================================================
+- Binary modules are now installed in the correct directory
+- Distutils exports the dbus and dbus-glib cflags
+
+D-Bus Python Bindings 0.70 (17 July 2006)
+==============================================================
+- First release of bindings split
+- Move to a distutils build enviornment
+- It is possible to now specify sender_keyword="foo", path_keyword="bar" when
+ adding a signal listener
diff --git a/README b/README
new file mode 100644
index 0000000..ddbcca7
--- /dev/null
+++ b/README
@@ -0,0 +1,35 @@
+=======================================
+dbus-python_: Python bindings for D-Bus
+=======================================
+
+.. _dbus-python: http://www.freedesktop.org/wiki/Software/DBusBindings#python
+
+dbus-python is the original Python binding for ``dbus``, the reference
+implementation of the D-Bus protocol.
+
+Online documentation can be found at
+<http://dbus.freedesktop.org/doc/dbus-python/>.
+
+Problems and alternatives
+=========================
+
+dbus-python might not be the best D-Bus binding for you to use. dbus-python
+does not follow the principle of "In the face of ambiguity, refuse the
+temptation to guess", and can't be changed to not do so without seriously
+breaking compatibility.
+
+In addition, it uses libdbus (which has known problems with multi-threaded
+use) and attempts to be main-loop-agnostic (which means you have to select
+a suitable main loop for your application).
+
+Alternative ways to get your Python code onto D-Bus include:
+
+* GDBus, part of the GIO module of `GLib`_, via GObject-Introspection and
+ `PyGI`_ (uses the GLib main loop and object model)
+
+* QtDBus, part of `Qt`_, via `PyQt`_ (uses the Qt main loop and object model)
+
+.. _GLib: http://developer.gnome.org/glib/
+.. _PyGI: https://live.gnome.org/PyGObject
+.. _Qt: https://www.qt.io
+.. _PyQT: http://www.riverbankcomputing.co.uk/software/pyqt/intro
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..0cc7020
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+# Copyright 2006-2022 Collabora Ltd.
+# Copyright 2016 GNOME wiki contributors
+# SPDX-License-Identifier: MIT
+
+# Run this to generate all the initial makefiles, etc.
+test -n "$srcdir" || srcdir=$(dirname "$0")
+test -n "$srcdir" || srcdir=.
+
+olddir=$(pwd)
+
+cd $srcdir
+
+(test -f configure.ac) || {
+ echo "*** ERROR: Directory '$srcdir' does not look like the top-level project directory ***"
+ exit 1
+}
+
+# shellcheck disable=SC2016
+PKG_NAME=$(autoconf --trace 'AC_INIT:$1' configure.ac)
+
+if [ "$#" = 0 -a "x$NOCONFIGURE" = "x" ]; then
+ echo "*** WARNING: I am going to run 'configure' with no arguments." >&2
+ echo "*** If you wish to pass any to it, please specify them on the" >&2
+ echo "*** '$0' command line." >&2
+ echo "" >&2
+fi
+
+aclocal --install || exit 1
+autoreconf --verbose --force --install || exit 1
+
+cd "$olddir"
+if [ "$NOCONFIGURE" = "" ]; then
+ $srcdir/configure "$@" || exit 1
+
+ if [ "$1" = "--help" ]; then exit 0 else
+ echo "Now type 'make' to compile $PKG_NAME" || exit 1
+ fi
+else
+ echo "Skipping configure process."
+fi
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..02b7714
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,167 @@
+-*- mode: m4 -*-
+AC_PREREQ(2.59c)
+# Copyright 2006-2022 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+
+dnl The dbus-python version number
+m4_define(dbus_python_major_version, 1)
+m4_define(dbus_python_minor_version, 2)
+dnl Micro version is odd for non-releases
+m4_define(dbus_python_micro_version, 19)
+
+AC_INIT([dbus-python],
+ dbus_python_major_version.dbus_python_minor_version.dbus_python_micro_version,
+ [https://gitlab.freedesktop.org/dbus/dbus-python/-/issues/new])
+AC_CONFIG_MACRO_DIR([m4])
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES])
+AC_CONFIG_AUX_DIR([build-aux])
+
+m4_pattern_forbid([^AX_(COMPILER_FLAGS|IS_RELEASE|PYTHON_DEVEL|PYTHON_MODULE)\b],
+ [Unexpanded AX_ macro found. Please install GNU autoconf-archive])
+
+# By default, rebuild autotools files on demand; only use ./missing if the
+# user says --disable-maintainer-mode (in particular this is useful when
+# running under pip, which does not preserve timestamps)
+AM_MAINTAINER_MODE([enable])
+
+AX_IS_RELEASE([micro-version])
+AC_DEFINE(DBUS_PYTHON_MAJOR_VERSION, dbus_python_major_version, [dbus-python major version])
+AC_SUBST(DBUS_PYTHON_MAJOR_VERSION, dbus_python_major_version)
+AC_DEFINE(DBUS_PYTHON_MINOR_VERSION, dbus_python_minor_version, [dbus-python minor version])
+AC_SUBST(DBUS_PYTHON_MINOR_VERSION, dbus_python_minor_version)
+AC_DEFINE(DBUS_PYTHON_MICRO_VERSION, dbus_python_micro_version, [dbus-python micro version])
+AC_SUBST(DBUS_PYTHON_MICRO_VERSION, dbus_python_micro_version)
+
+AC_CONFIG_SRCDIR([dbus_bindings/module.c])
+AC_CONFIG_HEADERS(config.h)
+AC_USE_SYSTEM_EXTENSIONS
+
+AM_INIT_AUTOMAKE([1.13 -Wno-portability foreign subdir-objects tar-ustar])
+
+AC_CANONICAL_BUILD
+AC_CANONICAL_HOST
+
+# mingw32, mingw-w64 are native Windows; for our purposes, Cygwin isn't
+AC_MSG_CHECKING([for native Windows host])
+AS_CASE(["$host"],
+ [*-*-mingw*],
+ [windows=yes],
+ [*],
+ [windows=no])
+AC_MSG_RESULT([$windows])
+AM_CONDITIONAL([WINDOWS], [test "x$windows" = xyes])
+
+AC_DISABLE_STATIC
+
+dnl XXXX hack to kill off all the libtool tags ...
+dnl it isn't like we are using C++ or Fortran.
+dnl (copied from libglade/configure.in)
+m4_define([_LT_AC_TAGCONFIG],[])
+
+LT_INIT
+AM_PROG_CC_C_O
+AC_C_INLINE
+AC_PROG_MKDIR_P
+
+AC_PROG_AWK
+AC_REQUIRE_AUX_FILE([tap-driver.sh])
+
+AC_ARG_VAR([PYTHON_CPPFLAGS],
+ [compiler flags to find Python headers [default: auto-detect] [typical value: -I/opt/mypython/include]])
+AC_ARG_VAR([PYTHON_LIBS],
+ [libraries to link into Python extensions [default: auto-detect] [typical value: -L/opt/mypython/lib -lpython2.7]])
+AC_ARG_VAR([PYTHON_EXTRA_LIBS],
+ [libraries to link when embedding a Python interpreter [default: auto-detect]])
+AC_ARG_VAR([PYTHON_EXTRA_LDFLAGS],
+ [compiler flags to link when embedding a Python interpreter [default: auto-detect]])
+
+AC_ARG_VAR([PYTHON_INCLUDES], [deprecated form of PYTHON_CPPFLAGS])
+AS_IF([test -n "$PYTHON_INCLUDES"],
+ [PYTHON_CPPFLAGS="$PYTHON_CPPFLAGS $PYTHON_INCLUDES"])
+
+AS_IF([test -z "$PYTHON_VERSION" && test -z "$PYTHON"], [PYTHON_VERSION=3])
+AX_PYTHON_DEVEL([>= '2.7'])
+AM_PATH_PYTHON
+
+PLATFORM=`$PYTHON -c "import sysconfig; print(sysconfig.get_platform())"`
+AC_SUBST(PLATFORM)
+
+AC_ARG_ENABLE([installed-tests],
+ [AS_HELP_STRING([--enable-installed tests],
+ [install test programs and helpers for as-installed testing])],
+ [],
+ [enable_installed_tests=no])
+AM_CONDITIONAL([ENABLE_INSTALLED_TESTS], [test "_$enable_installed_tests" = _yes])
+
+dnl Building documentation
+
+AC_ARG_ENABLE([documentation],
+ [AS_HELP_STRING([--enable-documentation],
+ [Enable documentation building (requires sphinx and sphinx_rtd_theme)])],
+ [:],
+ [enable_documentation=auto])
+AX_PYTHON_MODULE([sphinx])
+AS_IF([test "x$HAVE_PYMOD_SPHINX" = xno],
+ [
+ AS_IF([test "$enable_documentation" = yes],
+ [AC_MSG_ERROR([cannot build documentation without sphinx Python module])],
+ [enable_documentation=no])
+ ])
+AX_PYTHON_MODULE([sphinx_rtd_theme])
+AS_IF([test "x$HAVE_PYMOD_SPHINX_RTD_THEME" = xno],
+ [
+ AS_IF([test "$enable_documentation" = yes],
+ [AC_MSG_ERROR([cannot build documentation without sphinx_rtd_theme Python module])],
+ [enable_documentation=no])
+ ])
+
+AC_ARG_VAR([DBUS_RUN_SESSION],
+ [The dbus-run-session tool from dbus 1.8 or later])
+AC_PATH_PROG([DBUS_RUN_SESSION], [dbus-run-session], [dbus-run-session])
+
+AM_CONDITIONAL([ENABLE_DOCUMENTATION], [test "$enable_documentation" != no])
+
+PKG_CHECK_MODULES(DBUS, [dbus-1 >= 1.8])
+PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.40])
+
+AX_COMPILER_FLAGS([WARN_CFLAGS],
+ [WARN_LDFLAGS], [],
+ dnl unfortunately the Python headers fail various checks included in
+ dnl AX_COMPILER_FLAGS, and are not const-correct for strings
+ [ \
+ -Wdeprecated-declarations \
+ -Wno-declaration-after-statement \
+ -Wno-duplicated-branches \
+ -Wno-inline \
+ -Wno-redundant-decls \
+ -Wno-switch-default \
+ -Wno-write-strings \
+ ])
+dnl AX_COMPILER_FLAGS doesn't order the compiler flags correctly to be able
+dnl to disable flags that it would normally enable
+WARN_CFLAGS="$(echo "${WARN_CFLAGS}" | ${SED} \
+ -e s/-Wdeclaration-after-statement// \
+ -e s/-Wduplicated-branches// \
+ -e s/-Winline// \
+ -e s/-Wredundant-decls// \
+ -e s/-Wswitch-default// \
+ -e s/-Wwrite-strings// \
+ )"
+
+AC_ARG_ENABLE([coding-style-checks],
+ [AS_HELP_STRING([--enable-coding-style-checks],
+ [check coding style using grep])],
+ [ENABLE_CODING_STYLE_CHECKS=$enableval],
+ [ENABLE_CODING_STYLE_CHECKS=$ax_is_release])
+
+# Make dbus-gmain submodule part of dbus-python's namespace
+AH_BOTTOM([
+#define DBUS_GMAIN_FUNCTION_NAME(name) _dbus_py_glib_ ## name
+])
+
+AC_CONFIG_FILES([
+ Makefile
+ dbus-python.pc
+ subprojects/dbus-gmain/Makefile
+])
+AC_OUTPUT
diff --git a/dbus-python.pc.in b/dbus-python.pc.in
new file mode 100644
index 0000000..9d74577
--- /dev/null
+++ b/dbus-python.pc.in
@@ -0,0 +1,14 @@
+# Copyright 2006-2022 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+# This file is currently Python-version-independent.
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+includedir=@includedir@
+datarootdir=@datarootdir@
+datadir=@datadir@
+
+Name: dbus-python
+Description: Python bindings for D-Bus
+Requires: dbus-1 >= 1.0
+Version: @VERSION@
+Cflags: -I${includedir}
diff --git a/dbus/__init__.py b/dbus/__init__.py
new file mode 100644
index 0000000..8cf3989
--- /dev/null
+++ b/dbus/__init__.py
@@ -0,0 +1,93 @@
+"""\
+Implements the public API for a D-Bus client. See the dbus.service module
+to export objects or claim well-known names.
+"""
+
+# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = [
+ # from _dbus
+ 'Bus', 'SystemBus', 'SessionBus', 'StarterBus',
+
+ # from proxies
+ 'Interface',
+
+ # from _dbus_bindings
+ 'get_default_main_loop', 'set_default_main_loop',
+
+ 'validate_interface_name', 'validate_member_name',
+ 'validate_bus_name', 'validate_object_path',
+ 'validate_error_name',
+
+ 'BUS_DAEMON_NAME', 'BUS_DAEMON_PATH', 'BUS_DAEMON_IFACE',
+ 'LOCAL_PATH', 'LOCAL_IFACE', 'PEER_IFACE',
+ 'INTROSPECTABLE_IFACE', 'PROPERTIES_IFACE',
+
+ 'ObjectPath', 'ByteArray', 'Signature', 'Byte', 'Boolean',
+ 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64',
+ 'Double', 'String', 'Array', 'Struct', 'Dictionary',
+
+ # from exceptions
+ 'DBusException',
+ 'MissingErrorHandlerException', 'MissingReplyHandlerException',
+ 'ValidationException', 'IntrospectionParserException',
+ 'UnknownMethodException', 'NameExistsException',
+
+ # submodules
+ 'service', 'mainloop', 'lowlevel'
+ ]
+
+from dbus._compat import is_py2
+
+__docformat__ = 'restructuredtext'
+
+# OLPC Sugar compatibility
+import dbus.exceptions as exceptions
+import dbus.types as types
+
+from _dbus_bindings import __version__
+version = tuple(map(int, __version__.split('.')))
+
+from _dbus_bindings import (
+ get_default_main_loop, set_default_main_loop, validate_bus_name,
+ validate_error_name, validate_interface_name, validate_member_name,
+ validate_object_path)
+from _dbus_bindings import (
+ BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, INTROSPECTABLE_IFACE,
+ LOCAL_IFACE, LOCAL_PATH, PEER_IFACE, PROPERTIES_IFACE)
+
+from dbus.exceptions import (
+ DBusException, IntrospectionParserException, MissingErrorHandlerException,
+ MissingReplyHandlerException, NameExistsException, UnknownMethodException,
+ ValidationException)
+from _dbus_bindings import (
+ Array, Boolean, Byte, ByteArray, Dictionary, Double, Int16, Int32, Int64,
+ ObjectPath, Signature, String, Struct, UInt16, UInt32, UInt64)
+
+from dbus._dbus import Bus, SystemBus, SessionBus, StarterBus
+from dbus.proxies import Interface
diff --git a/dbus/_compat.py b/dbus/_compat.py
new file mode 100644
index 0000000..3e5f148
--- /dev/null
+++ b/dbus/_compat.py
@@ -0,0 +1,15 @@
+# Python 2 / Python 3 compatibility helpers.
+# Copyright 2011 Barry Warsaw
+# Copyright 2021 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+
+import sys
+
+is_py3 = True
+is_py2 = False
+
+if sys.version_info.major < 3:
+ raise AssertionError(
+ 'Python 2 has reached end-of-life, and dbus-python no longer '
+ 'supports it.'
+ )
diff --git a/dbus/_dbus.py b/dbus/_dbus.py
new file mode 100644
index 0000000..2891194
--- /dev/null
+++ b/dbus/_dbus.py
@@ -0,0 +1,229 @@
+"""Implementation for dbus.Bus. Not to be imported directly."""
+
+# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from __future__ import generators
+
+__all__ = ('Bus', 'SystemBus', 'SessionBus', 'StarterBus')
+__docformat__ = 'reStructuredText'
+
+from dbus.exceptions import DBusException
+from _dbus_bindings import (
+ BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_SESSION,
+ BUS_STARTER, BUS_SYSTEM, DBUS_START_REPLY_ALREADY_RUNNING,
+ DBUS_START_REPLY_SUCCESS, validate_bus_name,
+ validate_interface_name, validate_member_name, validate_object_path)
+from dbus.bus import BusConnection
+from dbus.lowlevel import SignalMessage
+from dbus._compat import is_py2
+
+
+class Bus(BusConnection):
+ """A connection to one of three possible standard buses, the SESSION,
+ SYSTEM, or STARTER bus. This class manages shared connections to those
+ buses.
+
+ If you're trying to subclass `Bus`, you may be better off subclassing
+ `BusConnection`, which doesn't have all this magic.
+ """
+
+ _shared_instances = {}
+
+ def __new__(cls, bus_type=BusConnection.TYPE_SESSION, private=False,
+ mainloop=None):
+ """Constructor, returning an existing instance where appropriate.
+
+ The returned instance is actually always an instance of `SessionBus`,
+ `SystemBus` or `StarterBus`.
+
+ :Parameters:
+ `bus_type` : cls.TYPE_SESSION, cls.TYPE_SYSTEM or cls.TYPE_STARTER
+ Connect to the appropriate bus
+ `private` : bool
+ If true, never return an existing shared instance, but instead
+ return a private connection.
+
+ :Deprecated: since 0.82.3. Use dbus.bus.BusConnection for
+ private connections.
+
+ `mainloop` : dbus.mainloop.NativeMainLoop
+ The main loop to use. The default is to use the default
+ main loop if one has been set up, or raise an exception
+ if none has been.
+ :Changed: in dbus-python 0.80:
+ converted from a wrapper around a Connection to a Connection
+ subclass.
+ """
+ if (not private and bus_type in cls._shared_instances):
+ return cls._shared_instances[bus_type]
+
+ # this is a bit odd, but we create instances of the subtypes
+ # so we can return the shared instances if someone tries to
+ # construct one of them (otherwise we'd eg try and return an
+ # instance of Bus from __new__ in SessionBus). why are there
+ # three ways to construct this class? we just don't know.
+ if bus_type == BUS_SESSION:
+ subclass = SessionBus
+ elif bus_type == BUS_SYSTEM:
+ subclass = SystemBus
+ elif bus_type == BUS_STARTER:
+ subclass = StarterBus
+ else:
+ raise ValueError('invalid bus_type %s' % bus_type)
+
+ bus = BusConnection.__new__(subclass, bus_type, mainloop=mainloop)
+
+ bus._bus_type = bus_type
+
+ if not private:
+ cls._shared_instances[bus_type] = bus
+
+ return bus
+
+ def close(self):
+ t = self._bus_type
+ if self.__class__._shared_instances.get(t) is self:
+ del self.__class__._shared_instances[t]
+ super(Bus, self).close()
+
+ def get_connection(self):
+ """Return self, for backwards compatibility with earlier dbus-python
+ versions where Bus was not a subclass of Connection.
+
+ :Deprecated: since 0.80.0
+ """
+ return self
+ _connection = property(get_connection, None, None,
+ """self._connection == self, for backwards
+ compatibility with earlier dbus-python versions
+ where Bus was not a subclass of Connection.""")
+
+ def get_session(private=False):
+ """Static method that returns a connection to the session bus.
+
+ :Parameters:
+ `private` : bool
+ If true, do not return a shared connection.
+ """
+ return SessionBus(private=private)
+
+ get_session = staticmethod(get_session)
+
+ def get_system(private=False):
+ """Static method that returns a connection to the system bus.
+
+ :Parameters:
+ `private` : bool
+ If true, do not return a shared connection.
+ """
+ return SystemBus(private=private)
+
+ get_system = staticmethod(get_system)
+
+
+ def get_starter(private=False):
+ """Static method that returns a connection to the starter bus.
+
+ :Parameters:
+ `private` : bool
+ If true, do not return a shared connection.
+ """
+ return StarterBus(private=private)
+
+ get_starter = staticmethod(get_starter)
+
+ def __repr__(self):
+ if self._bus_type == BUS_SESSION:
+ name = 'session'
+ elif self._bus_type == BUS_SYSTEM:
+ name = 'system'
+ elif self._bus_type == BUS_STARTER:
+ name = 'starter'
+ else:
+ name = 'unknown bus type'
+
+ return '<%s.%s (%s) at %#x>' % (self.__class__.__module__,
+ self.__class__.__name__,
+ name, id(self))
+ __str__ = __repr__
+
+
+# FIXME: Drop the subclasses here? I can't think why we'd ever want
+# polymorphism
+class SystemBus(Bus):
+ """The system-wide message bus."""
+ def __new__(cls, private=False, mainloop=None):
+ """Return a connection to the system bus.
+
+ :Parameters:
+ `private` : bool
+ If true, never return an existing shared instance, but instead
+ return a private connection.
+ `mainloop` : dbus.mainloop.NativeMainLoop
+ The main loop to use. The default is to use the default
+ main loop if one has been set up, or raise an exception
+ if none has been.
+ """
+ return Bus.__new__(cls, Bus.TYPE_SYSTEM, mainloop=mainloop,
+ private=private)
+
+class SessionBus(Bus):
+ """The session (current login) message bus."""
+ def __new__(cls, private=False, mainloop=None):
+ """Return a connection to the session bus.
+
+ :Parameters:
+ `private` : bool
+ If true, never return an existing shared instance, but instead
+ return a private connection.
+ `mainloop` : dbus.mainloop.NativeMainLoop
+ The main loop to use. The default is to use the default
+ main loop if one has been set up, or raise an exception
+ if none has been.
+ """
+ return Bus.__new__(cls, Bus.TYPE_SESSION, private=private,
+ mainloop=mainloop)
+
+class StarterBus(Bus):
+ """The bus that activated this process (only valid if
+ this process was launched by DBus activation).
+ """
+ def __new__(cls, private=False, mainloop=None):
+ """Return a connection to the bus that activated this process.
+
+ :Parameters:
+ `private` : bool
+ If true, never return an existing shared instance, but instead
+ return a private connection.
+ `mainloop` : dbus.mainloop.NativeMainLoop
+ The main loop to use. The default is to use the default
+ main loop if one has been set up, or raise an exception
+ if none has been.
+ """
+ return Bus.__new__(cls, Bus.TYPE_STARTER, private=private,
+ mainloop=mainloop)
diff --git a/dbus/_expat_introspect_parser.py b/dbus/_expat_introspect_parser.py
new file mode 100644
index 0000000..2c6f341
--- /dev/null
+++ b/dbus/_expat_introspect_parser.py
@@ -0,0 +1,87 @@
+# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from xml.parsers.expat import ParserCreate
+from dbus.exceptions import IntrospectionParserException
+
+class _Parser(object):
+ __slots__ = ('map', 'in_iface', 'in_method', 'sig')
+ def __init__(self):
+ self.map = {}
+ self.in_iface = ''
+ self.in_method = ''
+ self.sig = ''
+
+ def parse(self, data):
+ parser = ParserCreate('UTF-8', ' ')
+ parser.buffer_text = True
+ parser.StartElementHandler = self.StartElementHandler
+ parser.EndElementHandler = self.EndElementHandler
+ parser.Parse(data)
+ return self.map
+
+ def StartElementHandler(self, name, attributes):
+ if not self.in_iface:
+ if (not self.in_method and name == 'interface'):
+ self.in_iface = attributes['name']
+ else:
+ if (not self.in_method and name == 'method'):
+ self.in_method = attributes['name']
+ elif (self.in_method and name == 'arg'):
+ if attributes.get('direction', 'in') == 'in':
+ self.sig += attributes['type']
+
+ def EndElementHandler(self, name):
+ if self.in_iface:
+ if (not self.in_method and name == 'interface'):
+ self.in_iface = ''
+ elif (self.in_method and name == 'method'):
+ self.map[self.in_iface + '.' + self.in_method] = self.sig
+ self.in_method = ''
+ self.sig = ''
+
+def process_introspection_data(data):
+ """Return a dict mapping ``interface.method`` strings to the
+ concatenation of all their 'in' parameters, and mapping
+ ``interface.signal`` strings to the concatenation of all their
+ parameters.
+
+ Example output::
+
+ {
+ 'com.example.SignalEmitter.OneString': 's',
+ 'com.example.MethodImplementor.OneInt32Argument': 'i',
+ }
+
+ :Parameters:
+ `data` : str
+ The introspection XML. Must be an 8-bit string of UTF-8.
+ """
+ try:
+ return _Parser().parse(data)
+ except Exception as e:
+ raise IntrospectionParserException('%s: %s' % (e.__class__, e))
diff --git a/dbus/bus.py b/dbus/bus.py
new file mode 100644
index 0000000..fd5a281
--- /dev/null
+++ b/dbus/bus.py
@@ -0,0 +1,434 @@
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('BusConnection',)
+__docformat__ = 'reStructuredText'
+
+import logging
+import weakref
+
+from _dbus_bindings import (
+ BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_SESSION,
+ BUS_STARTER, BUS_SYSTEM, DBUS_START_REPLY_ALREADY_RUNNING,
+ DBUS_START_REPLY_SUCCESS, NAME_FLAG_ALLOW_REPLACEMENT,
+ NAME_FLAG_DO_NOT_QUEUE, NAME_FLAG_REPLACE_EXISTING,
+ RELEASE_NAME_REPLY_NON_EXISTENT, RELEASE_NAME_REPLY_NOT_OWNER,
+ RELEASE_NAME_REPLY_RELEASED, REQUEST_NAME_REPLY_ALREADY_OWNER,
+ REQUEST_NAME_REPLY_EXISTS, REQUEST_NAME_REPLY_IN_QUEUE,
+ REQUEST_NAME_REPLY_PRIMARY_OWNER, validate_bus_name, validate_error_name,
+ validate_interface_name, validate_member_name, validate_object_path)
+from dbus.connection import Connection
+from dbus.exceptions import DBusException
+from dbus.lowlevel import HANDLER_RESULT_NOT_YET_HANDLED
+from dbus._compat import is_py2
+
+
+_NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
+ "interface='%s',member='NameOwnerChanged',"
+ "path='%s',arg0='%%s'"
+ % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
+ BUS_DAEMON_PATH))
+"""(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
+messages"""
+
+_NAME_HAS_NO_OWNER = 'org.freedesktop.DBus.Error.NameHasNoOwner'
+
+_logger = logging.getLogger('dbus.bus')
+
+
+class NameOwnerWatch(object):
+ __slots__ = ('_match', '_pending_call')
+
+ def __init__(self, bus_conn, bus_name, callback):
+ validate_bus_name(bus_name)
+
+ def signal_cb(owned, old_owner, new_owner):
+ callback(new_owner)
+
+ def error_cb(e):
+ if e.get_dbus_name() == _NAME_HAS_NO_OWNER:
+ callback('')
+ else:
+ logging.basicConfig()
+ _logger.debug('GetNameOwner(%s) failed:', bus_name,
+ exc_info=(e.__class__, e, None))
+
+ self._match = bus_conn.add_signal_receiver(signal_cb,
+ 'NameOwnerChanged',
+ BUS_DAEMON_IFACE,
+ BUS_DAEMON_NAME,
+ BUS_DAEMON_PATH,
+ arg0=bus_name)
+ self._pending_call = bus_conn.call_async(BUS_DAEMON_NAME,
+ BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE,
+ 'GetNameOwner',
+ 's', (bus_name,),
+ callback, error_cb)
+
+ def cancel(self):
+ if self._match is not None:
+ self._match.remove()
+ if self._pending_call is not None:
+ self._pending_call.cancel()
+ self._match = None
+ self._pending_call = None
+
+
+class BusConnection(Connection):
+ """A connection to a D-Bus daemon that implements the
+ ``org.freedesktop.DBus`` pseudo-service.
+
+ :Since: 0.81.0
+ """
+
+ TYPE_SESSION = BUS_SESSION
+ """Represents a session bus (same as the global dbus.BUS_SESSION)"""
+
+ TYPE_SYSTEM = BUS_SYSTEM
+ """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
+
+ TYPE_STARTER = BUS_STARTER
+ """Represents the bus that started this service by activation (same as
+ the global dbus.BUS_STARTER)"""
+
+ START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
+ START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
+
+ def __new__(cls, address_or_type=TYPE_SESSION, mainloop=None):
+ bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
+
+ # _bus_names is used by dbus.service.BusName!
+ bus._bus_names = weakref.WeakValueDictionary()
+
+ bus._signal_sender_matches = {}
+ """Map from SignalMatch to NameOwnerWatch."""
+
+ return bus
+
+ def add_signal_receiver(self, handler_function, signal_name=None,
+ dbus_interface=None, bus_name=None,
+ path=None, **keywords):
+ named_service = keywords.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both be '
+ 'specified')
+ bus_name = named_service
+ from warnings import warn
+ warn('Passing the named_service parameter to add_signal_receiver '
+ 'by name is deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+
+ match = super(BusConnection, self).add_signal_receiver(
+ handler_function, signal_name, dbus_interface, bus_name,
+ path, **keywords)
+
+ if (bus_name is not None and bus_name != BUS_DAEMON_NAME):
+ if bus_name[:1] == ':':
+ def callback(new_owner):
+ if new_owner == '':
+ match.remove()
+ else:
+ callback = match.set_sender_name_owner
+ watch = self.watch_name_owner(bus_name, callback)
+ self._signal_sender_matches[match] = watch
+
+ self.add_match_string(str(match))
+
+ return match
+
+ def _clean_up_signal_match(self, match):
+ # The signals lock is no longer held here (it was in <= 0.81.0)
+ self.remove_match_string_non_blocking(str(match))
+ watch = self._signal_sender_matches.pop(match, None)
+ if watch is not None:
+ watch.cancel()
+
+ def activate_name_owner(self, bus_name):
+ if (bus_name is not None and bus_name[:1] != ':'
+ and bus_name != BUS_DAEMON_NAME):
+ try:
+ return self.get_name_owner(bus_name)
+ except DBusException as e:
+ if e.get_dbus_name() != _NAME_HAS_NO_OWNER:
+ raise
+ # else it doesn't exist: try to start it
+ self.start_service_by_name(bus_name)
+ return self.get_name_owner(bus_name)
+ else:
+ # already unique
+ return bus_name
+
+ def get_object(self, bus_name, object_path, introspect=True,
+ follow_name_owner_changes=False, **kwargs):
+ """Return a local proxy for the given remote object.
+
+ Method calls on the proxy are translated into method calls on the
+ remote object.
+
+ :Parameters:
+ `bus_name` : str
+ A bus name (either the unique name or a well-known name)
+ of the application owning the object. The keyword argument
+ named_service is a deprecated alias for this.
+ `object_path` : str
+ The object path of the desired object
+ `introspect` : bool
+ If true (default), attempt to introspect the remote
+ object to find out supported methods and their signatures
+ `follow_name_owner_changes` : bool
+ If the object path is a well-known name and this parameter
+ is false (default), resolve the well-known name to the unique
+ name of its current owner and bind to that instead; if the
+ ownership of the well-known name changes in future,
+ keep communicating with the original owner.
+ This is necessary if the D-Bus API used is stateful.
+
+ If the object path is a well-known name and this parameter
+ is true, whenever the well-known name changes ownership in
+ future, bind to the new owner, if any.
+
+ If the given object path is a unique name, this parameter
+ has no effect.
+
+ :Returns: a `dbus.proxies.ProxyObject`
+ :Raises `DBusException`: if resolving the well-known name to a
+ unique name fails
+ """
+ if follow_name_owner_changes:
+ self._require_main_loop() # we don't get the signals otherwise
+
+ named_service = kwargs.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both '
+ 'be specified')
+ from warnings import warn
+ warn('Passing the named_service parameter to get_object by name '
+ 'is deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+ bus_name = named_service
+ if kwargs:
+ raise TypeError('get_object does not take these keyword '
+ 'arguments: %s' % ', '.join(kwargs.keys()))
+
+ return self.ProxyObjectClass(self, bus_name, object_path,
+ introspect=introspect,
+ follow_name_owner_changes=follow_name_owner_changes)
+
+ def get_unix_user(self, bus_name):
+ """Get the numeric uid of the process owning the given bus name.
+
+ :Parameters:
+ `bus_name` : str
+ A bus name, either unique or well-known
+ :Returns: a `dbus.UInt32`
+ :Since: 0.80.0
+ """
+ validate_bus_name(bus_name)
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'GetConnectionUnixUser',
+ 's', (bus_name,))
+
+ def start_service_by_name(self, bus_name, flags=0):
+ """Start a service which will implement the given bus name on this Bus.
+
+ :Parameters:
+ `bus_name` : str
+ The well-known bus name to be activated.
+ `flags` : dbus.UInt32
+ Flags to pass to StartServiceByName (currently none are
+ defined)
+
+ :Returns: A tuple of 2 elements. The first is always True, the
+ second is either START_REPLY_SUCCESS or
+ START_REPLY_ALREADY_RUNNING.
+
+ :Raises `DBusException`: if the service could not be started.
+ :Since: 0.80.0
+ """
+ validate_bus_name(bus_name)
+ return (True, self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE,
+ 'StartServiceByName',
+ 'su', (bus_name, flags)))
+
+ # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
+ # but this would not be backwards-compatible
+ def request_name(self, name, flags=0):
+ """Request a bus name.
+
+ :Parameters:
+ `name` : str
+ The well-known name to be requested
+ `flags` : dbus.UInt32
+ A bitwise-OR of 0 or more of the flags
+ `NAME_FLAG_ALLOW_REPLACEMENT`,
+ `NAME_FLAG_REPLACE_EXISTING`
+ and `NAME_FLAG_DO_NOT_QUEUE`
+ :Returns: `REQUEST_NAME_REPLY_PRIMARY_OWNER`,
+ `REQUEST_NAME_REPLY_IN_QUEUE`,
+ `REQUEST_NAME_REPLY_EXISTS` or
+ `REQUEST_NAME_REPLY_ALREADY_OWNER`
+ :Raises `DBusException`: if the bus daemon cannot be contacted or
+ returns an error.
+ """
+ validate_bus_name(name, allow_unique=False)
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'RequestName',
+ 'su', (name, flags))
+
+ def release_name(self, name):
+ """Release a bus name.
+
+ :Parameters:
+ `name` : str
+ The well-known name to be released
+ :Returns: `RELEASE_NAME_REPLY_RELEASED`,
+ `RELEASE_NAME_REPLY_NON_EXISTENT`
+ or `RELEASE_NAME_REPLY_NOT_OWNER`
+ :Raises `DBusException`: if the bus daemon cannot be contacted or
+ returns an error.
+ """
+ validate_bus_name(name, allow_unique=False)
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'ReleaseName',
+ 's', (name,))
+
+ def list_names(self):
+ """Return a list of all currently-owned names on the bus.
+
+ :Returns: a dbus.Array of dbus.UTF8String
+ :Since: 0.81.0
+ """
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'ListNames',
+ '', ())
+
+ def list_activatable_names(self):
+ """Return a list of all names that can be activated on the bus.
+
+ :Returns: a dbus.Array of dbus.UTF8String
+ :Since: 0.81.0
+ """
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'ListActivatableNames',
+ '', ())
+
+ def get_name_owner(self, bus_name):
+ """Return the unique connection name of the primary owner of the
+ given name.
+
+ :Raises `DBusException`: if the `bus_name` has no owner
+ :Since: 0.81.0
+ """
+ validate_bus_name(bus_name, allow_unique=False)
+ return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'GetNameOwner',
+ 's', (bus_name,))
+
+ def watch_name_owner(self, bus_name, callback):
+ """Watch the unique connection name of the primary owner of the
+ given name.
+
+ `callback` will be called with one argument, which is either the
+ unique connection name, or the empty string (meaning the name is
+ not owned).
+
+ :Since: 0.81.0
+ """
+ return NameOwnerWatch(self, bus_name, callback)
+
+ def name_has_owner(self, bus_name):
+ """Return True iff the given bus name has an owner on this bus.
+
+ :Parameters:
+ `bus_name` : str
+ The bus name to look up
+ :Returns: a `bool`
+ """
+ return bool(self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'NameHasOwner',
+ 's', (bus_name,)))
+
+ def add_match_string(self, rule):
+ """Arrange for this application to receive messages on the bus that
+ match the given rule. This version will block.
+
+ :Parameters:
+ `rule` : str
+ The match rule
+ :Raises `DBusException`: on error.
+ :Since: 0.80.0
+ """
+ self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,))
+
+ # FIXME: add an async success/error handler capability?
+ # (and the same for remove_...)
+ def add_match_string_non_blocking(self, rule):
+ """Arrange for this application to receive messages on the bus that
+ match the given rule. This version will not block, but any errors
+ will be ignored.
+
+
+ :Parameters:
+ `rule` : str
+ The match rule
+ :Raises `DBusException`: on error.
+ :Since: 0.80.0
+ """
+ self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,),
+ None, None)
+
+ def remove_match_string(self, rule):
+ """Arrange for this application to receive messages on the bus that
+ match the given rule. This version will block.
+
+ :Parameters:
+ `rule` : str
+ The match rule
+ :Raises `DBusException`: on error.
+ :Since: 0.80.0
+ """
+ self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,))
+
+ def remove_match_string_non_blocking(self, rule):
+ """Arrange for this application to receive messages on the bus that
+ match the given rule. This version will not block, but any errors
+ will be ignored.
+
+
+ :Parameters:
+ `rule` : str
+ The match rule
+ :Raises `DBusException`: on error.
+ :Since: 0.80.0
+ """
+ self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
+ BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,),
+ None, None)
diff --git a/dbus/connection.py b/dbus/connection.py
new file mode 100644
index 0000000..fd20800
--- /dev/null
+++ b/dbus/connection.py
@@ -0,0 +1,651 @@
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('Connection', 'SignalMatch')
+__docformat__ = 'reStructuredText'
+
+import logging
+import threading
+import weakref
+
+from _dbus_bindings import (
+ Connection as _Connection, LOCAL_IFACE, LOCAL_PATH, validate_bus_name,
+ validate_interface_name, validate_member_name, validate_object_path)
+from dbus.exceptions import DBusException
+from dbus.lowlevel import (
+ ErrorMessage, HANDLER_RESULT_NOT_YET_HANDLED, MethodCallMessage,
+ MethodReturnMessage, SignalMessage)
+from dbus.proxies import ProxyObject
+from dbus._compat import is_py2, is_py3
+
+from _dbus_bindings import String
+
+
+_logger = logging.getLogger('dbus.connection')
+
+
+def _noop(*args, **kwargs):
+ pass
+
+
+class SignalMatch(object):
+ _slots = ['_sender_name_owner', '_member', '_interface', '_sender',
+ '_path', '_handler', '_args_match', '_rule',
+ '_byte_arrays', '_conn_weakref',
+ '_destination_keyword', '_interface_keyword',
+ '_message_keyword', '_member_keyword',
+ '_sender_keyword', '_path_keyword', '_int_args_match']
+
+ __slots__ = tuple(_slots)
+
+ def __init__(self, conn, sender, object_path, dbus_interface,
+ member, handler, byte_arrays=False,
+ sender_keyword=None, path_keyword=None,
+ interface_keyword=None, member_keyword=None,
+ message_keyword=None, destination_keyword=None,
+ **kwargs):
+ if member is not None:
+ validate_member_name(member)
+ if dbus_interface is not None:
+ validate_interface_name(dbus_interface)
+ if sender is not None:
+ validate_bus_name(sender)
+ if object_path is not None:
+ validate_object_path(object_path)
+
+ self._rule = None
+ self._conn_weakref = weakref.ref(conn)
+ self._sender = sender
+ self._interface = dbus_interface
+ self._member = member
+ self._path = object_path
+ self._handler = handler
+
+ # if the connection is actually a bus, it's responsible for changing
+ # this later
+ self._sender_name_owner = sender
+
+ if 'utf8_strings' in kwargs:
+ raise TypeError("unexpected keyword argument 'utf8_strings'")
+
+ self._byte_arrays = byte_arrays
+ self._sender_keyword = sender_keyword
+ self._path_keyword = path_keyword
+ self._member_keyword = member_keyword
+ self._interface_keyword = interface_keyword
+ self._message_keyword = message_keyword
+ self._destination_keyword = destination_keyword
+
+ self._args_match = kwargs
+ if not kwargs:
+ self._int_args_match = None
+ else:
+ self._int_args_match = {}
+ for kwarg in kwargs:
+ if not kwarg.startswith('arg'):
+ raise TypeError('SignalMatch: unknown keyword argument %s'
+ % kwarg)
+ try:
+ index = int(kwarg[3:])
+ except ValueError:
+ raise TypeError('SignalMatch: unknown keyword argument %s'
+ % kwarg)
+ if index < 0 or index > 63:
+ raise TypeError('SignalMatch: arg match index must be in '
+ 'range(64), not %d' % index)
+ self._int_args_match[index] = kwargs[kwarg]
+
+ def __hash__(self):
+ """SignalMatch objects are compared by identity."""
+ return hash(id(self))
+
+ def __eq__(self, other):
+ """SignalMatch objects are compared by identity."""
+ return self is other
+
+ def __ne__(self, other):
+ """SignalMatch objects are compared by identity."""
+ return self is not other
+
+ sender = property(lambda self: self._sender)
+
+ def __str__(self):
+ if self._rule is None:
+ rule = ["type='signal'"]
+ if self._sender is not None:
+ rule.append("sender='%s'" % self._sender)
+ if self._path is not None:
+ rule.append("path='%s'" % self._path)
+ if self._interface is not None:
+ rule.append("interface='%s'" % self._interface)
+ if self._member is not None:
+ rule.append("member='%s'" % self._member)
+ if self._int_args_match is not None:
+ for index, value in self._int_args_match.items():
+ rule.append("arg%d='%s'" % (index, value))
+
+ self._rule = ','.join(rule)
+
+ return self._rule
+
+ def __repr__(self):
+ return ('<%s at %x "%s" on conn %r>'
+ % (self.__class__, id(self), self._rule, self._conn_weakref()))
+
+ def set_sender_name_owner(self, new_name):
+ self._sender_name_owner = new_name
+
+ def matches_removal_spec(self, sender, object_path,
+ dbus_interface, member, handler, **kwargs):
+ if handler not in (None, self._handler):
+ return False
+ if sender != self._sender:
+ return False
+ if object_path != self._path:
+ return False
+ if dbus_interface != self._interface:
+ return False
+ if member != self._member:
+ return False
+ if kwargs != self._args_match:
+ return False
+ return True
+
+ def maybe_handle_message(self, message):
+ args = None
+
+ # these haven't been checked yet by the match tree
+ if self._sender_name_owner not in (None, message.get_sender()):
+ return False
+ if self._int_args_match is not None:
+ # extracting args with byte_arrays is less work
+ kwargs = dict(byte_arrays=True)
+ args = message.get_args_list(**kwargs)
+ for index, value in self._int_args_match.items():
+ if (index >= len(args)
+ or not isinstance(args[index], String)
+ or args[index] != value):
+ return False
+
+ # these have likely already been checked by the match tree
+ if self._member not in (None, message.get_member()):
+ return False
+ if self._interface not in (None, message.get_interface()):
+ return False
+ if self._path not in (None, message.get_path()):
+ return False
+
+ try:
+ # minor optimization: if we already extracted the args with the
+ # right calling convention to do the args match, don't bother
+ # doing so again
+ if args is None or not self._byte_arrays:
+ args = message.get_args_list(byte_arrays=self._byte_arrays)
+ kwargs = {}
+ if self._sender_keyword is not None:
+ kwargs[self._sender_keyword] = message.get_sender()
+ if self._destination_keyword is not None:
+ kwargs[self._destination_keyword] = message.get_destination()
+ if self._path_keyword is not None:
+ kwargs[self._path_keyword] = message.get_path()
+ if self._member_keyword is not None:
+ kwargs[self._member_keyword] = message.get_member()
+ if self._interface_keyword is not None:
+ kwargs[self._interface_keyword] = message.get_interface()
+ if self._message_keyword is not None:
+ kwargs[self._message_keyword] = message
+ self._handler(*args, **kwargs)
+ except:
+ # basicConfig is a no-op if logging is already configured
+ logging.basicConfig()
+ _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
+
+ return True
+
+ def remove(self):
+ conn = self._conn_weakref()
+ # do nothing if the connection has already vanished
+ if conn is not None:
+ conn.remove_signal_receiver(self, self._member,
+ self._interface, self._sender,
+ self._path,
+ **self._args_match)
+
+
+class Connection(_Connection):
+ """A connection to another application. In this base class there is
+ assumed to be no bus daemon.
+
+ :Since: 0.81.0
+ """
+
+ ProxyObjectClass = ProxyObject
+
+ def __init__(self, *args, **kwargs):
+ super(Connection, self).__init__(*args, **kwargs)
+
+ # this if-block is needed because shared bus connections can be
+ # __init__'ed more than once
+ if not hasattr(self, '_dbus_Connection_initialized'):
+ self._dbus_Connection_initialized = 1
+
+ self.__call_on_disconnection = []
+
+ self._signal_recipients_by_object_path = {}
+ """Map from object path to dict mapping dbus_interface to dict
+ mapping member to list of SignalMatch objects."""
+
+ self._signals_lock = threading.Lock()
+ """Lock used to protect signal data structures"""
+
+ self.add_message_filter(self.__class__._signal_func)
+
+ def activate_name_owner(self, bus_name):
+ """Return the unique name for the given bus name, activating it
+ if necessary and possible.
+
+ If the name is already unique or this connection is not to a
+ bus daemon, just return it.
+
+ :Returns: a bus name. If the given `bus_name` exists, the returned
+ name identifies its current owner; otherwise the returned name
+ does not exist.
+ :Raises DBusException: if the implementation has failed
+ to activate the given bus name.
+ :Since: 0.81.0
+ """
+ return bus_name
+
+ def get_object(self, bus_name=None, object_path=None, introspect=True,
+ **kwargs):
+ """Return a local proxy for the given remote object.
+
+ Method calls on the proxy are translated into method calls on the
+ remote object.
+
+ :Parameters:
+ `bus_name` : str
+ A bus name (either the unique name or a well-known name)
+ of the application owning the object. The keyword argument
+ named_service is a deprecated alias for this.
+ `object_path` : str
+ The object path of the desired object
+ `introspect` : bool
+ If true (default), attempt to introspect the remote
+ object to find out supported methods and their signatures
+
+ :Returns: a `dbus.proxies.ProxyObject`
+ """
+ named_service = kwargs.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both '
+ 'be specified')
+ from warnings import warn
+ warn('Passing the named_service parameter to get_object by name '
+ 'is deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+ bus_name = named_service
+ if kwargs:
+ raise TypeError('get_object does not take these keyword '
+ 'arguments: %s' % ', '.join(kwargs.keys()))
+
+ return self.ProxyObjectClass(self, bus_name, object_path,
+ introspect=introspect)
+
+ def add_signal_receiver(self, handler_function,
+ signal_name=None,
+ dbus_interface=None,
+ bus_name=None,
+ path=None,
+ **keywords):
+ """Arrange for the given function to be called when a signal matching
+ the parameters is received.
+
+ :Parameters:
+ `handler_function` : callable
+ The function to be called. Its positional arguments will
+ be the arguments of the signal. By default it will receive
+ no keyword arguments, but see the description of
+ the optional keyword arguments below.
+ `signal_name` : str
+ The signal name; None (the default) matches all names
+ `dbus_interface` : str
+ The D-Bus interface name with which to qualify the signal;
+ None (the default) matches all interface names
+ `bus_name` : str
+ A bus name for the sender, which will be resolved to a
+ unique name if it is not already; None (the default) matches
+ any sender.
+ `path` : str
+ The object path of the object which must have emitted the
+ signal; None (the default) matches any object path
+ :Keywords:
+ `utf8_strings` : bool
+ If True, the handler function will receive any string
+ arguments as dbus.UTF8String objects (a subclass of str
+ guaranteed to be UTF-8). If False (default) it will receive
+ any string arguments as dbus.String objects (a subclass of
+ unicode).
+ `byte_arrays` : bool
+ If True, the handler function will receive any byte-array
+ arguments as dbus.ByteArray objects (a subclass of str).
+ If False (default) it will receive any byte-array
+ arguments as a dbus.Array of dbus.Byte (subclasses of:
+ a list of ints).
+ `sender_keyword` : str
+ If not None (the default), the handler function will receive
+ the unique name of the sending endpoint as a keyword
+ argument with this name.
+ `destination_keyword` : str
+ If not None (the default), the handler function will receive
+ the bus name of the destination (or None if the signal is a
+ broadcast, as is usual) as a keyword argument with this name.
+ `interface_keyword` : str
+ If not None (the default), the handler function will receive
+ the signal interface as a keyword argument with this name.
+ `member_keyword` : str
+ If not None (the default), the handler function will receive
+ the signal name as a keyword argument with this name.
+ `path_keyword` : str
+ If not None (the default), the handler function will receive
+ the object-path of the sending object as a keyword argument
+ with this name.
+ `message_keyword` : str
+ If not None (the default), the handler function will receive
+ the `dbus.lowlevel.SignalMessage` as a keyword argument with
+ this name.
+ `arg...` : unicode or UTF-8 str
+ If there are additional keyword parameters of the form
+ ``arg``\\ *n*, match only signals where the *n*\\ th argument
+ is the value given for that keyword parameter. As of this
+ time only string arguments can be matched (in particular,
+ object paths and signatures can't).
+ `named_service` : str
+ A deprecated alias for `bus_name`.
+ """
+ self._require_main_loop()
+
+ named_service = keywords.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both be '
+ 'specified')
+ bus_name = named_service
+ from warnings import warn
+ warn('Passing the named_service parameter to add_signal_receiver '
+ 'by name is deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+
+ match = SignalMatch(self, bus_name, path, dbus_interface,
+ signal_name, handler_function, **keywords)
+
+ self._signals_lock.acquire()
+ try:
+ by_interface = self._signal_recipients_by_object_path.setdefault(
+ path, {})
+ by_member = by_interface.setdefault(dbus_interface, {})
+ matches = by_member.setdefault(signal_name, [])
+
+ matches.append(match)
+ finally:
+ self._signals_lock.release()
+
+ return match
+
+ def _iter_easy_matches(self, path, dbus_interface, member):
+ if path is not None:
+ path_keys = (None, path)
+ else:
+ path_keys = (None,)
+ if dbus_interface is not None:
+ interface_keys = (None, dbus_interface)
+ else:
+ interface_keys = (None,)
+ if member is not None:
+ member_keys = (None, member)
+ else:
+ member_keys = (None,)
+
+ for path in path_keys:
+ by_interface = self._signal_recipients_by_object_path.get(path)
+ if by_interface is None:
+ continue
+ for dbus_interface in interface_keys:
+ by_member = by_interface.get(dbus_interface, None)
+ if by_member is None:
+ continue
+ for member in member_keys:
+ matches = by_member.get(member, None)
+ if matches is None:
+ continue
+ for m in matches:
+ yield m
+
+ def remove_signal_receiver(self, handler_or_match,
+ signal_name=None,
+ dbus_interface=None,
+ bus_name=None,
+ path=None,
+ **keywords):
+ named_service = keywords.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both be '
+ 'specified')
+ bus_name = named_service
+ from warnings import warn
+ warn('Passing the named_service parameter to '
+ 'remove_signal_receiver by name is deprecated: please use '
+ 'positional parameters',
+ DeprecationWarning, stacklevel=2)
+
+ new = []
+ deletions = []
+ self._signals_lock.acquire()
+ try:
+ by_interface = self._signal_recipients_by_object_path.get(path,
+ None)
+ if by_interface is None:
+ return
+ by_member = by_interface.get(dbus_interface, None)
+ if by_member is None:
+ return
+ matches = by_member.get(signal_name, None)
+ if matches is None:
+ return
+
+ for match in matches:
+ if (handler_or_match is match
+ or match.matches_removal_spec(bus_name,
+ path,
+ dbus_interface,
+ signal_name,
+ handler_or_match,
+ **keywords)):
+ deletions.append(match)
+ else:
+ new.append(match)
+
+ if new:
+ by_member[signal_name] = new
+ else:
+ del by_member[signal_name]
+ if not by_member:
+ del by_interface[dbus_interface]
+ if not by_interface:
+ del self._signal_recipients_by_object_path[path]
+ finally:
+ self._signals_lock.release()
+
+ for match in deletions:
+ self._clean_up_signal_match(match)
+
+ def _clean_up_signal_match(self, match):
+ # Now called without the signals lock held (it was held in <= 0.81.0)
+ pass
+
+ def _signal_func(self, message):
+ """D-Bus filter function. Handle signals by dispatching to Python
+ callbacks kept in the match-rule tree.
+ """
+
+ if not isinstance(message, SignalMessage):
+ return HANDLER_RESULT_NOT_YET_HANDLED
+
+ dbus_interface = message.get_interface()
+ path = message.get_path()
+ signal_name = message.get_member()
+
+ for match in self._iter_easy_matches(path, dbus_interface,
+ signal_name):
+ match.maybe_handle_message(message)
+
+ if (dbus_interface == LOCAL_IFACE and
+ path == LOCAL_PATH and
+ signal_name == 'Disconnected'):
+ for cb in self.__call_on_disconnection:
+ try:
+ cb(self)
+ except Exception:
+ # basicConfig is a no-op if logging is already configured
+ logging.basicConfig()
+ _logger.error('Exception in handler for Disconnected '
+ 'signal:', exc_info=1)
+
+ return HANDLER_RESULT_NOT_YET_HANDLED
+
+ def call_async(self, bus_name, object_path, dbus_interface, method,
+ signature, args, reply_handler, error_handler,
+ timeout=-1.0, byte_arrays=False,
+ require_main_loop=True, **kwargs):
+ """Call the given method, asynchronously.
+
+ If the reply_handler is None, successful replies will be ignored.
+ If the error_handler is None, failures will be ignored. If both
+ are None, the implementation may request that no reply is sent.
+
+ :Returns: The dbus.lowlevel.PendingCall.
+ :Since: 0.81.0
+ """
+ if object_path == LOCAL_PATH:
+ raise DBusException('Methods may not be called on the reserved '
+ 'path %s' % LOCAL_PATH)
+ if dbus_interface == LOCAL_IFACE:
+ raise DBusException('Methods may not be called on the reserved '
+ 'interface %s' % LOCAL_IFACE)
+ # no need to validate other args - MethodCallMessage ctor will do
+
+ get_args_opts = dict(byte_arrays=byte_arrays)
+ if 'utf8_strings' in kwargs:
+ raise TypeError("unexpected keyword argument 'utf8_strings'")
+
+ message = MethodCallMessage(destination=bus_name,
+ path=object_path,
+ interface=dbus_interface,
+ method=method)
+ # Add the arguments to the function
+ try:
+ message.append(signature=signature, *args)
+ except Exception as e:
+ logging.basicConfig()
+ _logger.error('Unable to set arguments %r according to '
+ 'signature %r: %s: %s',
+ args, signature, e.__class__, e)
+ raise
+
+ if reply_handler is None and error_handler is None:
+ # we don't care what happens, so just send it
+ self.send_message(message)
+ return
+
+ if reply_handler is None:
+ reply_handler = _noop
+ if error_handler is None:
+ error_handler = _noop
+
+ def msg_reply_handler(message):
+ if isinstance(message, MethodReturnMessage):
+ reply_handler(*message.get_args_list(**get_args_opts))
+ elif isinstance(message, ErrorMessage):
+ error_handler(DBusException(name=message.get_error_name(),
+ *message.get_args_list()))
+ else:
+ error_handler(TypeError('Unexpected type for reply '
+ 'message: %r' % message))
+ return self.send_message_with_reply(message, msg_reply_handler,
+ timeout,
+ require_main_loop=require_main_loop)
+
+ def call_blocking(self, bus_name, object_path, dbus_interface, method,
+ signature, args, timeout=-1.0,
+ byte_arrays=False, **kwargs):
+ """Call the given method, synchronously.
+ :Since: 0.81.0
+ """
+ if object_path == LOCAL_PATH:
+ raise DBusException('Methods may not be called on the reserved '
+ 'path %s' % LOCAL_PATH)
+ if dbus_interface == LOCAL_IFACE:
+ raise DBusException('Methods may not be called on the reserved '
+ 'interface %s' % LOCAL_IFACE)
+ # no need to validate other args - MethodCallMessage ctor will do
+
+ get_args_opts = dict(byte_arrays=byte_arrays)
+ if 'utf8_strings' in kwargs:
+ raise TypeError("unexpected keyword argument 'utf8_strings'")
+
+ message = MethodCallMessage(destination=bus_name,
+ path=object_path,
+ interface=dbus_interface,
+ method=method)
+ # Add the arguments to the function
+ try:
+ message.append(signature=signature, *args)
+ except Exception as e:
+ logging.basicConfig()
+ _logger.error('Unable to set arguments %r according to '
+ 'signature %r: %s: %s',
+ args, signature, e.__class__, e)
+ raise
+
+ # make a blocking call
+ reply_message = self.send_message_with_reply_and_block(
+ message, timeout)
+ args_list = reply_message.get_args_list(**get_args_opts)
+ if len(args_list) == 0:
+ return None
+ elif len(args_list) == 1:
+ return args_list[0]
+ else:
+ return tuple(args_list)
+
+ def call_on_disconnection(self, callable):
+ """Arrange for `callable` to be called with one argument (this
+ Connection object) when the Connection becomes
+ disconnected.
+
+ :Since: 0.83.0
+ """
+ self.__call_on_disconnection.append(callable)
diff --git a/dbus/decorators.py b/dbus/decorators.py
new file mode 100644
index 0000000..3dc04a6
--- /dev/null
+++ b/dbus/decorators.py
@@ -0,0 +1,362 @@
+"""Service-side D-Bus decorators."""
+
+# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('method', 'signal')
+__docformat__ = 'restructuredtext'
+
+import inspect
+
+from dbus import validate_interface_name, Signature, validate_member_name
+from dbus.lowlevel import SignalMessage
+from dbus.exceptions import DBusException
+from dbus._compat import is_py2
+
+
+def method(dbus_interface, in_signature=None, out_signature=None,
+ async_callbacks=None,
+ sender_keyword=None, path_keyword=None, destination_keyword=None,
+ message_keyword=None, connection_keyword=None,
+ byte_arrays=False,
+ rel_path_keyword=None, **kwargs):
+ """Factory for decorators used to mark methods of a `dbus.service.Object`
+ to be exported on the D-Bus.
+
+ The decorated method will be exported over D-Bus as the method of the
+ same name on the given D-Bus interface.
+
+ :Parameters:
+ `dbus_interface` : str
+ Name of a D-Bus interface
+ `in_signature` : str or None
+ If not None, the signature of the method parameters in the usual
+ D-Bus notation
+ `out_signature` : str or None
+ If not None, the signature of the return value in the usual
+ D-Bus notation
+ `async_callbacks` : tuple containing (str,str), or None
+ If None (default) the decorated method is expected to return
+ values matching the `out_signature` as usual, or raise
+ an exception on error. If not None, the following applies:
+
+ `async_callbacks` contains the names of two keyword arguments to
+ the decorated function, which will be used to provide a success
+ callback and an error callback (in that order).
+
+ When the decorated method is called via the D-Bus, its normal
+ return value will be ignored; instead, a pair of callbacks are
+ passed as keyword arguments, and the decorated method is
+ expected to arrange for one of them to be called.
+
+ On success the success callback must be called, passing the
+ results of this method as positional parameters in the format
+ given by the `out_signature`.
+
+ On error the decorated method may either raise an exception
+ before it returns, or arrange for the error callback to be
+ called with an Exception instance as parameter.
+
+ `sender_keyword` : str or None
+ If not None, contains the name of a keyword argument to the
+ decorated function, conventionally ``'sender'``. When the
+ method is called, the sender's unique name will be passed as
+ this keyword argument.
+
+ `path_keyword` : str or None
+ If not None (the default), the decorated method will receive
+ the destination object path as a keyword argument with this
+ name. Normally you already know the object path, but in the
+ case of "fallback paths" you'll usually want to use the object
+ path in the method's implementation.
+
+ For fallback objects, `rel_path_keyword` (new in 0.82.2) is
+ likely to be more useful.
+
+ :Since: 0.80.0?
+
+ `rel_path_keyword` : str or None
+ If not None (the default), the decorated method will receive
+ the destination object path, relative to the path at which the
+ object was exported, as a keyword argument with this
+ name. For non-fallback objects the relative path will always be
+ '/'.
+
+ :Since: 0.82.2
+
+ `destination_keyword` : str or None
+ If not None (the default), the decorated method will receive
+ the destination bus name as a keyword argument with this name.
+ Included for completeness - you shouldn't need this.
+
+ :Since: 0.80.0?
+
+ `message_keyword` : str or None
+ If not None (the default), the decorated method will receive
+ the `dbus.lowlevel.MethodCallMessage` as a keyword argument
+ with this name.
+
+ :Since: 0.80.0?
+
+ `connection_keyword` : str or None
+ If not None (the default), the decorated method will receive
+ the `dbus.connection.Connection` as a keyword argument
+ with this name. This is generally only useful for objects
+ that are available on more than one connection.
+
+ :Since: 0.82.0
+
+ `utf8_strings` : bool
+ If False (default), D-Bus strings are passed to the decorated
+ method as objects of class dbus.String, a unicode subclass.
+
+ If True, D-Bus strings are passed to the decorated method
+ as objects of class dbus.UTF8String, a str subclass guaranteed
+ to be encoded in UTF-8.
+
+ This option does not affect object-paths and signatures, which
+ are always 8-bit strings (str subclass) encoded in ASCII.
+
+ :Since: 0.80.0
+
+ `byte_arrays` : bool
+ If False (default), a byte array will be passed to the decorated
+ method as an `Array` (a list subclass) of `Byte` objects.
+
+ If True, a byte array will be passed to the decorated method as
+ a `ByteArray`, a str subclass. This is usually what you want,
+ but is switched off by default to keep dbus-python's API
+ consistent.
+
+ :Since: 0.80.0
+ """
+ validate_interface_name(dbus_interface)
+
+ def decorator(func):
+ if hasattr(inspect, 'Signature'):
+ args = []
+
+ for arg in inspect.signature(func).parameters.values():
+ if arg.kind in (inspect.Parameter.POSITIONAL_ONLY,
+ inspect.Parameter.POSITIONAL_OR_KEYWORD):
+ args.append(arg.name)
+ else:
+ args = inspect.getargspec(func)[0]
+
+ args.pop(0)
+
+ if async_callbacks:
+ if type(async_callbacks) != tuple:
+ raise TypeError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
+ if len(async_callbacks) != 2:
+ raise ValueError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
+ args.remove(async_callbacks[0])
+ args.remove(async_callbacks[1])
+
+ if sender_keyword:
+ args.remove(sender_keyword)
+ if rel_path_keyword:
+ args.remove(rel_path_keyword)
+ if path_keyword:
+ args.remove(path_keyword)
+ if destination_keyword:
+ args.remove(destination_keyword)
+ if message_keyword:
+ args.remove(message_keyword)
+ if connection_keyword:
+ args.remove(connection_keyword)
+
+ if in_signature:
+ in_sig = tuple(Signature(in_signature))
+
+ if len(in_sig) > len(args):
+ raise ValueError('input signature is longer than the number of arguments taken')
+ elif len(in_sig) < len(args):
+ raise ValueError('input signature is shorter than the number of arguments taken')
+
+ func._dbus_is_method = True
+ func._dbus_async_callbacks = async_callbacks
+ func._dbus_interface = dbus_interface
+ func._dbus_in_signature = in_signature
+ func._dbus_out_signature = out_signature
+ func._dbus_sender_keyword = sender_keyword
+ func._dbus_path_keyword = path_keyword
+ func._dbus_rel_path_keyword = rel_path_keyword
+ func._dbus_destination_keyword = destination_keyword
+ func._dbus_message_keyword = message_keyword
+ func._dbus_connection_keyword = connection_keyword
+ func._dbus_args = args
+ func._dbus_get_args_options = dict(byte_arrays=byte_arrays)
+ if 'utf8_strings' in kwargs:
+ raise TypeError("unexpected keyword argument 'utf8_strings'")
+ return func
+
+ return decorator
+
+
+def signal(dbus_interface, signature=None, path_keyword=None,
+ rel_path_keyword=None):
+ """Factory for decorators used to mark methods of a `dbus.service.Object`
+ to emit signals on the D-Bus.
+
+ Whenever the decorated method is called in Python, after the method
+ body is executed, a signal with the same name as the decorated method,
+ with the given D-Bus interface, will be emitted from this object.
+
+ :Parameters:
+ `dbus_interface` : str
+ The D-Bus interface whose signal is emitted
+ `signature` : str
+ The signature of the signal in the usual D-Bus notation
+
+ `path_keyword` : str or None
+ A keyword argument to the decorated method. If not None,
+ that argument will not be emitted as an argument of
+ the signal, and when the signal is emitted, it will appear
+ to come from the object path given by the keyword argument.
+
+ Note that when calling the decorated method, you must always
+ pass in the object path as a keyword argument, not as a
+ positional argument.
+
+ This keyword argument cannot be used on objects where
+ the class attribute ``SUPPORTS_MULTIPLE_OBJECT_PATHS`` is true.
+
+ :Deprecated: since 0.82.0. Use `rel_path_keyword` instead.
+
+ `rel_path_keyword` : str or None
+ A keyword argument to the decorated method. If not None,
+ that argument will not be emitted as an argument of
+ the signal.
+
+ When the signal is emitted, if the named keyword argument is given,
+ the signal will appear to come from the object path obtained by
+ appending the keyword argument to the object's object path.
+ This is useful to implement "fallback objects" (objects which
+ own an entire subtree of the object-path tree).
+
+ If the object is available at more than one object-path on the
+ same or different connections, the signal will be emitted at
+ an appropriate object-path on each connection - for instance,
+ if the object is exported at /abc on connection 1 and at
+ /def and /x/y/z on connection 2, and the keyword argument is
+ /foo, then signals will be emitted from /abc/foo and /def/foo
+ on connection 1, and /x/y/z/foo on connection 2.
+
+ :Since: 0.82.0
+ """
+ validate_interface_name(dbus_interface)
+
+ if path_keyword is not None:
+ from warnings import warn
+ warn(DeprecationWarning('dbus.service.signal::path_keyword has been '
+ 'deprecated since dbus-python 0.82.0, and '
+ 'will not work on objects that support '
+ 'multiple object paths'),
+ DeprecationWarning, stacklevel=2)
+ if rel_path_keyword is not None:
+ raise TypeError('dbus.service.signal::path_keyword and '
+ 'rel_path_keyword cannot both be used')
+
+ def decorator(func):
+ member_name = func.__name__
+ validate_member_name(member_name)
+
+ def emit_signal(self, *args, **keywords):
+ abs_path = None
+ if path_keyword is not None:
+ if self.SUPPORTS_MULTIPLE_OBJECT_PATHS:
+ raise TypeError('path_keyword cannot be used on the '
+ 'signals of an object that supports '
+ 'multiple object paths')
+ abs_path = keywords.pop(path_keyword, None)
+ if (abs_path != self.__dbus_object_path__ and
+ not self.__dbus_object_path__.startswith(abs_path + '/')):
+ raise ValueError('Path %r is not below %r', abs_path,
+ self.__dbus_object_path__)
+
+ rel_path = None
+ if rel_path_keyword is not None:
+ rel_path = keywords.pop(rel_path_keyword, None)
+
+ func(self, *args, **keywords)
+
+ for location in self.locations:
+ if abs_path is None:
+ # non-deprecated case
+ if rel_path is None or rel_path in ('/', ''):
+ object_path = location[1]
+ else:
+ # will be validated by SignalMessage ctor in a moment
+ object_path = location[1] + rel_path
+ else:
+ object_path = abs_path
+
+ message = SignalMessage(object_path,
+ dbus_interface,
+ member_name)
+ message.append(signature=signature, *args)
+
+ location[0].send_message(message)
+ # end emit_signal
+
+ if hasattr(inspect, 'Signature'):
+ args = []
+
+ for arg in inspect.signature(func).parameters.values():
+ if arg.kind in (inspect.Parameter.POSITIONAL_ONLY,
+ inspect.Parameter.POSITIONAL_OR_KEYWORD):
+ args.append(arg.name)
+ else:
+ args = inspect.getargspec(func)[0]
+
+ args.pop(0)
+
+ for keyword in rel_path_keyword, path_keyword:
+ if keyword is not None:
+ try:
+ args.remove(keyword)
+ except ValueError:
+ raise ValueError('function has no argument "%s"' % keyword)
+
+ if signature:
+ sig = tuple(Signature(signature))
+
+ if len(sig) > len(args):
+ raise ValueError('signal signature is longer than the number of arguments provided')
+ elif len(sig) < len(args):
+ raise ValueError('signal signature is shorter than the number of arguments provided')
+
+ emit_signal.__name__ = func.__name__
+ emit_signal.__doc__ = func.__doc__
+ emit_signal._dbus_is_signal = True
+ emit_signal._dbus_interface = dbus_interface
+ emit_signal._dbus_signature = signature
+ emit_signal._dbus_args = args
+ return emit_signal
+
+ return decorator
diff --git a/dbus/exceptions.py b/dbus/exceptions.py
new file mode 100644
index 0000000..870b731
--- /dev/null
+++ b/dbus/exceptions.py
@@ -0,0 +1,133 @@
+"""D-Bus exceptions."""
+
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('DBusException', 'MissingErrorHandlerException',
+ 'MissingReplyHandlerException', 'ValidationException',
+ 'IntrospectionParserException', 'UnknownMethodException',
+ 'NameExistsException')
+
+from dbus._compat import is_py3
+
+
+class DBusException(Exception):
+
+ include_traceback = False
+ """If True, tracebacks will be included in the exception message sent to
+ D-Bus clients.
+
+ Exceptions that are not DBusException subclasses always behave
+ as though this is True. Set this to True on DBusException subclasses
+ that represent a programming error, and leave it False on subclasses that
+ represent an expected failure condition (e.g. a network server not
+ responding)."""
+
+ def __init__(self, *args, **kwargs):
+ name = kwargs.pop('name', None)
+ if name is not None or getattr(self, '_dbus_error_name', None) is None:
+ self._dbus_error_name = name
+ if kwargs:
+ raise TypeError('DBusException does not take keyword arguments: %s'
+ % ', '.join(kwargs.keys()))
+ Exception.__init__(self, *args)
+
+ def __unicode__(self):
+ """Return a unicode error"""
+ # We can't just use Exception.__unicode__ because it chains up weirdly.
+ # https://code.launchpad.net/~mvo/ubuntu/quantal/dbus-python/lp846044/+merge/129214
+ if len(self.args) > 1:
+ s = unicode(self.args)
+ else:
+ s = ''.join(self.args)
+
+ if self._dbus_error_name is not None:
+ return '%s: %s' % (self._dbus_error_name, s)
+ else:
+ return s
+
+ def __str__(self):
+ """Return a str error"""
+ s = Exception.__str__(self)
+ if self._dbus_error_name is not None:
+ return '%s: %s' % (self._dbus_error_name, s)
+ else:
+ return s
+
+ def get_dbus_message(self):
+ if len(self.args) > 1:
+ s = str(self.args)
+ else:
+ s = ''.join(self.args)
+
+ if isinstance(s, bytes):
+ return s.decode('utf-8', 'replace')
+
+ return s
+
+ def get_dbus_name(self):
+ return self._dbus_error_name
+
+class MissingErrorHandlerException(DBusException):
+
+ include_traceback = True
+
+ def __init__(self):
+ DBusException.__init__(self, "error_handler not defined: if you define a reply_handler you must also define an error_handler")
+
+class MissingReplyHandlerException(DBusException):
+
+ include_traceback = True
+
+ def __init__(self):
+ DBusException.__init__(self, "reply_handler not defined: if you define an error_handler you must also define a reply_handler")
+
+class ValidationException(DBusException):
+
+ include_traceback = True
+
+ def __init__(self, msg=''):
+ DBusException.__init__(self, "Error validating string: %s"%msg)
+
+class IntrospectionParserException(DBusException):
+
+ include_traceback = True
+
+ def __init__(self, msg=''):
+ DBusException.__init__(self, "Error parsing introspect data: %s"%msg)
+
+class UnknownMethodException(DBusException):
+
+ include_traceback = True
+ _dbus_error_name = 'org.freedesktop.DBus.Error.UnknownMethod'
+
+ def __init__(self, method):
+ DBusException.__init__(self, "Unknown method: %s"%method)
+
+class NameExistsException(DBusException):
+
+ include_traceback = True
+
+ def __init__(self, name):
+ DBusException.__init__(self, "Bus name already exists: %s"%name)
diff --git a/dbus/gi_service.py b/dbus/gi_service.py
new file mode 100644
index 0000000..f68b088
--- /dev/null
+++ b/dbus/gi_service.py
@@ -0,0 +1,87 @@
+"""Support code for implementing D-Bus services via PyGI."""
+
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ['ExportedGObject']
+
+from gi.repository import GObject
+import dbus.service
+
+# The odd syntax used here is required so that the code is compatible with
+# both Python 2 and Python 3. It essentially creates a new class called
+# ExportedGObject with a metaclass of ExportGObjectType and an __init__()
+# function.
+#
+# Because GObject and `dbus.service.Object` both have custom metaclasses, the
+# naive approach using simple multiple inheritance won't work. This class has
+# `ExportedGObjectType` as its metaclass, which is sufficient to make it work
+# correctly.
+
+class ExportedGObjectType(GObject.GObject.__class__, dbus.service.InterfaceType):
+ """A metaclass which inherits from both GObjectMeta and
+ `dbus.service.InterfaceType`. Used as the metaclass for `ExportedGObject`.
+ """
+ def __init__(cls, name, bases, dct):
+ GObject.GObject.__class__.__init__(cls, name, bases, dct)
+ dbus.service.InterfaceType.__init__(cls, name, bases, dct)
+
+
+def ExportedGObject__init__(self, conn=None, object_path=None, **kwargs):
+ """Initialize an exported GObject.
+
+ :Parameters:
+ `conn` : dbus.connection.Connection
+ The D-Bus connection or bus
+ `object_path` : str
+ The object path at which to register this object.
+ :Keywords:
+ `bus_name` : dbus.service.BusName
+ A bus name to be held on behalf of this object, or None.
+ `gobject_properties` : dict
+ GObject properties to be set on the constructed object.
+
+ Any unrecognised keyword arguments will also be interpreted
+ as GObject properties.
+ """
+ bus_name = kwargs.pop('bus_name', None)
+ gobject_properties = kwargs.pop('gobject_properties', None)
+
+ if gobject_properties is not None:
+ kwargs.update(gobject_properties)
+ GObject.GObject.__init__(self, **kwargs)
+ dbus.service.Object.__init__(self, conn=conn,
+ object_path=object_path,
+ bus_name=bus_name)
+
+ExportedGObject__doc__ = '''
+A GObject which is exported on D-Bus.
+'''
+
+ExportedGObject = ExportedGObjectType(
+ 'ExportedGObject',
+ (GObject.GObject, dbus.service.Object),
+ {'__init__': ExportedGObject__init__,
+ '__doc__': ExportedGObject__doc__,
+ })
diff --git a/dbus/glib.py b/dbus/glib.py
new file mode 100644
index 0000000..b521fcf
--- /dev/null
+++ b/dbus/glib.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2004 Anders Carlsson
+# Copyright (C) 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""Deprecated module which sets the default GLib main context as the mainloop
+implementation within D-Bus, as a side-effect of being imported!
+
+This API is highly non-obvious, so instead of importing this module,
+new programs which don't need pre-0.80 compatibility should use this
+equivalent code::
+
+ from dbus.mainloop.glib import DBusGMainLoop
+ DBusGMainLoop(set_as_default=True)
+"""
+__docformat__ = 'restructuredtext'
+
+from dbus.mainloop.glib import DBusGMainLoop, threads_init
+from warnings import warn as _warn
+
+init_threads = threads_init
+
+DBusGMainLoop(set_as_default=True)
+
+_warn(DeprecationWarning("""\
+Importing dbus.glib to use the GLib main loop with dbus-python is deprecated.
+Instead, use this sequence:
+
+ from dbus.mainloop.glib import DBusGMainLoop
+
+ DBusGMainLoop(set_as_default=True)
+"""), DeprecationWarning, stacklevel=2)
diff --git a/dbus/lowlevel.py b/dbus/lowlevel.py
new file mode 100644
index 0000000..59bd8fe
--- /dev/null
+++ b/dbus/lowlevel.py
@@ -0,0 +1,38 @@
+# Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""Low-level interface to D-Bus."""
+
+__all__ = ('PendingCall', 'Message', 'MethodCallMessage',
+ 'MethodReturnMessage', 'ErrorMessage', 'SignalMessage',
+ 'HANDLER_RESULT_HANDLED', 'HANDLER_RESULT_NOT_YET_HANDLED',
+ 'MESSAGE_TYPE_INVALID', 'MESSAGE_TYPE_METHOD_CALL',
+ 'MESSAGE_TYPE_METHOD_RETURN', 'MESSAGE_TYPE_ERROR',
+ 'MESSAGE_TYPE_SIGNAL')
+
+from _dbus_bindings import (
+ ErrorMessage, HANDLER_RESULT_HANDLED, HANDLER_RESULT_NOT_YET_HANDLED,
+ MESSAGE_TYPE_ERROR, MESSAGE_TYPE_INVALID, MESSAGE_TYPE_METHOD_CALL,
+ MESSAGE_TYPE_METHOD_RETURN, MESSAGE_TYPE_SIGNAL, Message,
+ MethodCallMessage, MethodReturnMessage, PendingCall, SignalMessage)
diff --git a/dbus/mainloop/__init__.py b/dbus/mainloop/__init__.py
new file mode 100644
index 0000000..b0d20a9
--- /dev/null
+++ b/dbus/mainloop/__init__.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""Base definitions, etc. for main loop integration.
+
+"""
+
+import _dbus_bindings
+
+NativeMainLoop = _dbus_bindings.NativeMainLoop
+
+NULL_MAIN_LOOP = _dbus_bindings.NULL_MAIN_LOOP
+"""A null mainloop which doesn't actually do anything.
+
+For advanced users who want to dispatch events by hand. This is almost
+certainly a bad idea - if in doubt, use the GLib main loop found in
+`dbus.mainloop.glib`.
+"""
+
+WATCH_READABLE = _dbus_bindings.WATCH_READABLE
+"""Represents a file descriptor becoming readable.
+Used to implement file descriptor watches."""
+
+WATCH_WRITABLE = _dbus_bindings.WATCH_WRITABLE
+"""Represents a file descriptor becoming readable.
+Used to implement file descriptor watches."""
+
+WATCH_HANGUP = _dbus_bindings.WATCH_HANGUP
+"""Represents a file descriptor reaching end-of-file.
+Used to implement file descriptor watches."""
+
+WATCH_ERROR = _dbus_bindings.WATCH_ERROR
+"""Represents an error condition on a file descriptor.
+Used to implement file descriptor watches."""
+
+__all__ = (
+ # Imported into this module
+ 'NativeMainLoop', 'WATCH_READABLE', 'WATCH_WRITABLE',
+ 'WATCH_HANGUP', 'WATCH_ERROR', 'NULL_MAIN_LOOP',
+
+ # Submodules
+ 'glib'
+ )
diff --git a/dbus/mainloop/glib.py b/dbus/mainloop/glib.py
new file mode 100644
index 0000000..5bb2f2e
--- /dev/null
+++ b/dbus/mainloop/glib.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2004 Anders Carlsson
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""GLib main loop integration using libdbus-glib."""
+
+__all__ = ('DBusGMainLoop', 'threads_init')
+
+from _dbus_glib_bindings import DBusGMainLoop, gthreads_init
+
+_dbus_gthreads_initialized = False
+def threads_init():
+ """Initialize threads in dbus-glib, if this has not already been done.
+
+ This must be called before creating a second thread in a program that
+ uses this module.
+ """
+ global _dbus_gthreads_initialized
+ if not _dbus_gthreads_initialized:
+ gthreads_init()
+ _dbus_gthreads_initialized = True
diff --git a/dbus/proxies.py b/dbus/proxies.py
new file mode 100644
index 0000000..487976c
--- /dev/null
+++ b/dbus/proxies.py
@@ -0,0 +1,567 @@
+# Copyright (C) 2003-2007 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import logging
+
+try:
+ from threading import RLock
+except ImportError:
+ from dummy_threading import RLock
+
+import _dbus_bindings
+from dbus._expat_introspect_parser import process_introspection_data
+from dbus.exceptions import (
+ DBusException, IntrospectionParserException, MissingErrorHandlerException,
+ MissingReplyHandlerException)
+
+__docformat__ = 'restructuredtext'
+
+
+_logger = logging.getLogger('dbus.proxies')
+
+from _dbus_bindings import (
+ BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, INTROSPECTABLE_IFACE,
+ LOCAL_PATH)
+from dbus._compat import is_py2
+
+
+class _DeferredMethod:
+ """A proxy method which will only get called once we have its
+ introspection reply.
+ """
+ def __init__(self, proxy_method, append, block):
+ self._proxy_method = proxy_method
+ # the test suite relies on the existence of this property
+ self._method_name = proxy_method._method_name
+ self._append = append
+ self._block = block
+
+ def __call__(self, *args, **keywords):
+ if ('reply_handler' in keywords or
+ keywords.get('ignore_reply', False)):
+ # defer the async call til introspection finishes
+ self._append(self._proxy_method, args, keywords)
+ return None
+ else:
+ # we're being synchronous, so block
+ self._block()
+ return self._proxy_method(*args, **keywords)
+
+ def call_async(self, *args, **keywords):
+ self._append(self._proxy_method, args, keywords)
+
+
+class _ProxyMethod:
+ """A proxy method.
+
+ Typically a member of a ProxyObject. Calls to the
+ method produce messages that travel over the Bus and are routed
+ to a specific named Service.
+ """
+ def __init__(self, proxy, connection, bus_name, object_path, method_name,
+ iface):
+ if object_path == LOCAL_PATH:
+ raise DBusException('Methods may not be called on the reserved '
+ 'path %s' % LOCAL_PATH)
+
+ # trust that the proxy, and the properties it had, are OK
+ self._proxy = proxy
+ self._connection = connection
+ self._named_service = bus_name
+ self._object_path = object_path
+ # fail early if the method name is bad
+ _dbus_bindings.validate_member_name(method_name)
+ # the test suite relies on the existence of this property
+ self._method_name = method_name
+ # fail early if the interface name is bad
+ if iface is not None:
+ _dbus_bindings.validate_interface_name(iface)
+ self._dbus_interface = iface
+
+ def __call__(self, *args, **keywords):
+ reply_handler = keywords.pop('reply_handler', None)
+ error_handler = keywords.pop('error_handler', None)
+ ignore_reply = keywords.pop('ignore_reply', False)
+ signature = keywords.pop('signature', None)
+
+ if reply_handler is not None or error_handler is not None:
+ if reply_handler is None:
+ raise MissingReplyHandlerException()
+ elif error_handler is None:
+ raise MissingErrorHandlerException()
+ elif ignore_reply:
+ raise TypeError('ignore_reply and reply_handler cannot be '
+ 'used together')
+
+ dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
+
+ if signature is None:
+ if dbus_interface is None:
+ key = self._method_name
+ else:
+ key = dbus_interface + '.' + self._method_name
+
+ signature = self._proxy._introspect_method_map.get(key, None)
+
+ if ignore_reply or reply_handler is not None:
+ self._connection.call_async(self._named_service,
+ self._object_path,
+ dbus_interface,
+ self._method_name,
+ signature,
+ args,
+ reply_handler,
+ error_handler,
+ **keywords)
+ else:
+ return self._connection.call_blocking(self._named_service,
+ self._object_path,
+ dbus_interface,
+ self._method_name,
+ signature,
+ args,
+ **keywords)
+
+ def call_async(self, *args, **keywords):
+ reply_handler = keywords.pop('reply_handler', None)
+ error_handler = keywords.pop('error_handler', None)
+ signature = keywords.pop('signature', None)
+
+ dbus_interface = keywords.pop('dbus_interface', self._dbus_interface)
+
+ if signature is None:
+ if dbus_interface:
+ key = dbus_interface + '.' + self._method_name
+ else:
+ key = self._method_name
+ signature = self._proxy._introspect_method_map.get(key, None)
+
+ self._connection.call_async(self._named_service,
+ self._object_path,
+ dbus_interface,
+ self._method_name,
+ signature,
+ args,
+ reply_handler,
+ error_handler,
+ **keywords)
+
+
+class ProxyObject(object):
+ """A proxy to the remote Object.
+
+ A ProxyObject is provided by the Bus. ProxyObjects
+ have member functions, and can be called like normal Python objects.
+ """
+ ProxyMethodClass = _ProxyMethod
+ DeferredMethodClass = _DeferredMethod
+
+ INTROSPECT_STATE_DONT_INTROSPECT = 0
+ INTROSPECT_STATE_INTROSPECT_IN_PROGRESS = 1
+ INTROSPECT_STATE_INTROSPECT_DONE = 2
+
+ def __init__(self, conn=None, bus_name=None, object_path=None,
+ introspect=True, follow_name_owner_changes=False, **kwargs):
+ """Initialize the proxy object.
+
+ :Parameters:
+ `conn` : `dbus.connection.Connection`
+ The bus or connection on which to find this object.
+ The keyword argument `bus` is a deprecated alias for this.
+ `bus_name` : str
+ A bus name for the application owning the object, to be used
+ as the destination for method calls and the sender for
+ signal matches. The keyword argument ``named_service`` is a
+ deprecated alias for this.
+ `object_path` : str
+ The object path at which the application exports the object
+ `introspect` : bool
+ If true (default), attempt to introspect the remote
+ object to find out supported methods and their signatures
+ `follow_name_owner_changes` : bool
+ If true (default is false) and the `bus_name` is a
+ well-known name, follow ownership changes for that name
+ """
+ bus = kwargs.pop('bus', None)
+ if bus is not None:
+ if conn is not None:
+ raise TypeError('conn and bus cannot both be specified')
+ conn = bus
+ from warnings import warn
+ warn('Passing the bus parameter to ProxyObject by name is '
+ 'deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+ named_service = kwargs.pop('named_service', None)
+ if named_service is not None:
+ if bus_name is not None:
+ raise TypeError('bus_name and named_service cannot both be '
+ 'specified')
+ bus_name = named_service
+ from warnings import warn
+ warn('Passing the named_service parameter to ProxyObject by name '
+ 'is deprecated: please use positional parameters',
+ DeprecationWarning, stacklevel=2)
+ if kwargs:
+ raise TypeError('ProxyObject.__init__ does not take these '
+ 'keyword arguments: %s'
+ % ', '.join(kwargs.keys()))
+
+ if follow_name_owner_changes:
+ # we don't get the signals unless the Bus has a main loop
+ # XXX: using Bus internals
+ conn._require_main_loop()
+
+ self._bus = conn
+
+ if bus_name is not None:
+ _dbus_bindings.validate_bus_name(bus_name)
+ # the attribute is still called _named_service for the moment,
+ # for the benefit of telepathy-python
+ self._named_service = self._requested_bus_name = bus_name
+
+ _dbus_bindings.validate_object_path(object_path)
+ self.__dbus_object_path__ = object_path
+
+ if not follow_name_owner_changes:
+ self._named_service = conn.activate_name_owner(bus_name)
+
+ #PendingCall object for Introspect call
+ self._pending_introspect = None
+ #queue of async calls waiting on the Introspect to return
+ self._pending_introspect_queue = []
+ #dictionary mapping method names to their input signatures
+ self._introspect_method_map = {}
+
+ # must be a recursive lock because block() is called while locked,
+ # and calls the callback which re-takes the lock
+ self._introspect_lock = RLock()
+
+ if not introspect or self.__dbus_object_path__ == LOCAL_PATH:
+ self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
+ else:
+ self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS
+
+ self._pending_introspect = self._Introspect()
+
+ bus_name = property(lambda self: self._named_service, None, None,
+ """The bus name to which this proxy is bound. (Read-only,
+ may change.)
+
+ If the proxy was instantiated using a unique name, this property
+ is that unique name.
+
+ If the proxy was instantiated with a well-known name and with
+ ``follow_name_owner_changes`` set false (the default), this
+ property is the unique name of the connection that owned that
+ well-known name when the proxy was instantiated, which might
+ not actually own the requested well-known name any more.
+
+ If the proxy was instantiated with a well-known name and with
+ ``follow_name_owner_changes`` set true, this property is that
+ well-known name.
+ """)
+
+ requested_bus_name = property(lambda self: self._requested_bus_name,
+ None, None,
+ """The bus name which was requested when this proxy was
+ instantiated.
+ """)
+
+ object_path = property(lambda self: self.__dbus_object_path__,
+ None, None,
+ """The object-path of this proxy.""")
+
+ # XXX: We don't currently support this because it's the signal receiver
+ # that's responsible for tracking name owner changes, but it
+ # seems a natural thing to add in future.
+ #unique_bus_name = property(lambda self: something, None, None,
+ # """The unique name of the connection to which this proxy is
+ # currently bound. (Read-only, may change.)
+ # """)
+
+ def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords):
+ """Arrange for the given function to be called when the given signal
+ is received.
+
+ :Parameters:
+ `signal_name` : str
+ The name of the signal
+ `handler_function` : callable
+ A function to be called when the signal is emitted by
+ the remote object. Its positional arguments will be the
+ arguments of the signal; optionally, it may be given
+ keyword arguments as described below.
+ `dbus_interface` : str
+ Optional interface with which to qualify the signal name.
+ If None (the default) the handler will be called whenever a
+ signal of the given member name is received, whatever
+ its interface.
+ :Keywords:
+ `utf8_strings` : bool
+ If True, the handler function will receive any string
+ arguments as dbus.UTF8String objects (a subclass of str
+ guaranteed to be UTF-8). If False (default) it will receive
+ any string arguments as dbus.String objects (a subclass of
+ unicode).
+ `byte_arrays` : bool
+ If True, the handler function will receive any byte-array
+ arguments as dbus.ByteArray objects (a subclass of str).
+ If False (default) it will receive any byte-array
+ arguments as a dbus.Array of dbus.Byte (subclasses of:
+ a list of ints).
+ `sender_keyword` : str
+ If not None (the default), the handler function will receive
+ the unique name of the sending endpoint as a keyword
+ argument with this name
+ `destination_keyword` : str
+ If not None (the default), the handler function will receive
+ the bus name of the destination (or None if the signal is a
+ broadcast, as is usual) as a keyword argument with this name.
+ `interface_keyword` : str
+ If not None (the default), the handler function will receive
+ the signal interface as a keyword argument with this name.
+ `member_keyword` : str
+ If not None (the default), the handler function will receive
+ the signal name as a keyword argument with this name.
+ `path_keyword` : str
+ If not None (the default), the handler function will receive
+ the object-path of the sending object as a keyword argument
+ with this name
+ `message_keyword` : str
+ If not None (the default), the handler function will receive
+ the `dbus.lowlevel.SignalMessage` as a keyword argument with
+ this name.
+ `arg...` : unicode or UTF-8 str
+ If there are additional keyword parameters of the form
+ ``arg``\\ *n*, match only signals where the *n*\\ th argument
+ is the value given for that keyword parameter. As of this time
+ only string arguments can be matched (in particular,
+ object paths and signatures can't).
+ """
+ return \
+ self._bus.add_signal_receiver(handler_function,
+ signal_name=signal_name,
+ dbus_interface=dbus_interface,
+ bus_name=self._named_service,
+ path=self.__dbus_object_path__,
+ **keywords)
+
+ def _Introspect(self):
+ kwargs = {}
+ return self._bus.call_async(self._named_service,
+ self.__dbus_object_path__,
+ INTROSPECTABLE_IFACE, 'Introspect', '', (),
+ self._introspect_reply_handler,
+ self._introspect_error_handler,
+ require_main_loop=False, **kwargs)
+
+ def _introspect_execute_queue(self):
+ # FIXME: potential to flood the bus
+ # We should make sure mainloops all have idle handlers
+ # and do one message per idle
+ for (proxy_method, args, keywords) in self._pending_introspect_queue:
+ proxy_method(*args, **keywords)
+ self._pending_introspect_queue = []
+
+ def _introspect_reply_handler(self, data):
+ self._introspect_lock.acquire()
+ try:
+ try:
+ self._introspect_method_map = process_introspection_data(data)
+ except IntrospectionParserException as e:
+ self._introspect_error_handler(e)
+ return
+
+ self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_DONE
+ self._pending_introspect = None
+ self._introspect_execute_queue()
+ finally:
+ self._introspect_lock.release()
+
+ def _introspect_error_handler(self, error):
+ logging.basicConfig()
+ _logger.error("Introspect error on %s:%s: %s.%s: %s",
+ self._named_service, self.__dbus_object_path__,
+ error.__class__.__module__, error.__class__.__name__,
+ error)
+ self._introspect_lock.acquire()
+ try:
+ _logger.debug('Executing introspect queue due to error')
+ self._introspect_state = self.INTROSPECT_STATE_DONT_INTROSPECT
+ self._pending_introspect = None
+ self._introspect_execute_queue()
+ finally:
+ self._introspect_lock.release()
+
+ def _introspect_block(self):
+ self._introspect_lock.acquire()
+ try:
+ if self._pending_introspect is not None:
+ self._pending_introspect.block()
+ # else someone still has a _DeferredMethod from before we
+ # finished introspection: no need to do anything special any more
+ finally:
+ self._introspect_lock.release()
+
+ def _introspect_add_to_queue(self, callback, args, kwargs):
+ self._introspect_lock.acquire()
+ try:
+ if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
+ self._pending_introspect_queue.append((callback, args, kwargs))
+ else:
+ # someone still has a _DeferredMethod from before we
+ # finished introspection
+ callback(*args, **kwargs)
+ finally:
+ self._introspect_lock.release()
+
+ def __getattr__(self, member):
+ if member.startswith('__') and member.endswith('__'):
+ raise AttributeError(member)
+ else:
+ return self.get_dbus_method(member)
+
+ def get_dbus_method(self, member, dbus_interface=None):
+ """Return a proxy method representing the given D-Bus method. The
+ returned proxy method can be called in the usual way. For instance, ::
+
+ proxy.get_dbus_method("Foo", dbus_interface='com.example.Bar')(123)
+
+ is equivalent to::
+
+ proxy.Foo(123, dbus_interface='com.example.Bar')
+
+ or even::
+
+ getattr(proxy, "Foo")(123, dbus_interface='com.example.Bar')
+
+ However, using `get_dbus_method` is the only way to call D-Bus
+ methods with certain awkward names - if the author of a service
+ implements a method called ``connect_to_signal`` or even
+ ``__getattr__``, you'll need to use `get_dbus_method` to call them.
+
+ For services which follow the D-Bus convention of CamelCaseMethodNames
+ this won't be a problem.
+ """
+
+ ret = self.ProxyMethodClass(self, self._bus,
+ self._named_service,
+ self.__dbus_object_path__, member,
+ dbus_interface)
+
+ # this can be done without taking the lock - the worst that can
+ # happen is that we accidentally return a _DeferredMethod just after
+ # finishing introspection, in which case _introspect_add_to_queue and
+ # _introspect_block will do the right thing anyway
+ if self._introspect_state == self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS:
+ ret = self.DeferredMethodClass(ret, self._introspect_add_to_queue,
+ self._introspect_block)
+
+ return ret
+
+ def __repr__(self):
+ return '<ProxyObject wrapping %s %s %s at %#x>'%(
+ self._bus, self._named_service, self.__dbus_object_path__, id(self))
+ __str__ = __repr__
+
+
+class Interface(object):
+ """An interface into a remote object.
+
+ An Interface can be used to wrap ProxyObjects
+ so that calls can be routed to their correct
+ D-Bus interface.
+ """
+
+ def __init__(self, object, dbus_interface):
+ """Construct a proxy for the given interface on the given object.
+
+ :Parameters:
+ `object` : `dbus.proxies.ProxyObject` or `dbus.Interface`
+ The remote object or another of its interfaces
+ `dbus_interface` : str
+ An interface the `object` implements
+ """
+ if isinstance(object, Interface):
+ self._obj = object.proxy_object
+ else:
+ self._obj = object
+ self._dbus_interface = dbus_interface
+
+ object_path = property (lambda self: self._obj.object_path, None, None,
+ "The D-Bus object path of the underlying object")
+ __dbus_object_path__ = object_path
+ bus_name = property (lambda self: self._obj.bus_name, None, None,
+ "The bus name to which the underlying proxy object "
+ "is bound")
+ requested_bus_name = property (lambda self: self._obj.requested_bus_name,
+ None, None,
+ "The bus name which was requested when the "
+ "underlying object was created")
+ proxy_object = property (lambda self: self._obj, None, None,
+ """The underlying proxy object""")
+ dbus_interface = property (lambda self: self._dbus_interface, None, None,
+ """The D-Bus interface represented""")
+
+ def connect_to_signal(self, signal_name, handler_function,
+ dbus_interface=None, **keywords):
+ """Arrange for a function to be called when the given signal is
+ emitted.
+
+ The parameters and keyword arguments are the same as for
+ `dbus.proxies.ProxyObject.connect_to_signal`, except that if
+ `dbus_interface` is None (the default), the D-Bus interface that
+ was passed to the `Interface` constructor is used.
+ """
+ if not dbus_interface:
+ dbus_interface = self._dbus_interface
+
+ return self._obj.connect_to_signal(signal_name, handler_function,
+ dbus_interface, **keywords)
+
+ def __getattr__(self, member):
+ if member.startswith('__') and member.endswith('__'):
+ raise AttributeError(member)
+ else:
+ return self._obj.get_dbus_method(member, self._dbus_interface)
+
+ def get_dbus_method(self, member, dbus_interface=None):
+ """Return a proxy method representing the given D-Bus method.
+
+ This is the same as `dbus.proxies.ProxyObject.get_dbus_method`
+ except that if `dbus_interface` is None (the default),
+ the D-Bus interface that was passed to the `Interface` constructor
+ is used.
+ """
+ if dbus_interface is None:
+ dbus_interface = self._dbus_interface
+ return self._obj.get_dbus_method(member, dbus_interface)
+
+ def __repr__(self):
+ return '<Interface %r implementing %r at %#x>'%(
+ self._obj, self._dbus_interface, id(self))
+ __str__ = __repr__
diff --git a/dbus/server.py b/dbus/server.py
new file mode 100644
index 0000000..40a7bb9
--- /dev/null
+++ b/dbus/server.py
@@ -0,0 +1,119 @@
+# Copyright (C) 2008 Openismus GmbH <http://openismus.com/>
+# Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('Server', )
+__docformat__ = 'reStructuredText'
+
+from _dbus_bindings import _Server
+from dbus.connection import Connection
+
+class Server(_Server):
+ """An opaque object representing a server that listens for connections from
+ other applications.
+
+ This class is not useful to instantiate directly: you must subclass it and
+ either extend the method connection_added, or append to the
+ list on_connection_added.
+
+ :Since: 0.83
+ """
+
+ def __new__(cls, address, connection_class=Connection,
+ mainloop=None, auth_mechanisms=None):
+ """Construct a new Server.
+
+ :Parameters:
+ `address` : str
+ Listen on this address.
+ `connection_class` : type
+ When new connections come in, instantiate this subclass
+ of dbus.connection.Connection to represent them.
+ The default is Connection.
+ `mainloop` : dbus.mainloop.NativeMainLoop or None
+ The main loop with which to associate the new connections.
+ `auth_mechanisms` : sequence of str
+ Authentication mechanisms to allow. The default is to allow
+ any authentication mechanism supported by ``libdbus``.
+ """
+ return super(Server, cls).__new__(cls, address, connection_class,
+ mainloop, auth_mechanisms)
+
+ def __init__(self, *args, **kwargs):
+
+ self.__connections = {}
+
+ self.on_connection_added = []
+ """A list of callbacks to invoke when a connection is added.
+ They receive two arguments: this Server and the new Connection."""
+
+ self.on_connection_removed = []
+ """A list of callbacks to invoke when a connection becomes
+ disconnected. They receive two arguments: this Server and the removed
+ Connection."""
+
+ # This method name is hard-coded in _dbus_bindings._Server.
+ # This is not public API.
+ def _on_new_connection(self, conn):
+ conn.call_on_disconnection(self.connection_removed)
+ self.connection_added(conn)
+
+ def connection_added(self, conn):
+ """Respond to the creation of a new Connection.
+
+ This base-class implementation just invokes the callbacks in
+ the on_connection_added attribute.
+
+ :Parameters:
+ `conn` : dbus.connection.Connection
+ A D-Bus connection which has just been added.
+
+ The type of this parameter is whatever was passed
+ to the Server constructor as the ``connection_class``.
+ """
+ if self.on_connection_added:
+ for cb in self.on_connection_added:
+ cb(conn)
+
+ def connection_removed(self, conn):
+ """Respond to the disconnection of a Connection.
+
+ This base-class implementation just invokes the callbacks in
+ the on_connection_removed attribute.
+
+ :Parameters:
+ `conn` : dbus.connection.Connection
+ A D-Bus connection which has just become disconnected.
+
+ The type of this parameter is whatever was passed
+ to the Server constructor as the ``connection_class``.
+ """
+ if self.on_connection_removed:
+ for cb in self.on_connection_removed:
+ cb(conn)
+
+ address = property(_Server.get_address)
+ id = property(_Server.get_id)
+ is_connected = property(_Server.get_is_connected)
+
diff --git a/dbus/service.py b/dbus/service.py
new file mode 100644
index 0000000..2e13d3c
--- /dev/null
+++ b/dbus/service.py
@@ -0,0 +1,840 @@
+# Copyright (C) 2003-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2003 David Zeuthen
+# Copyright (C) 2004 Rob Taylor
+# Copyright (C) 2005-2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+__all__ = ('BusName', 'Object', 'FallbackObject', 'method', 'signal')
+__docformat__ = 'restructuredtext'
+
+import sys
+import logging
+import threading
+import traceback
+try:
+ from collections.abc import Sequence
+except ImportError:
+ # Python 2 (and 3.x < 3.3, but we don't support those)
+ from collections import Sequence
+
+import _dbus_bindings
+from dbus import (
+ INTROSPECTABLE_IFACE, ObjectPath, SessionBus, Signature, Struct,
+ validate_bus_name, validate_object_path)
+from dbus.decorators import method, signal
+from dbus.exceptions import (
+ DBusException, NameExistsException, UnknownMethodException)
+from dbus.lowlevel import ErrorMessage, MethodReturnMessage, MethodCallMessage
+from dbus.proxies import LOCAL_PATH
+from dbus._compat import is_py2
+
+
+_logger = logging.getLogger('dbus.service')
+
+
+class _VariantSignature(object):
+ """A fake method signature which, when iterated, yields an endless stream
+ of 'v' characters representing variants (handy with zip()).
+
+ It has no string representation.
+ """
+ def __iter__(self):
+ """Return self."""
+ return self
+
+ def __next__(self):
+ """Return 'v' whenever called."""
+ return 'v'
+
+
+class BusName(object):
+ """A base class for exporting your own Named Services across the Bus.
+
+ When instantiated, objects of this class attempt to claim the given
+ well-known name on the given bus for the current process. The name is
+ released when the BusName object becomes unreferenced.
+
+ If a well-known name is requested multiple times, multiple references
+ to the same BusName object will be returned.
+
+ :Caveats:
+
+ - Assumes that named services are only ever requested using this class -
+ if you request names from the bus directly, confusion may occur.
+ - Does not handle queueing.
+ """
+ def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
+ """Constructor, which may either return an existing cached object
+ or a new object.
+
+ :Parameters:
+ `name` : str
+ The well-known name to be advertised
+ `bus` : dbus.Bus
+ A Bus on which this service will be advertised.
+
+ Omitting this parameter or setting it to None has been
+ deprecated since version 0.82.1. For backwards compatibility,
+ if this is done, the global shared connection to the session
+ bus will be used.
+
+ `allow_replacement` : bool
+ If True, other processes trying to claim the same well-known
+ name will take precedence over this one.
+ `replace_existing` : bool
+ If True, this process can take over the well-known name
+ from other processes already holding it.
+ `do_not_queue` : bool
+ If True, this service will not be placed in the queue of
+ services waiting for the requested name if another service
+ already holds it.
+ """
+ validate_bus_name(name, allow_well_known=True, allow_unique=False)
+
+ # if necessary, get default bus (deprecated)
+ if bus is None:
+ import warnings
+ warnings.warn('Omitting the "bus" parameter to '
+ 'dbus.service.BusName.__init__ is deprecated',
+ DeprecationWarning, stacklevel=2)
+ bus = SessionBus()
+
+ # see if this name is already defined, return it if so
+ # FIXME: accessing internals of Bus
+ if name in bus._bus_names:
+ return bus._bus_names[name]
+
+ # otherwise register the name
+ name_flags = (
+ (allow_replacement and _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT or 0) |
+ (replace_existing and _dbus_bindings.NAME_FLAG_REPLACE_EXISTING or 0) |
+ (do_not_queue and _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE or 0))
+
+ retval = bus.request_name(name, name_flags)
+
+ # TODO: more intelligent tracking of bus name states?
+ if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
+ pass
+ elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
+ # queueing can happen by default, maybe we should
+ # track this better or let the user know if they're
+ # queued or not?
+ pass
+ elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
+ raise NameExistsException(name)
+ elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
+ # if this is a shared bus which is being used by someone
+ # else in this process, this can happen legitimately
+ pass
+ else:
+ raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
+
+ # and create the object
+ bus_name = object.__new__(cls)
+ bus_name._bus = bus
+ bus_name._name = name
+
+ # cache instance (weak ref only)
+ # FIXME: accessing Bus internals again
+ bus._bus_names[name] = bus_name
+
+ return bus_name
+
+ # do nothing because this is called whether or not the bus name
+ # object was retrieved from the cache or created new
+ def __init__(self, *args, **keywords):
+ pass
+
+ # we can delete the low-level name here because these objects
+ # are guaranteed to exist only once for each bus name
+ def __del__(self):
+ self._bus.release_name(self._name)
+ pass
+
+ def get_bus(self):
+ """Get the Bus this Service is on"""
+ return self._bus
+
+ def get_name(self):
+ """Get the name of this service"""
+ return self._name
+
+ def __repr__(self):
+ return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
+ __str__ = __repr__
+
+
+def _method_lookup(self, method_name, dbus_interface):
+ """Walks the Python MRO of the given class to find the method to invoke.
+
+ Returns two methods, the one to call, and the one it inherits from which
+ defines its D-Bus interface name, signature, and attributes.
+ """
+ parent_method = None
+ candidate_class = None
+ successful = False
+
+ # split up the cases when we do and don't have an interface because the
+ # latter is much simpler
+ if dbus_interface:
+ # search through the class hierarchy in python MRO order
+ for cls in self.__class__.__mro__:
+ # if we haven't got a candidate class yet, and we find a class with a
+ # suitably named member, save this as a candidate class
+ if (not candidate_class and method_name in cls.__dict__):
+ if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
+ and "_dbus_interface" in cls.__dict__[method_name].__dict__):
+ # however if it is annotated for a different interface
+ # than we are looking for, it cannot be a candidate
+ if cls.__dict__[method_name]._dbus_interface == dbus_interface:
+ candidate_class = cls
+ parent_method = cls.__dict__[method_name]
+ successful = True
+ break
+ else:
+ pass
+ else:
+ candidate_class = cls
+
+ # if we have a candidate class, carry on checking this and all
+ # superclasses for a method annoated as a dbus method
+ # on the correct interface
+ if (candidate_class and method_name in cls.__dict__
+ and "_dbus_is_method" in cls.__dict__[method_name].__dict__
+ and "_dbus_interface" in cls.__dict__[method_name].__dict__
+ and cls.__dict__[method_name]._dbus_interface == dbus_interface):
+ # the candidate class has a dbus method on the correct interface,
+ # or overrides a method that is, success!
+ parent_method = cls.__dict__[method_name]
+ successful = True
+ break
+
+ else:
+ # simpler version of above
+ for cls in self.__class__.__mro__:
+ if (not candidate_class and method_name in cls.__dict__):
+ candidate_class = cls
+
+ if (candidate_class and method_name in cls.__dict__
+ and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
+ parent_method = cls.__dict__[method_name]
+ successful = True
+ break
+
+ if successful:
+ return (candidate_class.__dict__[method_name], parent_method)
+ else:
+ if dbus_interface:
+ raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
+ else:
+ raise UnknownMethodException('%s is not a valid method' % method_name)
+
+
+def _method_reply_return(connection, message, method_name, signature, *retval):
+ reply = MethodReturnMessage(message)
+ try:
+ reply.append(signature=signature, *retval)
+ except Exception as e:
+ logging.basicConfig()
+ if signature is None:
+ try:
+ signature = reply.guess_signature(retval) + ' (guessed)'
+ except Exception as e:
+ _logger.error('Unable to guess signature for arguments %r: '
+ '%s: %s', retval, e.__class__, e)
+ raise
+ _logger.error('Unable to append %r to message with signature %s: '
+ '%s: %s', retval, signature, e.__class__, e)
+ raise
+
+ if not message.get_no_reply():
+ connection.send_message(reply)
+
+
+def _method_reply_error(connection, message, exception):
+ name = getattr(exception, '_dbus_error_name', None)
+
+ if name is not None:
+ pass
+ elif getattr(exception, '__module__', '') in ('', '__main__'):
+ name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
+ else:
+ name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
+
+ et, ev, etb = sys.exc_info()
+ if isinstance(exception, DBusException) and not exception.include_traceback:
+ # We don't actually want the traceback anyway
+ contents = exception.get_dbus_message()
+ elif ev is exception:
+ # The exception was actually thrown, so we can get a traceback
+ contents = ''.join(traceback.format_exception(et, ev, etb))
+ else:
+ # We don't have any traceback for it, e.g.
+ # async_err_cb(MyException('Failed to badger the mushroom'))
+ # see also https://bugs.freedesktop.org/show_bug.cgi?id=12403
+ contents = ''.join(traceback.format_exception_only(exception.__class__,
+ exception))
+ reply = ErrorMessage(message, name, contents)
+
+ if not message.get_no_reply():
+ connection.send_message(reply)
+
+
+class InterfaceType(type):
+ def __init__(cls, name, bases, dct):
+ # these attributes are shared between all instances of the Interface
+ # object, so this has to be a dictionary that maps class names to
+ # the per-class introspection/interface data
+ class_table = getattr(cls, '_dbus_class_table', {})
+ cls._dbus_class_table = class_table
+ interface_table = class_table[cls.__module__ + '.' + name] = {}
+
+ # merge all the name -> method tables for all the interfaces
+ # implemented by our base classes into our own
+ for b in bases:
+ base_name = b.__module__ + '.' + b.__name__
+ if getattr(b, '_dbus_class_table', False):
+ for (interface, method_table) in class_table[base_name].items():
+ our_method_table = interface_table.setdefault(interface, {})
+ our_method_table.update(method_table)
+
+ # add in all the name -> method entries for our own methods/signals
+ for func in dct.values():
+ if getattr(func, '_dbus_interface', False):
+ method_table = interface_table.setdefault(func._dbus_interface, {})
+ method_table[func.__name__] = func
+
+ super(InterfaceType, cls).__init__(name, bases, dct)
+
+ # methods are different to signals, so we have two functions... :)
+ def _reflect_on_method(cls, func):
+ args = func._dbus_args
+
+ if func._dbus_in_signature:
+ # convert signature into a tuple so length refers to number of
+ # types, not number of characters. the length is checked by
+ # the decorator to make sure it matches the length of args.
+ in_sig = tuple(Signature(func._dbus_in_signature))
+ else:
+ # magic iterator which returns as many v's as we need
+ in_sig = _VariantSignature()
+
+ if func._dbus_out_signature:
+ out_sig = Signature(func._dbus_out_signature)
+ else:
+ # its tempting to default to Signature('v'), but
+ # for methods that return nothing, providing incorrect
+ # introspection data is worse than providing none at all
+ out_sig = []
+
+ reflection_data = ' <method name="%s">\n' % (func.__name__)
+ for pair in zip(in_sig, args):
+ reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
+ for type in out_sig:
+ reflection_data += ' <arg direction="out" type="%s" />\n' % type
+ reflection_data += ' </method>\n'
+
+ return reflection_data
+
+ def _reflect_on_signal(cls, func):
+ args = func._dbus_args
+
+ if func._dbus_signature:
+ # convert signature into a tuple so length refers to number of
+ # types, not number of characters
+ sig = tuple(Signature(func._dbus_signature))
+ else:
+ # magic iterator which returns as many v's as we need
+ sig = _VariantSignature()
+
+ reflection_data = ' <signal name="%s">\n' % (func.__name__)
+ for pair in zip(sig, args):
+ reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
+ reflection_data = reflection_data + ' </signal>\n'
+
+ return reflection_data
+
+
+# Define Interface as an instance of the metaclass InterfaceType, in a way
+# that is compatible across both Python 2 and Python 3.
+Interface = InterfaceType('Interface', (object,), {})
+
+
+#: A unique object used as the value of Object._object_path and
+#: Object._connection if it's actually in more than one place
+_MANY = object()
+
+class Object(Interface):
+ r"""A base class for exporting your own Objects across the Bus.
+
+ Just inherit from Object and mark exported methods with the
+ @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
+
+ Example::
+
+ class Example(dbus.service.object):
+ def __init__(self, object_path):
+ dbus.service.Object.__init__(self, dbus.SessionBus(), path)
+ self._last_input = None
+
+ @dbus.service.method(interface='com.example.Sample',
+ in_signature='v', out_signature='s')
+ def StringifyVariant(self, var):
+ self.LastInputChanged(var) # emits the signal
+ return str(var)
+
+ @dbus.service.signal(interface='com.example.Sample',
+ signature='v')
+ def LastInputChanged(self, var):
+ # run just before the signal is actually emitted
+ # just put "pass" if nothing should happen
+ self._last_input = var
+
+ @dbus.service.method(interface='com.example.Sample',
+ in_signature='', out_signature='v')
+ def GetLastInput(self):
+ return self._last_input
+ """
+
+ #: If True, this object can be made available at more than one object path.
+ #: If True but `SUPPORTS_MULTIPLE_CONNECTIONS` is False, the object may
+ #: handle more than one object path, but they must all be on the same
+ #: connection.
+ SUPPORTS_MULTIPLE_OBJECT_PATHS = False
+
+ #: If True, this object can be made available on more than one connection.
+ #: If True but `SUPPORTS_MULTIPLE_OBJECT_PATHS` is False, the object must
+ #: have the same object path on all its connections.
+ SUPPORTS_MULTIPLE_CONNECTIONS = False
+
+ def __init__(self, conn=None, object_path=None, bus_name=None):
+ """Constructor. Either conn or bus_name is required; object_path
+ is also required.
+
+ :Parameters:
+ `conn` : dbus.connection.Connection or None
+ The connection on which to export this object.
+
+ If None, use the Bus associated with the given ``bus_name``.
+ If there is no ``bus_name`` either, the object is not
+ initially available on any Connection.
+
+ For backwards compatibility, if an instance of
+ dbus.service.BusName is passed as the first parameter,
+ this is equivalent to passing its associated Bus as
+ ``conn``, and passing the BusName itself as ``bus_name``.
+
+ `object_path` : str or None
+ A D-Bus object path at which to make this Object available
+ immediately. If this is not None, a `conn` or `bus_name` must
+ also be provided.
+
+ `bus_name` : dbus.service.BusName or None
+ Represents a well-known name claimed by this process. A
+ reference to the BusName object will be held by this
+ Object, preventing the name from being released during this
+ Object's lifetime (unless it's released manually).
+ """
+ if object_path is not None:
+ validate_object_path(object_path)
+
+ if isinstance(conn, BusName):
+ # someone's using the old API; don't gratuitously break them
+ bus_name = conn
+ conn = bus_name.get_bus()
+ elif conn is None:
+ if bus_name is not None:
+ # someone's using the old API but naming arguments, probably
+ conn = bus_name.get_bus()
+
+ #: Either an object path, None or _MANY
+ self._object_path = None
+ #: Either a dbus.connection.Connection, None or _MANY
+ self._connection = None
+ #: A list of tuples (Connection, object path, False) where the False
+ #: is for future expansion (to support fallback paths)
+ self._locations = []
+ #: Lock protecting `_locations`, `_connection` and `_object_path`
+ self._locations_lock = threading.Lock()
+
+ #: True if this is a fallback object handling a whole subtree.
+ self._fallback = False
+
+ self._name = bus_name
+
+ if conn is None and object_path is not None:
+ raise TypeError('If object_path is given, either conn or bus_name '
+ 'is required')
+ if conn is not None and object_path is not None:
+ self.add_to_connection(conn, object_path)
+
+ @property
+ def __dbus_object_path__(self):
+ """The object-path at which this object is available.
+ Access raises AttributeError if there is no object path, or more than
+ one object path.
+
+ Changed in 0.82.0: AttributeError can be raised.
+ """
+ if self._object_path is _MANY:
+ raise AttributeError('Object %r has more than one object path: '
+ 'use Object.locations instead' % self)
+ elif self._object_path is None:
+ raise AttributeError('Object %r has no object path yet' % self)
+ else:
+ return self._object_path
+
+ @property
+ def connection(self):
+ """The Connection on which this object is available.
+ Access raises AttributeError if there is no Connection, or more than
+ one Connection.
+
+ Changed in 0.82.0: AttributeError can be raised.
+ """
+ if self._connection is _MANY:
+ raise AttributeError('Object %r is on more than one Connection: '
+ 'use Object.locations instead' % self)
+ elif self._connection is None:
+ raise AttributeError('Object %r has no Connection yet' % self)
+ else:
+ return self._connection
+
+ @property
+ def locations(self):
+ """An iterable over tuples representing locations at which this
+ object is available.
+
+ Each tuple has at least two items, but may have more in future
+ versions of dbus-python, so do not rely on their exact length.
+ The first two items are the dbus.connection.Connection and the object
+ path.
+
+ :Since: 0.82.0
+ """
+ return iter(self._locations)
+
+ def add_to_connection(self, connection, path):
+ """Make this object accessible via the given D-Bus connection and
+ object path.
+
+ :Parameters:
+ `connection` : dbus.connection.Connection
+ Export the object on this connection. If the class attribute
+ SUPPORTS_MULTIPLE_CONNECTIONS is False (default), this object
+ can only be made available on one connection; if the class
+ attribute is set True by a subclass, the object can be made
+ available on more than one connection.
+
+ `path` : dbus.ObjectPath or other str
+ Place the object at this object path. If the class attribute
+ SUPPORTS_MULTIPLE_OBJECT_PATHS is False (default), this object
+ can only be made available at one object path; if the class
+ attribute is set True by a subclass, the object can be made
+ available with more than one object path.
+
+ :Raises ValueError: if the object's class attributes do not allow the
+ object to be exported in the desired way.
+ :Since: 0.82.0
+ """
+ if path == LOCAL_PATH:
+ raise ValueError('Objects may not be exported on the reserved '
+ 'path %s' % LOCAL_PATH)
+
+ self._locations_lock.acquire()
+ try:
+ if (self._connection is not None and
+ self._connection is not connection and
+ not self.SUPPORTS_MULTIPLE_CONNECTIONS):
+ raise ValueError('%r is already exported on '
+ 'connection %r' % (self, self._connection))
+
+ if (self._object_path is not None and
+ not self.SUPPORTS_MULTIPLE_OBJECT_PATHS and
+ self._object_path != path):
+ raise ValueError('%r is already exported at object '
+ 'path %s' % (self, self._object_path))
+
+ connection._register_object_path(path, self._message_cb,
+ self._unregister_cb,
+ self._fallback)
+
+ if self._connection is None:
+ self._connection = connection
+ elif self._connection is not connection:
+ self._connection = _MANY
+
+ if self._object_path is None:
+ self._object_path = path
+ elif self._object_path != path:
+ self._object_path = _MANY
+
+ self._locations.append((connection, path, self._fallback))
+ finally:
+ self._locations_lock.release()
+
+ def remove_from_connection(self, connection=None, path=None):
+ """Make this object inaccessible via the given D-Bus connection
+ and object path. If no connection or path is specified,
+ the object ceases to be accessible via any connection or path.
+
+ :Parameters:
+ `connection` : dbus.connection.Connection or None
+ Only remove the object from this Connection. If None,
+ remove from all Connections on which it's exported.
+ `path` : dbus.ObjectPath or other str, or None
+ Only remove the object from this object path. If None,
+ remove from all object paths.
+ :Raises LookupError:
+ if the object was not exported on the requested connection
+ or path, or (if both are None) was not exported at all.
+ :Since: 0.81.1
+ """
+ self._locations_lock.acquire()
+ try:
+ if self._object_path is None or self._connection is None:
+ raise LookupError('%r is not exported' % self)
+
+ if connection is not None or path is not None:
+ dropped = []
+ for location in self._locations:
+ if ((connection is None or location[0] is connection) and
+ (path is None or location[1] == path)):
+ dropped.append(location)
+ else:
+ dropped = self._locations
+ self._locations = []
+
+ if not dropped:
+ raise LookupError('%r is not exported at a location matching '
+ '(%r,%r)' % (self, connection, path))
+
+ for location in dropped:
+ try:
+ location[0]._unregister_object_path(location[1])
+ except LookupError:
+ pass
+ if self._locations:
+ try:
+ self._locations.remove(location)
+ except ValueError:
+ pass
+ finally:
+ self._locations_lock.release()
+
+ def _unregister_cb(self, connection):
+ # there's not really enough information to do anything useful here
+ _logger.info('Unregistering exported object %r from some path '
+ 'on %r', self, connection)
+
+ def _message_cb(self, connection, message):
+ if not isinstance(message, MethodCallMessage):
+ return
+
+ try:
+ # lookup candidate method and parent method
+ method_name = message.get_member()
+ interface_name = message.get_interface()
+ (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
+
+ # set up method call parameters
+ args = message.get_args_list(**parent_method._dbus_get_args_options)
+ keywords = {}
+
+ if parent_method._dbus_out_signature is not None:
+ signature = Signature(parent_method._dbus_out_signature)
+ else:
+ signature = None
+
+ # set up async callback functions
+ if parent_method._dbus_async_callbacks:
+ (return_callback, error_callback) = parent_method._dbus_async_callbacks
+ keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
+ keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
+
+ # include the sender etc. if desired
+ if parent_method._dbus_sender_keyword:
+ keywords[parent_method._dbus_sender_keyword] = message.get_sender()
+ if parent_method._dbus_path_keyword:
+ keywords[parent_method._dbus_path_keyword] = message.get_path()
+ if parent_method._dbus_rel_path_keyword:
+ path = message.get_path()
+ rel_path = path
+ for exp in self._locations:
+ # pathological case: if we're exported in two places,
+ # one of which is a subtree of the other, then pick the
+ # subtree by preference (i.e. minimize the length of
+ # rel_path)
+ if exp[0] is connection:
+ if path == exp[1]:
+ rel_path = '/'
+ break
+ if exp[1] == '/':
+ # we already have rel_path == path at the beginning
+ continue
+ if path.startswith(exp[1] + '/'):
+ # yes we're in this exported subtree
+ suffix = path[len(exp[1]):]
+ if len(suffix) < len(rel_path):
+ rel_path = suffix
+ rel_path = ObjectPath(rel_path)
+ keywords[parent_method._dbus_rel_path_keyword] = rel_path
+
+ if parent_method._dbus_destination_keyword:
+ keywords[parent_method._dbus_destination_keyword] = message.get_destination()
+ if parent_method._dbus_message_keyword:
+ keywords[parent_method._dbus_message_keyword] = message
+ if parent_method._dbus_connection_keyword:
+ keywords[parent_method._dbus_connection_keyword] = connection
+
+ # call method
+ retval = candidate_method(self, *args, **keywords)
+
+ # we're done - the method has got callback functions to reply with
+ if parent_method._dbus_async_callbacks:
+ return
+
+ # otherwise we send the return values in a reply. if we have a
+ # signature, use it to turn the return value into a tuple as
+ # appropriate
+ if signature is not None:
+ signature_tuple = tuple(signature)
+ # if we have zero or one return values we want make a tuple
+ # for the _method_reply_return function, otherwise we need
+ # to check we're passing it a sequence
+ if len(signature_tuple) == 0:
+ if retval == None:
+ retval = ()
+ else:
+ raise TypeError('%s has an empty output signature but did not return None' %
+ method_name)
+ elif len(signature_tuple) == 1:
+ retval = (retval,)
+ else:
+ if isinstance(retval, Sequence):
+ # multi-value signature, multi-value return... proceed
+ # unchanged
+ pass
+ else:
+ raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
+ (method_name, signature))
+
+ # no signature, so just turn the return into a tuple and send it as normal
+ else:
+ if retval is None:
+ retval = ()
+ elif (isinstance(retval, tuple)
+ and not isinstance(retval, Struct)):
+ # If the return is a tuple that is not a Struct, we use it
+ # as-is on the assumption that there are multiple return
+ # values - this is the usual Python idiom. (fd.o #10174)
+ pass
+ else:
+ retval = (retval,)
+
+ _method_reply_return(connection, message, method_name, signature, *retval)
+ except Exception as exception:
+ # send error reply
+ _method_reply_error(connection, message, exception)
+
+ @method(INTROSPECTABLE_IFACE, in_signature='', out_signature='s',
+ path_keyword='object_path', connection_keyword='connection')
+ def Introspect(self, object_path, connection):
+ """Return a string of XML encoding this object's supported interfaces,
+ methods and signals.
+ """
+ reflection_data = _dbus_bindings.DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ reflection_data += '<node name="%s">\n' % object_path
+
+ interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
+ for (name, funcs) in interfaces.items():
+ reflection_data += ' <interface name="%s">\n' % (name)
+
+ for func in funcs.values():
+ if getattr(func, '_dbus_is_method', False):
+ reflection_data += self.__class__._reflect_on_method(func)
+ elif getattr(func, '_dbus_is_signal', False):
+ reflection_data += self.__class__._reflect_on_signal(func)
+
+ reflection_data += ' </interface>\n'
+
+ for name in connection.list_exported_child_objects(object_path):
+ reflection_data += ' <node name="%s"/>\n' % name
+
+ reflection_data += '</node>\n'
+
+ return reflection_data
+
+ def __repr__(self):
+ where = ''
+ if (self._object_path is not _MANY
+ and self._object_path is not None):
+ where = ' at %s' % self._object_path
+ return '<%s.%s%s at %#x>' % (self.__class__.__module__,
+ self.__class__.__name__, where,
+ id(self))
+ __str__ = __repr__
+
+class FallbackObject(Object):
+ """An object that implements an entire subtree of the object-path
+ tree.
+
+ :Since: 0.82.0
+ """
+
+ SUPPORTS_MULTIPLE_OBJECT_PATHS = True
+
+ def __init__(self, conn=None, object_path=None):
+ """Constructor.
+
+ Note that the superclass' ``bus_name`` __init__ argument is not
+ supported here.
+
+ :Parameters:
+ `conn` : dbus.connection.Connection or None
+ The connection on which to export this object. If this is not
+ None, an `object_path` must also be provided.
+
+ If None, the object is not initially available on any
+ Connection.
+
+ `object_path` : str or None
+ A D-Bus object path at which to make this Object available
+ immediately. If this is not None, a `conn` must also be
+ provided.
+
+ This object will implements all object-paths in the subtree
+ starting at this object-path, except where a more specific
+ object has been added.
+ """
+ super(FallbackObject, self).__init__()
+ self._fallback = True
+
+ if conn is None:
+ if object_path is not None:
+ raise TypeError('If object_path is given, conn is required')
+ elif object_path is None:
+ raise TypeError('If conn is given, object_path is required')
+ else:
+ self.add_to_connection(conn, object_path)
diff --git a/dbus/types.py b/dbus/types.py
new file mode 100644
index 0000000..461639e
--- /dev/null
+++ b/dbus/types.py
@@ -0,0 +1,15 @@
+# Copyright 2006-2021 Collabora Ltd.
+# Copyright 2011 Barry Warsaw
+# SPDX-License-Identifier: MIT
+
+__all__ = ['ObjectPath', 'ByteArray', 'Signature', 'Byte', 'Boolean',
+ 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64',
+ 'Double', 'String', 'Array', 'Struct', 'Dictionary',
+ 'UnixFd']
+
+from _dbus_bindings import (
+ Array, Boolean, Byte, ByteArray, Dictionary, Double, Int16, Int32, Int64,
+ ObjectPath, Signature, String, Struct, UInt16, UInt32, UInt64,
+ UnixFd)
+
+from dbus._compat import is_py2
diff --git a/dbus_bindings/abstract.c b/dbus_bindings/abstract.c
new file mode 100644
index 0000000..64ac9fb
--- /dev/null
+++ b/dbus_bindings/abstract.c
@@ -0,0 +1,698 @@
+/* Subclasses of built-in Python types supporting extra D-Bus functionality.
+ *
+ * Copyright (C) 2006-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "types-internal.h"
+
+/* Dict indexed by object IDs, whose values are nonzero variant levels
+ * for immutable variable-sized D-Bus data types (_LongBase, _StrBase, Struct).
+ *
+ * This is a strange way to store them, but adding a __dict__ to the offending
+ * objects seems even more error-prone, given that their sizes are variable!
+ */
+PyObject *_dbus_py_variant_levels = NULL;
+
+long
+dbus_py_variant_level_get(PyObject *obj)
+{
+ PyObject *vl_obj;
+ PyObject *key = PyLong_FromVoidPtr(obj);
+ long variant_level;
+
+ if (!key) {
+ return -1;
+ }
+
+ vl_obj = PyDict_GetItem(_dbus_py_variant_levels, key);
+ Py_CLEAR(key);
+
+ if (!vl_obj) {
+ /* PyDict_GetItem() does not set an exception when the key is missing.
+ * In our case, it just means that there was no entry in the variant
+ * dictionary for this object. Semantically, this is equivalent to a
+ * variant level of 0.
+ */
+ return 0;
+ }
+ variant_level = PyLong_AsLong(vl_obj);
+ if (variant_level == -1 && PyErr_Occurred()) {
+ /* variant_level < 0 can never be inserted into the dictionary; see
+ * dbus_py_variant_level_set() below. The semantics of setting
+ * variant_level < 0 is to delete it from the dictionary.
+ */
+ return -1;
+ }
+ assert(variant_level >= 0);
+ return variant_level;
+}
+
+dbus_bool_t
+dbus_py_variant_level_set(PyObject *obj, long variant_level)
+{
+ /* key is the object's ID (= pointer) to avoid referencing it */
+ PyObject *key = PyLong_FromVoidPtr(obj);
+
+ if (!key) {
+ return FALSE;
+ }
+
+ if (variant_level <= 0) {
+ if (PyDict_GetItem (_dbus_py_variant_levels, key)) {
+ if (PyDict_DelItem (_dbus_py_variant_levels, key) < 0) {
+ Py_CLEAR(key);
+ return FALSE;
+ }
+ }
+ }
+ else {
+ PyObject *vl_obj = PyLong_FromLong(variant_level);
+ if (!vl_obj) {
+ Py_CLEAR(key);
+ return FALSE;
+ }
+ if (PyDict_SetItem(_dbus_py_variant_levels, key, vl_obj) < 0) {
+ Py_CLEAR(vl_obj);
+ Py_CLEAR(key);
+ return FALSE;
+ }
+ Py_CLEAR(vl_obj);
+ }
+ Py_CLEAR(key);
+ return TRUE;
+}
+
+PyObject *
+dbus_py_variant_level_getattro(PyObject *obj, PyObject *name)
+{
+ PyObject *key, *value;
+
+ if (PyUnicode_CompareWithASCIIString(name, "variant_level"))
+ return PyObject_GenericGetAttr(obj, name);
+
+ key = PyLong_FromVoidPtr(obj);
+
+ if (!key) {
+ return NULL;
+ }
+
+ value = PyDict_GetItem(_dbus_py_variant_levels, key);
+ Py_CLEAR(key);
+
+ if (!value)
+ return PyLong_FromLong(0);
+ Py_INCREF(value);
+ return value;
+}
+
+/* To be invoked by destructors. Clear the variant level without touching the
+ * exception state */
+void
+dbus_py_variant_level_clear(PyObject *self)
+{
+ PyObject *et, *ev, *etb;
+
+ /* avoid clobbering any pending exception */
+ PyErr_Fetch(&et, &ev, &etb);
+ if (!dbus_py_variant_level_set(self, 0)) {
+ /* should never happen */
+ PyErr_WriteUnraisable(self);
+ }
+ PyErr_Restore(et, ev, etb);
+}
+
+/* Support code for float subclasses. ================================ */
+
+/* There's only one subclass at the moment (Double) but these are factored
+out to make room for Float later. (Float is implemented and #if'd out) */
+
+/* In Python >= 3.8 the tp_str for subclasses of built-in types prints
+ * the subclass repr(), which does not match dbus-python's historical
+ * behaviour. */
+static PyObject *
+DBusPythonFloat_tp_str(PyObject *self)
+{
+ return (PyFloat_Type.tp_repr)(self);
+}
+
+PyDoc_STRVAR(DBusPythonFloat_tp_doc,\
+"Base class for float subclasses with a ``variant_level`` attribute.\n"
+"Do not rely on the existence of this class outside dbus-python.\n"
+);
+
+static PyMemberDef DBusPythonFloat_tp_members[] = {
+ {"variant_level", T_LONG, offsetof(DBusPyFloatBase, variant_level),
+ READONLY,
+ "The number of nested variants wrapping the real data. "
+ "0 if not in a variant."},
+ {NULL},
+};
+
+static PyObject *
+DBusPythonFloat_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *self;
+ long variantness = 0;
+ static char *argnames[] = {"variant_level", NULL};
+
+ if (PyTuple_Size(args) > 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "__new__ takes at most one positional parameter");
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(dbus_py_empty_tuple, kwargs,
+ "|l:__new__", argnames,
+ &variantness)) return NULL;
+ if (variantness < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "variant_level must be non-negative");
+ return NULL;
+ }
+
+ self = (PyFloat_Type.tp_new)(cls, args, NULL);
+ if (self) {
+ ((DBusPyFloatBase *)self)->variant_level = variantness;
+ }
+ return self;
+}
+
+static PyObject *
+DBusPythonFloat_tp_repr(PyObject *self)
+{
+ PyObject *parent_repr = (PyFloat_Type.tp_repr)(self);
+ long variant_level = ((DBusPyFloatBase *)self)->variant_level;
+ PyObject *my_repr;
+
+ if (!parent_repr) return NULL;
+ if (variant_level > 0) {
+ my_repr = PyUnicode_FromFormat("%s(%V, variant_level=%ld)",
+ Py_TYPE(self)->tp_name,
+ REPRV(parent_repr),
+ variant_level);
+ }
+ else {
+ my_repr = PyUnicode_FromFormat("%s(%V)", Py_TYPE(self)->tp_name,
+ REPRV(parent_repr));
+ }
+ /* whether my_repr is NULL or not: */
+ Py_CLEAR(parent_repr);
+ return my_repr;
+}
+
+PyTypeObject DBusPyFloatBase_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "_dbus_bindings._FloatBase",
+ sizeof(DBusPyFloatBase),
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ DBusPythonFloat_tp_repr, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ DBusPythonFloat_tp_str, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ DBusPythonFloat_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ DBusPythonFloat_tp_members, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&PyFloat_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ DBusPythonFloat_tp_new, /* tp_new */
+};
+
+/* Support code for bytes subclasses ================================== */
+
+PyDoc_STRVAR(DBusPythonBytes_tp_doc,\
+"Base class for bytes subclasses with a ``variant_level`` attribute.\n"
+"Do not rely on the existence of this class outside dbus-python.\n"
+);
+
+static PyObject *
+DBusPythonBytes_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *self;
+ long variantness = 0;
+ static char *argnames[] = {"variant_level", NULL};
+
+ if (PyTuple_Size(args) > 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "__new__ takes at most one positional parameter");
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(dbus_py_empty_tuple, kwargs,
+ "|l:__new__", argnames,
+ &variantness))
+ return NULL;
+
+ if (variantness < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "variant_level must be non-negative");
+ return NULL;
+ }
+
+ self = (PyBytes_Type.tp_new)(cls, args, NULL);
+ if (self) {
+ if (!dbus_py_variant_level_set(self, variantness)) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+ }
+ return self;
+}
+
+static PyObject *
+DBusPythonBytes_tp_repr(PyObject *self)
+{
+ PyObject *parent_repr = (PyBytes_Type.tp_repr)(self);
+ PyObject *vl_obj;
+ PyObject *my_repr;
+ long variant_level;
+
+ if (!parent_repr) return NULL;
+ vl_obj = PyObject_GetAttr(self, dbus_py_variant_level_const);
+ if (!vl_obj) {
+ Py_CLEAR(parent_repr);
+ return NULL;
+ }
+ variant_level = PyLong_AsLong(vl_obj);
+ Py_CLEAR(vl_obj);
+ if (variant_level == -1 && PyErr_Occurred()) {
+ Py_CLEAR(parent_repr);
+ return NULL;
+ }
+ if (variant_level > 0) {
+ my_repr = PyUnicode_FromFormat("%s(%V, variant_level=%ld)",
+ Py_TYPE(self)->tp_name,
+ REPRV(parent_repr),
+ variant_level);
+ }
+ else {
+ my_repr = PyUnicode_FromFormat("%s(%V)", Py_TYPE(self)->tp_name,
+ REPRV(parent_repr));
+ }
+ /* whether my_repr is NULL or not: */
+ Py_CLEAR(parent_repr);
+ return my_repr;
+}
+
+static void
+DBusPyBytesBase_tp_dealloc(PyObject *self)
+{
+ dbus_py_variant_level_clear(self);
+ (PyBytes_Type.tp_dealloc)(self);
+}
+
+PyTypeObject DBusPyBytesBase_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "_dbus_bindings._BytesBase",
+ 0,
+ 0,
+ DBusPyBytesBase_tp_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ DBusPythonBytes_tp_repr, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ dbus_py_variant_level_getattro, /* tp_getattro */
+ dbus_py_immutable_setattro, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ DBusPythonBytes_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&PyBytes_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ DBusPythonBytes_tp_new, /* tp_new */
+};
+
+/* Support code for str subclasses ================================== */
+
+PyDoc_STRVAR(DBusPythonString_tp_doc,\
+"Base class for str subclasses with a ``variant_level`` attribute.\n"
+"Do not rely on the existence of this class outside dbus-python.\n"
+);
+
+static PyObject *
+DBusPythonString_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *self;
+ long variantness = 0;
+ static char *argnames[] = {"variant_level", NULL};
+
+ if (PyTuple_Size(args) > 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "__new__ takes at most one positional parameter");
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(dbus_py_empty_tuple, kwargs,
+ "|l:__new__", argnames,
+ &variantness)) return NULL;
+ if (variantness < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "variant_level must be non-negative");
+ return NULL;
+ }
+
+ self = (PyUnicode_Type.tp_new)(cls, args, NULL);
+ if (self) {
+ if (!dbus_py_variant_level_set(self, variantness)) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+ }
+ return self;
+}
+
+static PyObject *
+DBusPythonString_tp_repr(PyObject *self)
+{
+ PyObject *parent_repr = (PyUnicode_Type.tp_repr)(self);
+ PyObject *vl_obj;
+ PyObject *my_repr;
+ long variant_level;
+
+ if (!parent_repr) return NULL;
+ vl_obj = PyObject_GetAttr(self, dbus_py_variant_level_const);
+ if (!vl_obj) {
+ Py_CLEAR(parent_repr);
+ return NULL;
+ }
+ variant_level = PyLong_AsLong(vl_obj);
+ Py_CLEAR(vl_obj);
+ if (variant_level == -1 && PyErr_Occurred()) {
+ Py_CLEAR(parent_repr);
+ return NULL;
+ }
+
+ if (variant_level > 0) {
+ my_repr = PyUnicode_FromFormat("%s(%V, variant_level=%ld)",
+ Py_TYPE(self)->tp_name,
+ REPRV(parent_repr),
+ variant_level);
+ }
+ else {
+ my_repr = PyUnicode_FromFormat("%s(%V)", Py_TYPE(self)->tp_name,
+ REPRV(parent_repr));
+ }
+ /* whether my_repr is NULL or not: */
+ Py_CLEAR(parent_repr);
+ return my_repr;
+}
+
+static void
+DBusPyStrBase_tp_dealloc(PyObject *self)
+{
+ dbus_py_variant_level_clear(self);
+ (PyUnicode_Type.tp_dealloc)(self);
+}
+
+PyTypeObject DBusPyStrBase_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "_dbus_bindings._StrBase",
+ 0,
+ 0,
+ DBusPyStrBase_tp_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ DBusPythonString_tp_repr, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ dbus_py_variant_level_getattro, /* tp_getattro */
+ dbus_py_immutable_setattro, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ DBusPythonString_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&PyUnicode_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ DBusPythonString_tp_new, /* tp_new */
+};
+
+/* Support code for long subclasses ================================= */
+
+PyDoc_STRVAR(DBusPythonLong_tp_doc,\
+"Base class for ``long`` subclasses with a ``variant_level`` attribute.\n"
+"Do not rely on the existence of this class outside dbus-python.\n"
+);
+
+static PyObject *
+DBusPythonLong_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *self;
+ long variantness = 0;
+ static char *argnames[] = {"variant_level", NULL};
+
+ if (PyTuple_Size(args) > 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "__new__ takes at most one positional parameter");
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(dbus_py_empty_tuple, kwargs,
+ "|l:__new__", argnames,
+ &variantness)) return NULL;
+ if (variantness < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "variant_level must be non-negative");
+ return NULL;
+ }
+
+ self = (PyLong_Type.tp_new)(cls, args, NULL);
+ if (self) {
+ if (!dbus_py_variant_level_set(self, variantness)) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+ }
+ return self;
+}
+
+static PyObject *
+DBusPythonLong_tp_repr(PyObject *self)
+{
+ PyObject *parent_repr = (PyLong_Type.tp_repr)(self);
+ PyObject *vl_obj;
+ PyObject *my_repr;
+ long variant_level;
+
+ if (!parent_repr) return NULL;
+ vl_obj = PyObject_GetAttr(self, dbus_py_variant_level_const);
+ if (!vl_obj) {
+ Py_CLEAR(parent_repr);
+ return NULL;
+ }
+ variant_level = PyLong_AsLong(vl_obj);
+ Py_CLEAR(vl_obj);
+ if (variant_level < 0 && PyErr_Occurred()) {
+ Py_CLEAR(parent_repr);
+ return NULL;
+ }
+
+ if (variant_level) {
+ my_repr = PyUnicode_FromFormat("%s(%V, variant_level=%ld)",
+ Py_TYPE(self)->tp_name,
+ REPRV(parent_repr),
+ variant_level);
+ }
+ else {
+ my_repr = PyUnicode_FromFormat("%s(%V)", Py_TYPE(self)->tp_name,
+ REPRV(parent_repr));
+ }
+ /* whether my_repr is NULL or not: */
+ Py_CLEAR(parent_repr);
+ return my_repr;
+}
+
+/* In Python >= 3.8 the tp_str for subclasses of built-in types prints
+ * the subclass repr(), which does not match dbus-python's historical
+ * behaviour. */
+static PyObject *
+DBusPythonLong_tp_str(PyObject *self)
+{
+ return (PyLong_Type.tp_repr)(self);
+}
+
+static void
+DBusPyLongBase_tp_dealloc(PyObject *self)
+{
+ dbus_py_variant_level_clear(self);
+ (PyLong_Type.tp_dealloc)(self);
+}
+
+PyTypeObject DBusPyLongBase_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "_dbus_bindings._LongBase",
+ 0,
+ 0,
+ DBusPyLongBase_tp_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ DBusPythonLong_tp_repr, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ DBusPythonLong_tp_str, /* tp_str */
+ dbus_py_variant_level_getattro, /* tp_getattro */
+ dbus_py_immutable_setattro, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ DBusPythonLong_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&PyLong_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ DBusPythonLong_tp_new, /* tp_new */
+};
+
+PyObject *dbus_py_variant_level_const = NULL;
+PyObject *dbus_py_signature_const = NULL;
+PyObject *dbus_py__dbus_object_path__const = NULL;
+
+dbus_bool_t
+dbus_py_init_abstract(void)
+{
+ _dbus_py_variant_levels = PyDict_New();
+ if (!_dbus_py_variant_levels) return 0;
+
+ dbus_py__dbus_object_path__const = PyUnicode_InternFromString("__dbus_object_path__");
+ if (!dbus_py__dbus_object_path__const) return 0;
+
+ dbus_py_variant_level_const = PyUnicode_InternFromString("variant_level");
+ if (!dbus_py_variant_level_const) return 0;
+
+ dbus_py_signature_const = PyUnicode_InternFromString("signature");
+ if (!dbus_py_signature_const) return 0;
+
+ DBusPyBytesBase_Type.tp_base = &PyBytes_Type;
+ if (PyType_Ready(&DBusPyBytesBase_Type) < 0) return 0;
+
+ DBusPyFloatBase_Type.tp_base = &PyFloat_Type;
+ if (PyType_Ready(&DBusPyFloatBase_Type) < 0) return 0;
+
+ DBusPyLongBase_Type.tp_base = &PyLong_Type;
+ if (PyType_Ready(&DBusPyLongBase_Type) < 0) return 0;
+
+ DBusPyStrBase_Type.tp_base = &PyUnicode_Type;
+ if (PyType_Ready(&DBusPyStrBase_Type) < 0) return 0;
+
+ return 1;
+}
+
+dbus_bool_t
+dbus_py_insert_abstract_types(PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF(&DBusPyBytesBase_Type);
+ if (PyModule_AddObject(this_module, "_BytesBase",
+ (PyObject *)&DBusPyBytesBase_Type) < 0) return 0;
+ Py_INCREF(&DBusPyLongBase_Type);
+ Py_INCREF(&DBusPyStrBase_Type);
+ Py_INCREF(&DBusPyFloatBase_Type);
+ if (PyModule_AddObject(this_module, "_LongBase",
+ (PyObject *)&DBusPyLongBase_Type) < 0) return 0;
+ if (PyModule_AddObject(this_module, "_StrBase",
+ (PyObject *)&DBusPyStrBase_Type) < 0) return 0;
+ if (PyModule_AddObject(this_module, "_FloatBase",
+ (PyObject *)&DBusPyFloatBase_Type) < 0) return 0;
+
+ return 1;
+}
diff --git a/dbus_bindings/bus.c b/dbus_bindings/bus.c
new file mode 100644
index 0000000..ab1d4eb
--- /dev/null
+++ b/dbus_bindings/bus.c
@@ -0,0 +1,195 @@
+/* Implementation of Bus, a subtype of Connection.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include "conn-internal.h"
+
+PyObject *
+DBusPyConnection_NewForBus(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *first = NULL, *mainloop = NULL;
+ DBusConnection *conn;
+ DBusError error;
+ Connection *self;
+ static char *argnames[] = {"address_or_type", "mainloop", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OO", argnames,
+ &first, &mainloop)) {
+ return NULL;
+ }
+
+ dbus_error_init(&error);
+
+ if (first && PyUnicode_Check(first))
+ {
+ dbus_bool_t ret;
+
+ /* It's a custom address. First connect to it, then register. */
+
+ self = (Connection *)(DBusPyConnection_Type.tp_new)(cls, args, kwargs);
+ if (!self) return NULL;
+ TRACE(self);
+
+ Py_BEGIN_ALLOW_THREADS
+ ret = dbus_bus_register(self->conn, &error);
+ Py_END_ALLOW_THREADS
+ if (!ret) {
+ DBusPyException_ConsumeError(&error);
+ Py_CLEAR(self);
+ return NULL;
+ }
+
+ return (PyObject *)self;
+ }
+ else if (!first || PyLong_Check(first))
+ {
+ long type;
+ PyObject *libdbusconn;
+ PyObject *new_args;
+ PyObject *new_kwargs;
+
+ /* If the first argument isn't a string, it must be an integer
+ representing one of the well-known bus types. The default is
+ DBUS_BUS_SESSION. */
+
+ if (first) {
+ /* on Python 2 this accepts either int or long */
+ type = PyLong_AsLong(first);
+ if (type == -1 && PyErr_Occurred())
+ return NULL;
+
+ if (type != DBUS_BUS_SESSION && type != DBUS_BUS_SYSTEM
+ && type != DBUS_BUS_STARTER) {
+ PyErr_Format(PyExc_ValueError, "Unknown bus type %ld", type);
+ return NULL;
+ }
+ }
+ else {
+ type = DBUS_BUS_SESSION;
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ conn = dbus_bus_get_private(type, &error);
+ Py_END_ALLOW_THREADS
+
+ if (!conn) {
+ DBusPyException_ConsumeError(&error);
+ return NULL;
+ }
+
+ libdbusconn = DBusPyLibDBusConnection_New (conn);
+ dbus_connection_unref (conn);
+
+ if (!libdbusconn)
+ return NULL;
+
+ new_args = PyTuple_Pack(2, libdbusconn, mainloop ? mainloop : Py_None);
+ Py_CLEAR(libdbusconn);
+
+ if (!new_args) {
+ return NULL;
+ }
+
+ new_kwargs = PyDict_New();
+
+ if (!new_kwargs) {
+ Py_CLEAR(new_args);
+ return NULL;
+ }
+
+ self = (Connection *)(DBusPyConnection_Type.tp_new)(cls, new_args,
+ new_kwargs);
+ Py_CLEAR(new_args);
+ Py_CLEAR(new_kwargs);
+
+ return (PyObject *)self; /* whether NULL or not */
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError, "A string address or an integer "
+ "bus type is required");
+ return NULL;
+ }
+}
+
+PyObject *
+DBusPyConnection_GetUniqueName(Connection *self, PyObject *args UNUSED)
+{
+ const char *name;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ Py_BEGIN_ALLOW_THREADS
+ name = dbus_bus_get_unique_name(self->conn);
+ Py_END_ALLOW_THREADS
+ if (!name) {
+ return DBusPyException_SetString("This connection has no unique name "
+ "yet");
+ }
+ return PyUnicode_FromString(name);
+}
+
+PyObject *
+DBusPyConnection_SetUniqueName(Connection *self, PyObject *args)
+{
+ const char *old_name, *new_name;
+
+ if (!PyArg_ParseTuple(args, "s:set_unique_name", &new_name)) {
+ return NULL;
+ }
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+
+ /* libdbus will assert if we try to set a unique name when there's
+ * already one, so we need to make sure that can't happen.
+ * (Thanks, libdbus.)
+ *
+ * The things that can set the unique name are:
+ * - this function - but we don't release the GIL, so only one instance of
+ * this function can run
+ * - dbus_bus_get - but this is only called in a __new__ or __new__-like
+ * function, so the new connection isn't available to other code yet
+ * and this function can't be called on it
+ * - dbus_bus_register - same as dbus_bus_get
+ *
+ * Code outside dbus-python shouldn't be setting the unique name, because
+ * we're using a private connection; we have to trust the authors
+ * of mainloop bindings not to do silly things like that.
+ */
+ old_name = dbus_bus_get_unique_name(self->conn);
+ if (old_name != NULL) {
+ PyErr_Format(PyExc_ValueError, "This connection already has a "
+ "unique name: '%s'", old_name);
+ return NULL;
+ }
+ dbus_bus_set_unique_name(self->conn, new_name);
+
+ Py_RETURN_NONE;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/bytes.c b/dbus_bindings/bytes.c
new file mode 100644
index 0000000..1d5f652
--- /dev/null
+++ b/dbus_bindings/bytes.c
@@ -0,0 +1,288 @@
+/* D-Bus Byte and ByteArray types.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "types-internal.h"
+
+PyDoc_STRVAR(Byte_tp_doc,
+"dbus.Byte(integer or bytes of length 1[, variant_level])\n"
+"\n"
+"An unsigned byte: a subtype of int, with range restricted to [0, 255].\n"
+"\n"
+"A Byte `b` may be converted to a ``str`` of length 1 via\n"
+"``str(b) == chr(b)`` (Python 2) or to a ``bytes`` of length 1\n"
+"via ``bytes([b])`` (Python 3).\n"
+"\n"
+"Most of the time you don't want to use this class - it mainly exists\n"
+"for symmetry with the other D-Bus types. See `dbus.ByteArray` for a\n"
+"better way to handle arrays of Byte.\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing a byte, this is represented in Python by a\n"
+" Byte with variant_level==2.\n"
+);
+
+static PyObject *
+Byte_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *obj;
+ PyObject *tuple;
+ long variantness = 0;
+ static char *argnames[] = {"variant_level", NULL};
+
+ if (PyTuple_Size(args) > 1) {
+ PyErr_SetString(PyExc_TypeError, "Byte constructor takes no more "
+ "than one positional argument");
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(dbus_py_empty_tuple, kwargs,
+ "|l:__new__", argnames,
+ &variantness)) return NULL;
+ if (variantness < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "variant_level must be non-negative");
+ return NULL;
+ }
+
+ /* obj is a borrowed reference. It gets turned into an owned reference on
+ * the good-path of the if-statements below.
+ */
+ obj = PyTuple_GetItem(args, 0);
+
+ if (PyBytes_Check(obj)) {
+ /* string of length 1, we hope */
+ if (PyBytes_GET_SIZE(obj) != 1) {
+ goto bad_arg;
+ }
+ obj = PyLong_FromLong((unsigned char)(PyBytes_AS_STRING(obj)[0]));
+ if (!obj)
+ goto bad_arg;
+ }
+ else if (PyLong_Check(obj)) {
+ /* on Python 2 this accepts either int or long */
+ long i = PyLong_AsLong(obj);
+ long my_variant_level;
+
+ if (i == -1 && PyErr_Occurred())
+ goto bad_arg;
+
+ my_variant_level = dbus_py_variant_level_get(obj);
+ if (my_variant_level < 0)
+ return NULL;
+ if (Py_TYPE(obj) == cls && my_variant_level == variantness) {
+ Py_INCREF(obj);
+ return obj;
+ }
+ if (i < 0 || i > 255) goto bad_range;
+ /* else make it a new reference */
+ Py_INCREF(obj);
+ }
+ else {
+ goto bad_arg;
+ }
+
+ /* The tuple steals the reference to obj. */
+ tuple = Py_BuildValue("(N)", obj);
+ if (!tuple) return NULL;
+
+ obj = DBusPyLongBase_Type.tp_new(cls, tuple, kwargs);
+ Py_CLEAR(tuple);
+ return obj;
+
+bad_arg:
+ PyErr_SetString(PyExc_TypeError, "Expected a bytes or str of length 1, "
+ "or an int in the range 0-255");
+ return NULL;
+bad_range:
+ PyErr_SetString(PyExc_ValueError, "Integer outside range 0-255");
+ return NULL;
+}
+
+static PyObject *
+Byte_tp_str(PyObject *self)
+{
+ long i = PyLong_AsLong(self);
+ unsigned char str[2] = { 0, 0 };
+
+ if (i == -1 && PyErr_Occurred())
+ return NULL;
+ if (i < 0 || i > 255) {
+ PyErr_SetString(PyExc_RuntimeError, "Integer outside range 0-255");
+ return NULL;
+ }
+
+ str[0] = (unsigned char)i;
+ return PyUnicode_FromStringAndSize((char *)str, 1);
+}
+
+PyTypeObject DBusPyByte_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.Byte",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ Byte_tp_str, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ Byte_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPyLongBase_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ Byte_new, /* tp_new */
+};
+
+PyDoc_STRVAR(ByteArray_tp_doc,
+"ByteArray(str)\n"
+"\n"
+"ByteArray is a subtype of :py:class:`bytes` (an alias for\n"
+":py:class:`str` in Python 2 but a distinct type in Python 3)\n"
+"which can be used when you want an\n"
+"efficient immutable representation of a D-Bus byte array (signature ``ay``).\n"
+"\n"
+"By default, when byte arrays are converted from D-Bus to Python, they\n"
+"come out as a `dbus.Array` of `dbus.Byte`. This is just for symmetry with\n"
+"the other D-Bus types - in practice, what you usually want is the byte\n"
+"array represented as a string, using this class. To get this, pass the\n"
+"``byte_arrays=True`` keyword argument to any of these methods:\n"
+"\n"
+"* any D-Bus method proxy, or ``connect_to_signal``, on the objects returned\n"
+" by `Bus.get_object`\n"
+"* any D-Bus method on a `dbus.Interface`\n"
+"* `dbus.Interface.connect_to_signal`\n"
+"* `Bus.add_signal_receiver`\n"
+"\n"
+"Import via::\n"
+"\n"
+" from dbus import ByteArray\n"
+"\n"
+"Constructor::\n"
+"\n"
+" ByteArray(str)\n"
+);
+
+PyTypeObject DBusPyByteArray_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.ByteArray",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ ByteArray_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPyBytesBase_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+
+dbus_bool_t
+dbus_py_init_byte_types(void)
+{
+ DBusPyByte_Type.tp_base = &DBusPyLongBase_Type;
+ if (PyType_Ready(&DBusPyByte_Type) < 0) return 0;
+
+ DBusPyByteArray_Type.tp_base = &DBusPyBytesBase_Type;
+ if (PyType_Ready(&DBusPyByteArray_Type) < 0) return 0;
+
+ return 1;
+}
+
+dbus_bool_t
+dbus_py_insert_byte_types(PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF(&DBusPyByte_Type);
+ if (PyModule_AddObject(this_module, "Byte",
+ (PyObject *)&DBusPyByte_Type) < 0) return 0;
+ Py_INCREF(&DBusPyByteArray_Type);
+ if (PyModule_AddObject(this_module, "ByteArray",
+ (PyObject *)&DBusPyByteArray_Type) < 0) return 0;
+
+ return 1;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/compat-internal.h b/dbus_bindings/compat-internal.h
new file mode 100644
index 0000000..1b43e28
--- /dev/null
+++ b/dbus_bindings/compat-internal.h
@@ -0,0 +1,34 @@
+/* Old D-Bus compatibility: implementation internals
+ *
+ * Copyright © 2006-2011 Collabora Ltd.
+ * Copyright © 2011 Nokia Corporation
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef DBUS_BINDINGS_COMPAT_INTERNAL_H
+#define DBUS_BINDINGS_COMPAT_INTERNAL_H
+
+#include "dbus_bindings-internal.h"
+
+#endif
diff --git a/dbus_bindings/conn-internal.h b/dbus_bindings/conn-internal.h
new file mode 100644
index 0000000..d333d27
--- /dev/null
+++ b/dbus_bindings/conn-internal.h
@@ -0,0 +1,69 @@
+/* _dbus_bindings internal API. For use within _dbus_bindings only.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef DBUS_BINDINGS_CONN_H
+#define DBUS_BINDINGS_CONN_H
+
+#include "dbus_bindings-internal.h"
+
+typedef struct {
+ PyObject_HEAD
+ DBusConnection *conn;
+ /* A list of filter callbacks. */
+ PyObject *filters;
+ /* A dict mapping object paths to one of:
+ * - tuples (unregister_callback or None, message_callback)
+ * - None (meaning unregistration from libdbus is in progress and nobody
+ * should touch this entry til we're finished)
+ */
+ PyObject *object_paths;
+
+ /* Weak-references list to make Connections weakly referenceable */
+ PyObject *weaklist;
+
+ dbus_bool_t has_mainloop;
+} Connection;
+
+typedef struct {
+ PyObject_HEAD
+ DBusConnection *conn;
+} DBusPyLibDBusConnection;
+
+extern struct PyMethodDef DBusPyConnection_tp_methods[];
+extern DBusHandlerResult DBusPyConnection_HandleMessage(Connection *,
+ PyObject *,
+ PyObject *);
+extern PyObject *DBusPyConnection_ExistingFromDBusConnection(DBusConnection *);
+extern PyObject *DBusPyConnection_GetObjectPathHandlers(PyObject *self,
+ PyObject *path);
+
+extern PyObject *DBusPyConnection_NewForBus(PyTypeObject *cls, PyObject *args,
+ PyObject *kwargs);
+extern PyObject *DBusPyConnection_SetUniqueName(Connection *, PyObject *);
+extern PyObject *DBusPyConnection_GetUniqueName(Connection *, PyObject *);
+
+#endif
diff --git a/dbus_bindings/conn-methods.c b/dbus_bindings/conn-methods.c
new file mode 100644
index 0000000..f6ffc5b
--- /dev/null
+++ b/dbus_bindings/conn-methods.c
@@ -0,0 +1,1073 @@
+/* Implementation of normal Python-accessible methods on the _dbus_bindings
+ * Connection type; separated out to keep the file size manageable.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include "conn-internal.h"
+
+static void
+_object_path_unregister(DBusConnection *conn, void *user_data)
+{
+ PyGILState_STATE gil = PyGILState_Ensure();
+ PyObject *tuple = NULL;
+ Connection *conn_obj = NULL;
+ PyObject *callable;
+
+ conn_obj = (Connection *)DBusPyConnection_ExistingFromDBusConnection(conn);
+ if (!conn_obj) goto out;
+ TRACE(conn_obj);
+
+ DBG("Connection at %p unregistering object path %s",
+ conn_obj, PyBytes_AS_STRING((PyObject *)user_data));
+ tuple = DBusPyConnection_GetObjectPathHandlers(
+ (PyObject *)conn_obj, (PyObject *)user_data);
+ if (!tuple) goto out;
+ if (tuple == Py_None) goto out;
+
+ DBG("%s", "... yes we have handlers for that object path");
+
+ /* 0'th item is the unregisterer (if that's a word) */
+ callable = PyTuple_GetItem(tuple, 0);
+ if (callable && callable != Py_None) {
+ DBG("%s", "... and we even have an unregisterer");
+ /* any return from the unregisterer is ignored */
+ Py_XDECREF(PyObject_CallFunctionObjArgs(callable, conn_obj, NULL));
+ }
+out:
+ Py_CLEAR(conn_obj);
+ Py_CLEAR(tuple);
+ /* the user_data (a Python str) is no longer ref'd by the DBusConnection */
+ Py_CLEAR(user_data);
+ if (PyErr_Occurred()) {
+ PyErr_Print();
+ }
+ PyGILState_Release(gil);
+}
+
+static DBusHandlerResult
+_object_path_message(DBusConnection *conn, DBusMessage *message,
+ void *user_data)
+{
+ DBusHandlerResult ret;
+ PyGILState_STATE gil = PyGILState_Ensure();
+ Connection *conn_obj = NULL;
+ PyObject *tuple = NULL;
+ PyObject *msg_obj;
+ PyObject *callable; /* borrowed */
+
+ dbus_message_ref(message);
+ msg_obj = DBusPyMessage_ConsumeDBusMessage(message);
+ if (!msg_obj) {
+ ret = DBUS_HANDLER_RESULT_NEED_MEMORY;
+ goto out;
+ }
+
+ conn_obj = (Connection *)DBusPyConnection_ExistingFromDBusConnection(conn);
+ if (!conn_obj) {
+ ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ goto out;
+ }
+ TRACE(conn_obj);
+
+ DBG("Connection at %p messaging object path %s",
+ conn_obj, PyBytes_AS_STRING((PyObject *)user_data));
+ DBG_DUMP_MESSAGE(message);
+ tuple = DBusPyConnection_GetObjectPathHandlers(
+ (PyObject *)conn_obj, (PyObject *)user_data);
+ if (!tuple || tuple == Py_None) {
+ ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ goto out;
+ }
+
+ DBG("%s", "... yes we have handlers for that object path");
+
+ /* 1st item (0-based) is the message callback */
+ callable = PyTuple_GetItem(tuple, 1);
+ if (!callable) {
+ DBG("%s", "... error getting message handler from tuple");
+ ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ else if (callable == Py_None) {
+ /* there was actually no handler after all */
+ DBG("%s", "... but those handlers don't do messages");
+ ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ else {
+ DBG("%s", "... and we have a message handler for that object path");
+ ret = DBusPyConnection_HandleMessage(conn_obj, msg_obj, callable);
+ }
+
+out:
+ Py_CLEAR(msg_obj);
+ Py_CLEAR(conn_obj);
+ Py_CLEAR(tuple);
+ if (PyErr_Occurred()) {
+ PyErr_Print();
+ }
+ PyGILState_Release(gil);
+ return ret;
+}
+
+static const DBusObjectPathVTable _object_path_vtable = {
+ _object_path_unregister,
+ _object_path_message,
+};
+
+static DBusHandlerResult
+_filter_message(DBusConnection *conn, DBusMessage *message, void *user_data)
+{
+ DBusHandlerResult ret;
+ PyGILState_STATE gil = PyGILState_Ensure();
+ Connection *conn_obj = NULL;
+ PyObject *callable = NULL;
+ PyObject *msg_obj;
+#ifndef DBUS_PYTHON_DISABLE_CHECKS
+ Py_ssize_t i, size;
+#endif
+
+ dbus_message_ref(message);
+ msg_obj = DBusPyMessage_ConsumeDBusMessage(message);
+ if (!msg_obj) {
+ DBG("%s", "OOM while trying to construct Message");
+ ret = DBUS_HANDLER_RESULT_NEED_MEMORY;
+ goto out;
+ }
+
+ conn_obj = (Connection *)DBusPyConnection_ExistingFromDBusConnection(conn);
+ if (!conn_obj) {
+ DBG("%s", "failed to traverse DBusConnection -> Connection weakref");
+ ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ goto out;
+ }
+ TRACE(conn_obj);
+
+ /* The user_data is a pointer to a Python object. To avoid
+ * cross-library reference cycles, the DBusConnection isn't allowed
+ * to reference it. However, as long as the Connection is still
+ * alive, its ->filters list owns a reference to the same Python
+ * object, so the object should also still be alive.
+ *
+ * To ensure that this works, be careful whenever manipulating the
+ * filters list! (always put things in the list *before* giving
+ * them to libdbus, etc.)
+ */
+#ifdef DBUS_PYTHON_DISABLE_CHECKS
+ callable = (PyObject *)user_data;
+#else
+ size = PyList_GET_SIZE(conn_obj->filters);
+ for (i = 0; i < size; i++) {
+ callable = PyList_GET_ITEM(conn_obj->filters, i);
+ if (callable == user_data) {
+ Py_INCREF(callable);
+ break;
+ }
+ else {
+ callable = NULL;
+ }
+ }
+
+ if (!callable) {
+ DBG("... filter %p has vanished from ->filters, so not calling it",
+ user_data);
+ ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ goto out;
+ }
+#endif
+
+ ret = DBusPyConnection_HandleMessage(conn_obj, msg_obj, callable);
+out:
+ Py_CLEAR(msg_obj);
+ Py_CLEAR(conn_obj);
+ Py_CLEAR(callable);
+ PyGILState_Release(gil);
+ return ret;
+}
+
+PyDoc_STRVAR(Connection__require_main_loop__doc__,
+"_require_main_loop()\n\n"
+"Raise an exception if this Connection is not bound to any main loop -\n"
+"in this state, asynchronous calls, receiving signals and exporting objects\n"
+"will not work.\n"
+"\n"
+"`dbus.mainloop.NULL_MAIN_LOOP` is treated like a valid main loop - if you're\n"
+"using that, you presumably know what you're doing.\n");
+static PyObject *
+Connection__require_main_loop (Connection *self, PyObject *args UNUSED)
+{
+ if (!self->has_mainloop) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "To make asynchronous calls, receive signals or "
+ "export objects, D-Bus connections must be attached "
+ "to a main loop by passing mainloop=... to the "
+ "constructor or calling "
+ "dbus.set_default_main_loop(...)");
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Connection_close__doc__,
+"close()\n\n"
+"Close the connection.");
+static PyObject *
+Connection_close (Connection *self, PyObject *args UNUSED)
+{
+ TRACE(self);
+ /* Because the user explicitly asked to close the connection, we'll even
+ let them close shared connections. */
+ if (self->conn) {
+ Py_BEGIN_ALLOW_THREADS
+ dbus_connection_close(self->conn);
+ Py_END_ALLOW_THREADS
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Connection_get_is_connected__doc__,
+"get_is_connected() -> bool\n\n"
+"Return true if this Connection is connected.\n");
+static PyObject *
+Connection_get_is_connected (Connection *self, PyObject *args UNUSED)
+{
+ dbus_bool_t ret;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ Py_BEGIN_ALLOW_THREADS
+ ret = dbus_connection_get_is_connected(self->conn);
+ Py_END_ALLOW_THREADS
+ return PyBool_FromLong(ret);
+}
+
+PyDoc_STRVAR(Connection_get_is_authenticated__doc__,
+"get_is_authenticated() -> bool\n\n"
+"Return true if this Connection was ever authenticated.\n");
+static PyObject *
+Connection_get_is_authenticated (Connection *self, PyObject *args UNUSED)
+{
+ dbus_bool_t ret;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ Py_BEGIN_ALLOW_THREADS
+ ret = dbus_connection_get_is_authenticated(self->conn);
+ Py_END_ALLOW_THREADS
+ return PyBool_FromLong(ret);
+}
+
+PyDoc_STRVAR(Connection_set_exit_on_disconnect__doc__,
+"set_exit_on_disconnect(bool)\n\n"
+"Set whether the C function ``_exit`` will be called when this Connection\n"
+"becomes disconnected. This will cause the program to exit without calling\n"
+"any cleanup code or exit handlers.\n"
+"\n"
+"The default is for this feature to be disabled for Connections and enabled\n"
+"for Buses.\n");
+static PyObject *
+Connection_set_exit_on_disconnect (Connection *self, PyObject *args)
+{
+ int exit_on_disconnect;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ if (!PyArg_ParseTuple(args, "i:set_exit_on_disconnect",
+ &exit_on_disconnect)) {
+ return NULL;
+ }
+ Py_BEGIN_ALLOW_THREADS
+ dbus_connection_set_exit_on_disconnect(self->conn,
+ exit_on_disconnect ? 1 : 0);
+ Py_END_ALLOW_THREADS
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Connection_send_message__doc__,
+"send_message(msg) -> long\n\n"
+"Queue the given message for sending, and return the message serial number.\n"
+"\n"
+":Parameters:\n"
+" `msg` : dbus.lowlevel.Message\n"
+" The message to be sent.\n"
+);
+static PyObject *
+Connection_send_message(Connection *self, PyObject *args)
+{
+ dbus_bool_t ok;
+ PyObject *obj;
+ DBusMessage *msg;
+ dbus_uint32_t serial;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ if (!PyArg_ParseTuple(args, "O", &obj)) return NULL;
+
+ msg = DBusPyMessage_BorrowDBusMessage(obj);
+ if (!msg) return NULL;
+
+ Py_BEGIN_ALLOW_THREADS
+ ok = dbus_connection_send(self->conn, msg, &serial);
+ Py_END_ALLOW_THREADS
+
+ if (!ok) {
+ return PyErr_NoMemory();
+ }
+
+ return PyLong_FromUnsignedLong(serial);
+}
+
+PyDoc_STRVAR(Connection_set_allow_anonymous__doc__,
+"set_allow_anonymous(bool)\n\n"
+"Allows anonymous clients. Call this on the server side of a connection in a on_connection_added callback"
+);
+static PyObject *
+Connection_set_allow_anonymous(Connection *self, PyObject *args)
+{
+ dbus_bool_t t;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ if (!PyArg_ParseTuple(args, "i", &t)) {
+ return NULL;
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ dbus_connection_set_allow_anonymous(self->conn, t ? 1 : 0);
+ Py_END_ALLOW_THREADS
+ Py_RETURN_NONE;
+}
+
+/* The timeout is in seconds here, since that's conventional in Python. */
+PyDoc_STRVAR(Connection_send_message_with_reply__doc__,
+"send_message_with_reply(msg, reply_handler, timeout_s=-1, "
+"require_main_loop=False) -> dbus.lowlevel.PendingCall\n\n"
+"Queue the message for sending; expect a reply via the returned PendingCall,\n"
+"which can also be used to cancel the pending call.\n"
+"\n"
+":Parameters:\n"
+" `msg` : dbus.lowlevel.Message\n"
+" The message to be sent\n"
+" `reply_handler` : callable\n"
+" Asynchronous reply handler: will be called with one positional\n"
+" parameter, a Message instance representing the reply.\n"
+" `timeout_s` : float\n"
+" If the reply takes more than this many seconds, a timeout error\n"
+" will be created locally and raised instead. If this timeout is\n"
+" negative (default), a sane default (supplied by libdbus) is used.\n"
+" `require_main_loop` : bool\n"
+" If True, raise RuntimeError if this Connection does not have a main\n"
+" loop configured. If False (default) and there is no main loop, you are\n"
+" responsible for calling block() on the PendingCall.\n"
+"\n"
+);
+static PyObject *
+Connection_send_message_with_reply(Connection *self, PyObject *args, PyObject *kw)
+{
+ dbus_bool_t ok;
+ double timeout_s = -1.0;
+ int timeout_ms;
+ PyObject *obj, *callable;
+ DBusMessage *msg;
+ DBusPendingCall *pending;
+ int require_main_loop = 0;
+ static char *argnames[] = {"msg", "reply_handler", "timeout_s",
+ "require_main_loop", NULL};
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ if (!PyArg_ParseTupleAndKeywords(args, kw,
+ "OO|di:send_message_with_reply",
+ argnames,
+ &obj, &callable, &timeout_s,
+ &require_main_loop)) {
+ return NULL;
+ }
+ if (require_main_loop && !Connection__require_main_loop(self, NULL)) {
+ return NULL;
+ }
+
+ msg = DBusPyMessage_BorrowDBusMessage(obj);
+ if (!msg) return NULL;
+
+ if (timeout_s < 0) {
+ timeout_ms = -1;
+ }
+ else {
+ if (timeout_s > ((double)INT_MAX) / 1000.0) {
+ PyErr_SetString(PyExc_ValueError, "Timeout too long");
+ return NULL;
+ }
+ timeout_ms = (int)(timeout_s * 1000.0);
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ ok = dbus_connection_send_with_reply(self->conn, msg, &pending,
+ timeout_ms);
+ Py_END_ALLOW_THREADS
+
+ if (!ok) {
+ return PyErr_NoMemory();
+ }
+
+ if (!pending) {
+ /* connection is disconnected (doesn't return FALSE!) */
+ return DBusPyException_SetString ("Connection is disconnected - "
+ "unable to make method call");
+ }
+
+ return DBusPyPendingCall_ConsumeDBusPendingCall(pending, callable);
+}
+
+/* Again, the timeout is in seconds, since that's conventional in Python. */
+PyDoc_STRVAR(Connection_send_message_with_reply_and_block__doc__,
+"send_message_with_reply_and_block(msg, timeout_s=-1)"
+" -> dbus.lowlevel.Message\n\n"
+"Send the message and block while waiting for a reply.\n"
+"\n"
+"This does not re-enter the main loop, so it can lead to a deadlock, if\n"
+"the called method tries to make a synchronous call to a method in this\n"
+"application. As such, it's probably a bad idea.\n"
+"\n"
+":Parameters:\n"
+" `msg` : dbus.lowlevel.Message\n"
+" The message to be sent\n"
+" `timeout_s` : float\n"
+" If the reply takes more than this many seconds, a timeout error\n"
+" will be created locally and raised instead. If this timeout is\n"
+" negative (default), a sane default (supplied by libdbus) is used.\n"
+":Returns:\n"
+" A `dbus.lowlevel.Message` instance (probably a `dbus.lowlevel.MethodReturnMessage`) on success\n"
+":Raises dbus.DBusException:\n"
+" On error (including if the reply arrives but is an\n"
+" error message)\n"
+"\n"
+);
+static PyObject *
+Connection_send_message_with_reply_and_block(Connection *self, PyObject *args)
+{
+ double timeout_s = -1.0;
+ int timeout_ms;
+ PyObject *obj;
+ DBusMessage *msg, *reply;
+ DBusError error;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ if (!PyArg_ParseTuple(args, "O|d:send_message_with_reply_and_block", &obj,
+ &timeout_s)) {
+ return NULL;
+ }
+
+ msg = DBusPyMessage_BorrowDBusMessage(obj);
+ if (!msg) return NULL;
+
+ if (timeout_s < 0) {
+ timeout_ms = -1;
+ }
+ else {
+ if (timeout_s > ((double)INT_MAX) / 1000.0) {
+ PyErr_SetString(PyExc_ValueError, "Timeout too long");
+ return NULL;
+ }
+ timeout_ms = (int)(timeout_s * 1000.0);
+ }
+
+ dbus_error_init(&error);
+ Py_BEGIN_ALLOW_THREADS
+ reply = dbus_connection_send_with_reply_and_block(self->conn, msg,
+ timeout_ms, &error);
+ Py_END_ALLOW_THREADS
+
+ /* FIXME: if we instead used send_with_reply and blocked on the resulting
+ * PendingCall, then we could get all args from the error, not just
+ * the first */
+ if (!reply) {
+ return DBusPyException_ConsumeError(&error);
+ }
+ return DBusPyMessage_ConsumeDBusMessage(reply);
+}
+
+PyDoc_STRVAR(Connection_flush__doc__,
+"flush()\n\n"
+"Block until the outgoing message queue is empty.\n");
+static PyObject *
+Connection_flush (Connection *self, PyObject *args UNUSED)
+{
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ Py_BEGIN_ALLOW_THREADS
+ dbus_connection_flush (self->conn);
+ Py_END_ALLOW_THREADS
+ Py_RETURN_NONE;
+}
+
+/* Unsupported:
+ * dbus_connection_preallocate_send
+ * dbus_connection_free_preallocated_send
+ * dbus_connection_send_preallocated
+ * dbus_connection_borrow_message
+ * dbus_connection_return_message
+ * dbus_connection_steal_borrowed_message
+ * dbus_connection_pop_message
+ */
+
+/* Non-main-loop handling not yet implemented: */
+ /* dbus_connection_read_write_dispatch */
+ /* dbus_connection_read_write */
+
+/* Main loop handling not yet implemented: */
+ /* dbus_connection_get_dispatch_status */
+ /* dbus_connection_dispatch */
+ /* dbus_connection_set_watch_functions */
+ /* dbus_connection_set_timeout_functions */
+ /* dbus_connection_set_wakeup_main_function */
+ /* dbus_connection_set_dispatch_status_function */
+
+/* Normally in Python this would be called fileno(), but I don't want to
+ * encourage people to select() on it */
+PyDoc_STRVAR(Connection_get_unix_fd__doc__,
+"get_unix_fd() -> int or None\n\n"
+"Get the connection's UNIX file descriptor, if any.\n\n"
+"This can be used for SELinux access control checks with ``getpeercon()``\n"
+"for example. **Do not** read or write to the file descriptor, or try to\n"
+"``select()`` on it.\n");
+static PyObject *
+Connection_get_unix_fd (Connection *self, PyObject *unused UNUSED)
+{
+ int fd;
+ dbus_bool_t ok;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ Py_BEGIN_ALLOW_THREADS
+ ok = dbus_connection_get_unix_fd (self->conn, &fd);
+ Py_END_ALLOW_THREADS
+ if (!ok) Py_RETURN_NONE;
+ return PyLong_FromLong(fd);
+}
+
+PyDoc_STRVAR(Connection_get_peer_unix_user__doc__,
+"get_peer_unix_user() -> long or None\n\n"
+"Get the UNIX user ID at the other end of the connection, if it has been\n"
+"authenticated. Return None if this is a non-UNIX platform or the\n"
+"connection has not been authenticated.\n");
+static PyObject *
+Connection_get_peer_unix_user (Connection *self, PyObject *unused UNUSED)
+{
+ unsigned long uid;
+ dbus_bool_t ok;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ Py_BEGIN_ALLOW_THREADS
+ ok = dbus_connection_get_unix_user (self->conn, &uid);
+ Py_END_ALLOW_THREADS
+ if (!ok) Py_RETURN_NONE;
+ return PyLong_FromUnsignedLong (uid);
+}
+
+PyDoc_STRVAR(Connection_get_peer_unix_process_id__doc__,
+"get_peer_unix_process_id() -> long or None\n\n"
+"Get the UNIX process ID at the other end of the connection, if it has been\n"
+"authenticated. Return None if this is a non-UNIX platform or the\n"
+"connection has not been authenticated.\n");
+static PyObject *
+Connection_get_peer_unix_process_id (Connection *self, PyObject *unused UNUSED)
+{
+ unsigned long pid;
+ dbus_bool_t ok;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ Py_BEGIN_ALLOW_THREADS
+ ok = dbus_connection_get_unix_process_id (self->conn, &pid);
+ Py_END_ALLOW_THREADS
+ if (!ok) Py_RETURN_NONE;
+ return PyLong_FromUnsignedLong (pid);
+}
+
+/* TODO: wrap dbus_connection_set_unix_user_function Pythonically */
+
+PyDoc_STRVAR(Connection_add_message_filter__doc__,
+"add_message_filter(callable)\n\n"
+"Add the given message filter to the internal list.\n\n"
+"Filters are handlers that are run on all incoming messages, prior to the\n"
+"objects registered to handle object paths.\n"
+"\n"
+"Filters are run in the order that they were added. The same handler can\n"
+"be added as a filter more than once, in which case it will be run more\n"
+"than once. Filters added during a filter callback won't be run on the\n"
+"message being processed.\n"
+);
+static PyObject *
+Connection_add_message_filter(Connection *self, PyObject *callable)
+{
+ dbus_bool_t ok;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ /* The callable must be referenced by ->filters *before* it is
+ * given to libdbus, which does not own a reference to it.
+ */
+ if (PyList_Append(self->filters, callable) < 0) {
+ return NULL;
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ ok = dbus_connection_add_filter(self->conn, _filter_message, callable,
+ NULL);
+ Py_END_ALLOW_THREADS
+
+ if (!ok) {
+ Py_XDECREF(PyObject_CallMethod(self->filters, "remove", "(O)",
+ callable));
+ PyErr_NoMemory();
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Connection_remove_message_filter__doc__,
+"remove_message_filter(callable)\n\n"
+"Remove the given message filter (see `add_message_filter` for details).\n"
+"\n"
+":Raises LookupError:\n"
+" The given callable is not among the registered filters\n");
+static PyObject *
+Connection_remove_message_filter(Connection *self, PyObject *callable)
+{
+ PyObject *obj;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ /* It's safe to do this before removing it from libdbus, because
+ * the presence of callable in our arguments means we have a ref
+ * to it. */
+ obj = PyObject_CallMethod(self->filters, "remove", "(O)", callable);
+ if (!obj) return NULL;
+ Py_CLEAR(obj);
+
+ Py_BEGIN_ALLOW_THREADS
+ dbus_connection_remove_filter(self->conn, _filter_message, callable);
+ Py_END_ALLOW_THREADS
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Connection__register_object_path__doc__,
+"register_object_path(path, on_message, on_unregister=None, fallback=False)\n"
+"\n"
+"Register a callback to be called when messages arrive at the given\n"
+"object-path. Used to export objects' methods on the bus in a low-level\n"
+"way. For the high-level interface to this functionality (usually\n"
+"recommended) see the `dbus.service.Object` base class.\n"
+"\n"
+":Parameters:\n"
+" `path` : str\n"
+" Object path to be acted on\n"
+" `on_message` : callable\n"
+" Called when a message arrives at the given object-path, with\n"
+" two positional parameters: the first is this Connection,\n"
+" the second is the incoming `dbus.lowlevel.Message`.\n"
+" `on_unregister` : callable or None\n"
+" If not None, called when the callback is unregistered.\n"
+" `fallback` : bool\n"
+" If True (the default is False), when a message arrives for a\n"
+" 'subdirectory' of the given path and there is no more specific\n"
+" handler, use this handler. Normally this handler is only run if\n"
+" the paths match exactly.\n"
+);
+static PyObject *
+Connection__register_object_path(Connection *self, PyObject *args,
+ PyObject *kwargs)
+{
+ dbus_bool_t ok;
+ int fallback = 0;
+ char *path_bytes;
+ PyObject *callbacks, *path, *tuple, *on_message, *on_unregister = Py_None;
+ static char *argnames[] = {"path", "on_message", "on_unregister",
+ "fallback", NULL};
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ if (!Connection__require_main_loop(self, NULL)) {
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "OO|Oi:_register_object_path",
+ argnames,
+ &path,
+ &on_message, &on_unregister,
+ &fallback)) return NULL;
+
+ /* Take a reference to path, which we give away to libdbus in a moment.
+
+ Also, path needs to be a string (not a subclass which could do something
+ mad) to preserve the desirable property that the DBusConnection can
+ never strongly reference the Connection, even indirectly.
+ */
+ if (PyBytes_CheckExact(path)) {
+ Py_INCREF(path);
+ }
+ else if (PyUnicode_Check(path)) {
+ path = PyUnicode_AsUTF8String(path);
+ if (!path) return NULL;
+ }
+ else if (PyBytes_Check(path)) {
+ path = PyBytes_FromString(PyBytes_AS_STRING(path));
+ if (!path) return NULL;
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ "path must be a str, bytes, or unicode object");
+ return NULL;
+ }
+
+ path_bytes = PyBytes_AS_STRING(path);
+ if (!dbus_py_validate_object_path(path_bytes)) {
+ Py_CLEAR(path);
+ return NULL;
+ }
+
+ tuple = Py_BuildValue("(OO)", on_unregister, on_message);
+ if (!tuple) {
+ Py_CLEAR(path);
+ return NULL;
+ }
+
+ /* Guard against registering a handler that already exists. */
+ callbacks = PyDict_GetItem(self->object_paths, path);
+ if (callbacks && callbacks != Py_None) {
+ PyErr_Format(PyExc_KeyError, "Can't register the object-path "
+ "handler for '%s': there is already a handler",
+ path_bytes);
+ Py_CLEAR(tuple);
+ Py_CLEAR(path);
+ return NULL;
+ }
+
+ /* Pre-allocate a slot in the dictionary, so we know we'll be able
+ * to replace it with the callbacks without OOM.
+ * This ensures we can keep libdbus' opinion of whether those
+ * paths are handled in sync with our own. */
+ if (PyDict_SetItem(self->object_paths, path, Py_None) < 0) {
+ Py_CLEAR(tuple);
+ Py_CLEAR(path);
+ return NULL;
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ if (fallback) {
+ ok = dbus_connection_register_fallback(self->conn,
+ path_bytes,
+ &_object_path_vtable,
+ path);
+ }
+ else {
+ ok = dbus_connection_register_object_path(self->conn,
+ path_bytes,
+ &_object_path_vtable,
+ path);
+ }
+ Py_END_ALLOW_THREADS
+
+ if (ok) {
+ if (PyDict_SetItem(self->object_paths, path, tuple) < 0) {
+ /* That shouldn't have happened, we already allocated enough
+ memory for it. Oh well, try to undo the registration to keep
+ things in sync. If this fails too, we've leaked a bit of
+ memory in libdbus, but tbh we should never get here anyway. */
+ Py_BEGIN_ALLOW_THREADS
+ ok = dbus_connection_unregister_object_path(self->conn,
+ path_bytes);
+ Py_END_ALLOW_THREADS
+ return NULL;
+ }
+ /* don't DECREF path: libdbus owns a ref now */
+ Py_CLEAR(tuple);
+ Py_RETURN_NONE;
+ }
+ else {
+ /* Oops, OOM. Tidy up, if we can, ignoring any error. */
+ PyDict_DelItem(self->object_paths, path);
+ PyErr_Clear();
+ Py_CLEAR(tuple);
+ Py_CLEAR(path);
+ PyErr_NoMemory();
+ return NULL;
+ }
+}
+
+PyDoc_STRVAR(Connection__unregister_object_path__doc__,
+"unregister_object_path(path)\n\n"
+"Remove a previously registered handler for the given object path.\n"
+"\n"
+":Parameters:\n"
+" `path` : str\n"
+" The object path whose handler is to be removed\n"
+":Raises KeyError: if there is no handler registered for exactly that\n"
+" object path.\n"
+);
+static PyObject *
+Connection__unregister_object_path(Connection *self, PyObject *args,
+ PyObject *kwargs)
+{
+ dbus_bool_t ok;
+ char *path_bytes;
+ PyObject *path;
+ PyObject *callbacks;
+ static char *argnames[] = {"path", NULL};
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "O:_unregister_object_path",
+ argnames, &path)) return NULL;
+
+ /* Take a ref to the path. Same comments as for _register_object_path. */
+ if (PyBytes_CheckExact(path)) {
+ Py_INCREF(path);
+ }
+ else if (PyUnicode_Check(path)) {
+ path = PyUnicode_AsUTF8String(path);
+ if (!path) return NULL;
+ }
+ else if (PyBytes_Check(path)) {
+ path = PyBytes_FromString(PyBytes_AS_STRING(path));
+ if (!path) return NULL;
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ "path must be a str, bytes, or unicode object");
+ return NULL;
+ }
+
+ path_bytes = PyBytes_AS_STRING(path);
+
+ /* Guard against unregistering a handler that doesn't, in fact, exist,
+ or whose unregistration is already in progress. */
+ callbacks = PyDict_GetItem(self->object_paths, path);
+ if (!callbacks || callbacks == Py_None) {
+ PyErr_Format(PyExc_KeyError, "Can't unregister the object-path "
+ "handler for '%s': there is no such handler",
+ path_bytes);
+ Py_CLEAR(path);
+ return NULL;
+ }
+
+ /* Hang on to a reference to the callbacks for the moment. */
+ Py_INCREF(callbacks);
+
+ /* Get rid of the object-path while we still have the GIL, to
+ guard against unregistering twice from different threads (which
+ causes undefined behaviour in libdbus).
+
+ Because deletion would make it possible for the re-insertion below
+ to fail, we instead set the handler to None as a placeholder.
+ */
+ if (PyDict_SetItem(self->object_paths, path, Py_None) < 0) {
+ /* If that failed, there's no need to be paranoid as below - the
+ callbacks are still set, so we failed, but at least everything
+ is in sync. */
+ Py_CLEAR(callbacks);
+ Py_CLEAR(path);
+ return NULL;
+ }
+
+ /* BEGIN PARANOIA
+ This is something of a critical section - the dict of object-paths
+ and libdbus' internal structures are out of sync for a bit. We have
+ to be able to cope with that.
+
+ It's really annoying that dbus_connection_unregister_object_path
+ can fail, *and* has undefined behaviour if the object path has
+ already been unregistered. Either/or would be fine.
+ */
+
+ Py_BEGIN_ALLOW_THREADS
+ ok = dbus_connection_unregister_object_path(self->conn, path_bytes);
+ Py_END_ALLOW_THREADS
+
+ if (ok) {
+ Py_CLEAR(callbacks);
+ PyDict_DelItem(self->object_paths, path);
+ /* END PARANOIA on successful code path */
+ /* The above can't fail unless by some strange trickery the key is no
+ longer present. Ignore any errors. */
+ Py_CLEAR(path);
+ PyErr_Clear();
+ Py_RETURN_NONE;
+ }
+ else {
+ /* Oops, OOM. Put the callbacks back in the dict so
+ * we'll have another go if/when the user frees some memory
+ * and tries calling this method again. */
+ PyDict_SetItem(self->object_paths, path, callbacks);
+ /* END PARANOIA on failing code path */
+ /* If the SetItem failed, there's nothing we can do about it - but
+ since we know it's an existing entry, it shouldn't be able to fail
+ anyway. */
+ Py_CLEAR(path);
+ Py_CLEAR(callbacks);
+ return PyErr_NoMemory();
+ }
+}
+
+PyDoc_STRVAR(Connection_list_exported_child_objects__doc__,
+"list_exported_child_objects(path: str) -> list of str\n\n"
+"Return a list of the names of objects exported on this Connection as\n"
+"direct children of the given object path.\n"
+"\n"
+"Each name returned may be converted to a valid object path using\n"
+"``dbus.ObjectPath('%s%s%s' % (path, (path != '/' and '/' or ''), name))``.\n"
+"For the purposes of this function, every parent or ancestor of an exported\n"
+"object is considered to be an exported object, even if it's only an object\n"
+"synthesized by the library to support introspection.\n");
+static PyObject *
+Connection_list_exported_child_objects (Connection *self, PyObject *args,
+ PyObject *kwargs)
+{
+ const char *path;
+ char **kids, **kid_ptr;
+ dbus_bool_t ok;
+ PyObject *ret;
+ static char *argnames[] = {"path", NULL};
+
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->conn);
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", argnames, &path)) {
+ return NULL;
+ }
+
+ if (!dbus_py_validate_object_path(path)) {
+ return NULL;
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ ok = dbus_connection_list_registered(self->conn, path, &kids);
+ Py_END_ALLOW_THREADS
+
+ if (!ok) {
+ return PyErr_NoMemory();
+ }
+
+ ret = PyList_New(0);
+ if (!ret) {
+ return NULL;
+ }
+ for (kid_ptr = kids; *kid_ptr; kid_ptr++) {
+ PyObject *tmp = PyUnicode_FromString(*kid_ptr);
+
+ if (!tmp) {
+ Py_CLEAR(ret);
+ return NULL;
+ }
+ if (PyList_Append(ret, tmp) < 0) {
+ Py_CLEAR(tmp);
+ Py_CLEAR(ret);
+ return NULL;
+ }
+ Py_CLEAR(tmp);
+ }
+
+ dbus_free_string_array(kids);
+
+ return ret;
+}
+
+ /* dbus_connection_get_object_path_data - not useful to Python,
+ * the object path data is just a PyBytes containing the path */
+ /* dbus_connection_list_registered could be useful, though */
+
+/* dbus_connection_set_change_sigpipe - sets global state */
+
+/* Maxima. Does Python code ever need to manipulate these?
+ * OTOH they're easy to wrap */
+ /* dbus_connection_set_max_message_size */
+ /* dbus_connection_get_max_message_size */
+ /* dbus_connection_set_max_received_size */
+ /* dbus_connection_get_max_received_size */
+
+/* dbus_connection_get_outgoing_size - almost certainly unneeded */
+
+PyDoc_STRVAR(new_for_bus__doc__,
+"Connection._new_for_bus([address: str or int]) -> Connection\n"
+"\n"
+"If the address is an int it must be one of the constants BUS_SESSION,\n"
+"BUS_SYSTEM, BUS_STARTER; if a string, it must be a D-Bus address.\n"
+"The default is BUS_SESSION.\n"
+);
+
+PyDoc_STRVAR(get_unique_name__doc__,
+"get_unique_name() -> str\n\n"
+"Return this application's unique name on this bus.\n"
+"\n"
+":Raises DBusException: if the connection has no unique name yet\n"
+" (for Bus objects this can't happen, for peer-to-peer connections\n"
+" this means you haven't called `set_unique_name`)\n");
+
+PyDoc_STRVAR(set_unique_name__doc__,
+"set_unique_name(str)\n\n"
+"Set this application's unique name on this bus. Raise ValueError if it has\n"
+"already been set.\n");
+
+struct PyMethodDef DBusPyConnection_tp_methods[] = {
+#define ENTRY(name, flags) {\
+ #name, (PyCFunction) (void (*)(void)) Connection_##name, \
+ flags, Connection_##name##__doc__ \
+}
+ ENTRY(_require_main_loop, METH_NOARGS),
+ ENTRY(close, METH_NOARGS),
+ ENTRY(flush, METH_NOARGS),
+ ENTRY(get_is_connected, METH_NOARGS),
+ ENTRY(get_is_authenticated, METH_NOARGS),
+ ENTRY(set_exit_on_disconnect, METH_VARARGS),
+ ENTRY(get_unix_fd, METH_NOARGS),
+ ENTRY(get_peer_unix_user, METH_NOARGS),
+ ENTRY(get_peer_unix_process_id, METH_NOARGS),
+ ENTRY(add_message_filter, METH_O),
+ ENTRY(_register_object_path, METH_VARARGS|METH_KEYWORDS),
+ ENTRY(remove_message_filter, METH_O),
+ ENTRY(send_message, METH_VARARGS),
+ ENTRY(send_message_with_reply, METH_VARARGS|METH_KEYWORDS),
+ ENTRY(send_message_with_reply_and_block, METH_VARARGS),
+ ENTRY(_unregister_object_path, METH_VARARGS|METH_KEYWORDS),
+ ENTRY(list_exported_child_objects, METH_VARARGS|METH_KEYWORDS),
+ {"_new_for_bus", (PyCFunction) (void (*)(void)) DBusPyConnection_NewForBus,
+ METH_CLASS|METH_VARARGS|METH_KEYWORDS,
+ new_for_bus__doc__},
+ {"get_unique_name", (PyCFunction) (void (*)(void)) DBusPyConnection_GetUniqueName,
+ METH_NOARGS,
+ get_unique_name__doc__},
+ {"set_unique_name", (PyCFunction) (void (*)(void)) DBusPyConnection_SetUniqueName,
+ METH_VARARGS,
+ set_unique_name__doc__},
+ ENTRY(set_allow_anonymous, METH_VARARGS),
+ {NULL},
+#undef ENTRY
+};
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/conn.c b/dbus_bindings/conn.c
new file mode 100644
index 0000000..59c3b36
--- /dev/null
+++ b/dbus_bindings/conn.c
@@ -0,0 +1,498 @@
+/* Implementation of the _dbus_bindings Connection type, a Python wrapper
+ * for DBusConnection. See also conn-methods.c.
+ *
+ * Copyright (C) 2006-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include "conn-internal.h"
+
+/* Connection definition ============================================ */
+
+PyDoc_STRVAR(Connection_tp_doc,
+"_dbus_bindings.Connection(address, mainloop=None)\n"
+"\n"
+"A D-Bus connection.\n"
+);
+
+/* D-Bus Connection user data slot, containing an owned reference to either
+ * the Connection, or a weakref to the Connection.
+ */
+static dbus_int32_t _connection_python_slot;
+
+/* C API for main-loop hooks ======================================== */
+
+/* Return a borrowed reference to the DBusConnection which underlies this
+ * Connection. */
+DBusConnection *
+DBusPyConnection_BorrowDBusConnection(PyObject *self)
+{
+ DBusConnection *dbc;
+
+ TRACE(self);
+ if (!DBusPyConnection_Check(self)) {
+ PyErr_SetString(PyExc_TypeError, "A dbus.Connection is required");
+ return NULL;
+ }
+ dbc = ((Connection *)self)->conn;
+ if (!dbc) {
+ PyErr_SetString(PyExc_RuntimeError, "Connection is in an invalid "
+ "state: no DBusConnection");
+ return NULL;
+ }
+ return dbc;
+}
+
+/* Internal C API =================================================== */
+
+/* Pass a message through a handler. */
+DBusHandlerResult
+DBusPyConnection_HandleMessage(Connection *conn,
+ PyObject *msg,
+ PyObject *callable)
+{
+ PyObject *obj;
+
+ TRACE(conn);
+ obj = PyObject_CallFunctionObjArgs(callable, conn, msg,
+ NULL);
+ if (obj == Py_None) {
+ DBG("%p: OK, handler %p returned None", conn, callable);
+ Py_CLEAR(obj);
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ else if (obj == Py_NotImplemented) {
+ DBG("%p: handler %p returned NotImplemented, continuing",
+ conn, callable);
+ Py_CLEAR(obj);
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ else if (!obj) {
+ if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
+ DBG_EXC("%p: handler %p caused OOM", conn, callable);
+ PyErr_Clear();
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ }
+ DBG_EXC("%p: handler %p raised exception", conn, callable);
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ else {
+ long i = PyLong_AsLong(obj);
+ DBG("%p: handler %p returned %ld", conn, callable, i);
+ Py_CLEAR(obj);
+ if (i == -1 && PyErr_Occurred()) {
+ PyErr_SetString(PyExc_TypeError, "Return from D-Bus message "
+ "handler callback should be None, "
+ "NotImplemented or integer");
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ else if (i == DBUS_HANDLER_RESULT_HANDLED ||
+ i == DBUS_HANDLER_RESULT_NOT_YET_HANDLED ||
+ i == DBUS_HANDLER_RESULT_NEED_MEMORY) {
+ return i;
+ }
+ else {
+ PyErr_Format(PyExc_ValueError, "Integer return from "
+ "D-Bus message handler callback should "
+ "be a DBUS_HANDLER_RESULT_... constant, "
+ "not %d", (int)i);
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ }
+}
+
+/* On KeyError or if unregistration is in progress, return None. */
+PyObject *
+DBusPyConnection_GetObjectPathHandlers(PyObject *self, PyObject *path)
+{
+ PyObject *callbacks;
+
+ TRACE(self);
+ callbacks = PyDict_GetItem(((Connection *)self)->object_paths, path);
+ if (!callbacks) {
+ if (PyErr_ExceptionMatches(PyExc_KeyError)) {
+ PyErr_Clear();
+ Py_RETURN_NONE;
+ }
+ }
+ Py_INCREF(callbacks);
+ return callbacks;
+}
+
+/* Return a new reference to a Python Connection or subclass corresponding
+ * to the DBusConnection conn. For use in callbacks.
+ *
+ * Raises AssertionError if the DBusConnection does not have a Connection.
+ */
+PyObject *
+DBusPyConnection_ExistingFromDBusConnection(DBusConnection *conn)
+{
+ PyObject *self, *ref;
+
+ Py_BEGIN_ALLOW_THREADS
+ ref = (PyObject *)dbus_connection_get_data(conn,
+ _connection_python_slot);
+ Py_END_ALLOW_THREADS
+ if (ref) {
+ DBG("(DBusConnection *)%p has weak reference at %p", conn, ref);
+ self = PyWeakref_GetObject(ref); /* still a borrowed ref */
+ if (self && self != Py_None && DBusPyConnection_Check(self)) {
+ DBG("(DBusConnection *)%p has weak reference at %p pointing to %p",
+ conn, ref, self);
+ TRACE(self);
+ Py_INCREF(self);
+ TRACE(self);
+ return self;
+ }
+ }
+
+ PyErr_SetString(PyExc_AssertionError,
+ "D-Bus connection does not have a Connection "
+ "instance associated with it");
+ return NULL;
+}
+
+/* Return a new reference to a Python Connection or subclass (given by cls)
+ * corresponding to the DBusConnection conn, which must have been newly
+ * created. For use by the Connection and Bus constructors.
+ *
+ * Raises AssertionError if the DBusConnection already has a Connection.
+ */
+static PyObject *
+DBusPyConnection_NewConsumingDBusConnection(PyTypeObject *cls,
+ DBusConnection *conn,
+ PyObject *mainloop)
+{
+ Connection *self = NULL;
+ PyObject *ref;
+ dbus_bool_t ok;
+
+ DBG("%s(cls=%p, conn=%p, mainloop=%p)", __func__, cls, conn, mainloop);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(conn);
+
+ Py_BEGIN_ALLOW_THREADS
+ ref = (PyObject *)dbus_connection_get_data(conn,
+ _connection_python_slot);
+ Py_END_ALLOW_THREADS
+ if (ref) {
+ self = (Connection *)PyWeakref_GetObject(ref);
+ ref = NULL;
+ if (self && (PyObject *)self != Py_None) {
+ self = NULL;
+ PyErr_SetString(PyExc_AssertionError,
+ "Newly created D-Bus connection already has a "
+ "Connection instance associated with it");
+ DBG("%s() fail - assertion failed, DBusPyConn has a DBusConn already", __func__);
+ DBG_WHEREAMI;
+ return NULL;
+ }
+ }
+ ref = NULL;
+
+ /* Change mainloop from a borrowed reference to an owned reference */
+ if (!mainloop || mainloop == Py_None) {
+ mainloop = dbus_py_get_default_main_loop();
+ if (!mainloop)
+ goto err;
+ }
+ else {
+ Py_INCREF(mainloop);
+ }
+
+ DBG("Constructing Connection from DBusConnection at %p", conn);
+
+ self = (Connection *)(cls->tp_alloc(cls, 0));
+ if (!self) goto err;
+ TRACE(self);
+
+ DBG_WHEREAMI;
+
+ self->has_mainloop = (mainloop != Py_None);
+ self->conn = NULL;
+ self->filters = PyList_New(0);
+ self->weaklist = NULL;
+ if (!self->filters) goto err;
+ self->object_paths = PyDict_New();
+ if (!self->object_paths) goto err;
+
+ ref = PyWeakref_NewRef((PyObject *)self, NULL);
+ if (!ref) goto err;
+ DBG("Created weak ref %p to (Connection *)%p for (DBusConnection *)%p",
+ ref, self, conn);
+
+ Py_BEGIN_ALLOW_THREADS
+ ok = dbus_connection_set_data(conn, _connection_python_slot,
+ (void *)ref,
+ (DBusFreeFunction)dbus_py_take_gil_and_xdecref);
+ Py_END_ALLOW_THREADS
+
+ if (ok) {
+ DBG("Attached weak ref %p ((Connection *)%p) to (DBusConnection *)%p",
+ ref, self, conn);
+ ref = NULL; /* don't DECREF it - the DBusConnection owns it now */
+ }
+ else {
+ DBG("Failed to attached weak ref %p ((Connection *)%p) to "
+ "(DBusConnection *)%p - will dispose of it", ref, self, conn);
+ PyErr_NoMemory();
+ goto err;
+ }
+
+ DBUS_PY_RAISE_VIA_GOTO_IF_FAIL(conn, err);
+ self->conn = conn;
+ /* the DBusPyConnection will close it now */
+ conn = NULL;
+
+ if (self->has_mainloop
+ && !dbus_py_set_up_connection((PyObject *)self, mainloop)) {
+ goto err;
+ }
+
+ Py_CLEAR(mainloop);
+
+ DBG("%s() -> %p", __func__, self);
+ TRACE(self);
+ return (PyObject *)self;
+
+err:
+ DBG("Failed to construct Connection from DBusConnection at %p", conn);
+ Py_CLEAR(mainloop);
+ Py_CLEAR(self);
+ Py_CLEAR(ref);
+ if (conn) {
+ Py_BEGIN_ALLOW_THREADS
+ dbus_connection_close(conn);
+ dbus_connection_unref(conn);
+ Py_END_ALLOW_THREADS
+ }
+ DBG("%s() fail", __func__);
+ DBG_WHEREAMI;
+ return NULL;
+}
+
+/* Connection type-methods ========================================== */
+
+/* Constructor */
+static PyObject *
+Connection_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ DBusConnection *conn;
+ PyObject *address_or_conn;
+ DBusError error;
+ PyObject *self, *mainloop = NULL;
+ static char *argnames[] = {"address", "mainloop", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", argnames,
+ &address_or_conn, &mainloop)) {
+ return NULL;
+ }
+
+ if (DBusPyLibDBusConnection_CheckExact(address_or_conn)) {
+ DBusPyLibDBusConnection *wrapper =
+ (DBusPyLibDBusConnection *) address_or_conn;
+
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(wrapper->conn);
+
+ conn = dbus_connection_ref (wrapper->conn);
+ }
+ else if (PyBytes_Check(address_or_conn)) {
+ const char *address = PyBytes_AS_STRING(address_or_conn);
+
+ dbus_error_init(&error);
+
+ /* We always open a private connection (at the libdbus level). Sharing
+ * is done in Python, to keep things simple. */
+ Py_BEGIN_ALLOW_THREADS
+ conn = dbus_connection_open_private(address, &error);
+ Py_END_ALLOW_THREADS
+
+ if (!conn) {
+ DBusPyException_ConsumeError(&error);
+ return NULL;
+ }
+ }
+ else if (PyUnicode_Check(address_or_conn)) {
+ PyObject *address_as_bytes = PyUnicode_AsUTF8String(address_or_conn);
+ const char *address;
+
+ if (!address_as_bytes)
+ return NULL;
+ address = PyBytes_AS_STRING(address_as_bytes);
+
+ dbus_error_init(&error);
+
+ /* We always open a private connection (at the libdbus level). Sharing
+ * is done in Python, to keep things simple. */
+ Py_BEGIN_ALLOW_THREADS
+ conn = dbus_connection_open_private(address, &error);
+ Py_END_ALLOW_THREADS
+
+ Py_CLEAR(address_as_bytes);
+ if (!conn) {
+ DBusPyException_ConsumeError(&error);
+ return NULL;
+ }
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError, "connection or str expected");
+ return NULL;
+ }
+
+ self = DBusPyConnection_NewConsumingDBusConnection(cls, conn, mainloop);
+ TRACE(self);
+
+ return self;
+}
+
+/* Post-construction: nothing to do (but don't chain up to object.__init__,
+ * which takes no arguments and does nothing) */
+static int
+Connection_tp_init(PyObject *self UNUSED, PyObject *args UNUSED,
+ PyObject *kwargs UNUSED)
+{
+ return 0;
+}
+
+/* Destructor */
+static void Connection_tp_dealloc(Connection *self)
+{
+ DBusConnection *conn = self->conn;
+ PyObject *et, *ev, *etb;
+ PyObject *filters = self->filters;
+ PyObject *object_paths = self->object_paths;
+
+ /* avoid clobbering any pending exception */
+ PyErr_Fetch(&et, &ev, &etb);
+
+ if (self->weaklist) {
+ PyObject_ClearWeakRefs((PyObject *)self);
+ }
+
+ TRACE(self);
+ DBG("Deallocating Connection at %p (DBusConnection at %p)", self, conn);
+ DBG_WHEREAMI;
+
+ DBG("Connection at %p: deleting callbacks", self);
+ self->filters = NULL;
+ Py_CLEAR(filters);
+ self->object_paths = NULL;
+ Py_CLEAR(object_paths);
+
+ if (conn) {
+ /* Might trigger callbacks if we're unlucky... */
+ DBG("Connection at %p has a conn, closing it...", self);
+ Py_BEGIN_ALLOW_THREADS
+ dbus_connection_close(conn);
+ Py_END_ALLOW_THREADS
+ }
+
+ /* make sure to do this last to preserve the invariant that
+ * self->conn is always non-NULL for any referenced Connection
+ * (until the filters and object paths were freed, we might have been
+ * in a reference cycle!)
+ */
+ DBG("Connection at %p: nulling self->conn", self);
+ self->conn = NULL;
+
+ if (conn) {
+ DBG("Connection at %p: unreffing conn", self);
+ dbus_connection_unref(conn);
+ }
+
+ DBG("Connection at %p: freeing self", self);
+ PyErr_Restore(et, ev, etb);
+ (Py_TYPE(self)->tp_free)((PyObject *)self);
+}
+
+/* Connection type object =========================================== */
+
+PyTypeObject DBusPyConnection_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "_dbus_bindings.Connection", /*tp_name*/
+ sizeof(Connection), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)Connection_tp_dealloc,
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ Connection_tp_doc, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ offsetof(Connection, weaklist), /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ DBusPyConnection_tp_methods, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ Connection_tp_init, /*tp_init*/
+ 0, /*tp_alloc*/
+ Connection_tp_new, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+};
+
+dbus_bool_t
+dbus_py_init_conn_types(void)
+{
+ /* Get a slot to store our weakref on DBus Connections */
+ _connection_python_slot = -1;
+ if (!dbus_connection_allocate_data_slot(&_connection_python_slot))
+ return FALSE;
+ if (PyType_Ready(&DBusPyConnection_Type) < 0)
+ return FALSE;
+ return TRUE;
+}
+
+dbus_bool_t
+dbus_py_insert_conn_types(PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF (&DBusPyConnection_Type);
+ if (PyModule_AddObject(this_module, "Connection",
+ (PyObject *)&DBusPyConnection_Type) < 0) return FALSE;
+ return TRUE;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/containers.c b/dbus_bindings/containers.c
new file mode 100644
index 0000000..1477752
--- /dev/null
+++ b/dbus_bindings/containers.c
@@ -0,0 +1,771 @@
+/* D-Bus container types: Array, Dict and Struct.
+ *
+ * Copyright (C) 2006-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "types-internal.h"
+
+/* Array ============================================================ */
+
+PyDoc_STRVAR(Array_tp_doc,
+"dbus.Array([iterable][, signature][, variant_level])\n"
+"\n"
+"An array of similar items, implemented as a subtype of list.\n"
+"\n"
+"As currently implemented, an Array behaves just like a list, but\n"
+"with the addition of a ``signature`` property set by the constructor;\n"
+"conversion of its items to D-Bus types is only done when it's sent in\n"
+"a Message. This might change in future so validation is done earlier.\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+"``signature`` is the D-Bus signature string for a single element of the\n"
+"array, or None. If not None it must represent a single complete type, the\n"
+"type of a single array item; the signature of the whole Array may be\n"
+"obtained by prepending ``a`` to the given signature.\n"
+"\n"
+"If None (the default), when the Array is sent over\n"
+"D-Bus, the item signature will be guessed from the first element.\n"
+);
+
+static struct PyMemberDef Array_tp_members[] = {
+ {"signature", T_OBJECT, offsetof(DBusPyArray, signature), READONLY,
+ "The D-Bus signature of each element of this Array (a Signature "
+ "instance)"},
+ {"variant_level", T_LONG, offsetof(DBusPyArray, variant_level),
+ READONLY,
+ "Indicates how many nested Variant containers this object\n"
+ "is contained in: if a message's wire format has a variant containing a\n"
+ "variant containing an array, this is represented in Python by an\n"
+ "Array with variant_level==2.\n"
+ },
+ {NULL},
+};
+
+static void
+Array_tp_dealloc (DBusPyArray *self)
+{
+ Py_CLEAR(self->signature);
+ (PyList_Type.tp_dealloc)((PyObject *)self);
+}
+
+static PyObject *
+Array_tp_repr(DBusPyArray *self)
+{
+ PyObject *parent_repr = (PyList_Type.tp_repr)((PyObject *)self);
+ PyObject *sig_repr = PyObject_Repr(self->signature);
+ PyObject *my_repr = NULL;
+ long variant_level = self->variant_level;
+
+ if (!parent_repr) goto finally;
+ if (!sig_repr) goto finally;
+ if (variant_level > 0) {
+ my_repr = PyUnicode_FromFormat("%s(%V, signature=%V, "
+ "variant_level=%ld)",
+ Py_TYPE(&self->super)->tp_name,
+ REPRV(parent_repr),
+ REPRV(sig_repr),
+ variant_level);
+ }
+ else {
+ my_repr = PyUnicode_FromFormat("%s(%V, signature=%V)",
+ Py_TYPE(&self->super)->tp_name,
+ REPRV(parent_repr),
+ REPRV(sig_repr));
+ }
+finally:
+ Py_CLEAR(parent_repr);
+ Py_CLEAR(sig_repr);
+ return my_repr;
+}
+
+static PyObject *
+Array_tp_new (PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *variant_level = NULL;
+ DBusPyArray *self = (DBusPyArray *)(PyList_Type.tp_new)(cls, args, kwargs);
+
+ /* variant_level is immutable, so handle it in __new__ rather than
+ __init__ */
+ if (!self) return NULL;
+ Py_INCREF(Py_None);
+ self->signature = Py_None;
+ self->variant_level = 0;
+ if (kwargs) {
+ variant_level = PyDict_GetItem(kwargs, dbus_py_variant_level_const);
+ }
+ if (variant_level) {
+ long new_variant_level = PyLong_AsLong(variant_level);
+ if (new_variant_level == -1 && PyErr_Occurred()) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+ self->variant_level = new_variant_level;
+ }
+ return (PyObject *)self;
+}
+
+static int
+Array_tp_init (DBusPyArray *self, PyObject *args, PyObject *kwargs)
+{
+ PyObject *obj = dbus_py_empty_tuple;
+ PyObject *signature = NULL;
+ PyObject *tuple;
+ PyObject *variant_level;
+ /* variant_level is accepted but ignored - it's immutable, so
+ * __new__ handles it */
+ static char *argnames[] = {"iterable", "signature", "variant_level", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOO:__init__", argnames,
+ &obj, &signature, &variant_level)) {
+ return -1;
+ }
+
+ /* convert signature from a borrowed ref of unknown type to an owned ref
+ of type Signature (or None) */
+ if (!signature) signature = Py_None;
+ if (signature == Py_None
+ || PyObject_IsInstance(signature, (PyObject *)&DBusPySignature_Type)) {
+ Py_INCREF(signature);
+ }
+ else {
+ signature = PyObject_CallFunction((PyObject *)&DBusPySignature_Type,
+ "(O)", signature);
+ if (!signature) return -1;
+ }
+
+ if (signature != Py_None) {
+ const char *c_str;
+ PyObject *signature_as_bytes;
+
+ if (!PyUnicode_Check(signature))
+ {
+ PyErr_SetString(PyExc_TypeError, "str expected");
+ Py_CLEAR(signature);
+ return -1;
+ }
+ if (!(signature_as_bytes = PyUnicode_AsUTF8String(signature))) {
+ Py_CLEAR(signature);
+ return -1;
+ }
+
+ c_str = PyBytes_AS_STRING(signature_as_bytes);
+
+ if (!dbus_signature_validate_single(c_str, NULL)) {
+ Py_CLEAR(signature);
+ Py_CLEAR(signature_as_bytes);
+ PyErr_SetString(PyExc_ValueError,
+ "There must be exactly one complete type in "
+ "an Array's signature parameter");
+ return -1;
+ }
+ Py_CLEAR(signature_as_bytes);
+ }
+
+ tuple = Py_BuildValue("(O)", obj);
+ if (!tuple) {
+ Py_CLEAR(signature);
+ return -1;
+ }
+ if ((PyList_Type.tp_init)((PyObject *)self, tuple, NULL) < 0) {
+ Py_CLEAR(tuple);
+ Py_CLEAR(signature);
+ return -1;
+ }
+ Py_CLEAR(tuple);
+
+ Py_CLEAR(self->signature);
+ self->signature = signature;
+ return 0;
+}
+
+PyTypeObject DBusPyArray_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.Array",
+ sizeof(DBusPyArray),
+ 0,
+ (destructor)Array_tp_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ (reprfunc)Array_tp_repr, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ Array_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ Array_tp_members, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)Array_tp_init, /* tp_init */
+ 0, /* tp_alloc */
+ Array_tp_new, /* tp_new */
+};
+
+/* Dict ============================================================= */
+
+PyDoc_STRVAR(Dict_tp_doc,
+"dbus.Dictionary(mapping_or_iterable=(), signature=None, variant_level=0)\n"
+"\n"
+"An mapping whose keys are similar and whose values are similar,\n"
+"implemented as a subtype of dict.\n"
+"\n"
+"As currently implemented, a Dictionary behaves just like a dict, but\n"
+"with the addition of a ``signature`` property set by the constructor;\n"
+"conversion of its items to D-Bus types is only done when it's sent in\n"
+"a Message. This may change in future so validation is done earlier.\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+"``signature`` is either a string or None. If a string, it must consist\n"
+"of exactly two complete type signatures, representing the 'key' type\n"
+"(which must be a primitive type, i.e. one of \"bdginoqstuxy\")\n"
+"and the 'value' type. The signature of the whole Dictionary will be\n"
+"``a{xx}`` where ``xx`` is replaced by the given signature.\n"
+"\n"
+"If it is None (the default), when the Dictionary is sent over\n"
+"D-Bus, the key and value signatures will be guessed from an arbitrary\n"
+"element of the Dictionary.\n"
+);
+
+static struct PyMemberDef Dict_tp_members[] = {
+ {"signature", T_OBJECT, offsetof(DBusPyDict, signature), READONLY,
+ "The D-Bus signature of each key in this Dictionary, followed by "
+ "that of each value in this Dictionary, as a Signature instance."},
+ {"variant_level", T_LONG, offsetof(DBusPyDict, variant_level),
+ READONLY,
+ "Indicates how many nested Variant containers this object\n"
+ "is contained in: if a message's wire format has a variant containing a\n"
+ "variant containing a dictionary, this is represented in Python by a\n"
+ "Dictionary with variant_level==2.\n"
+ },
+ {NULL},
+};
+
+static void
+Dict_tp_dealloc (DBusPyDict *self)
+{
+ Py_CLEAR(self->signature);
+ (PyDict_Type.tp_dealloc)((PyObject *)self);
+}
+
+static PyObject *
+Dict_tp_repr(DBusPyDict *self)
+{
+ PyObject *parent_repr = (PyDict_Type.tp_repr)((PyObject *)self);
+ PyObject *sig_repr = PyObject_Repr(self->signature);
+ PyObject *my_repr = NULL;
+ long variant_level = self->variant_level;
+
+ if (!parent_repr) goto finally;
+ if (!sig_repr) goto finally;
+ if (variant_level > 0) {
+ my_repr = PyUnicode_FromFormat("%s(%V, signature=%V, "
+ "variant_level=%ld)",
+ Py_TYPE(&self->super)->tp_name,
+ REPRV(parent_repr),
+ REPRV(sig_repr),
+ variant_level);
+ }
+ else {
+ my_repr = PyUnicode_FromFormat("%s(%V, signature=%V)",
+ Py_TYPE(&self->super)->tp_name,
+ REPRV(parent_repr),
+ REPRV(sig_repr));
+ }
+finally:
+ Py_CLEAR(parent_repr);
+ Py_CLEAR(sig_repr);
+ return my_repr;
+}
+
+static PyObject *
+Dict_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ DBusPyDict *self = (DBusPyDict *)(PyDict_Type.tp_new)(cls, args, kwargs);
+ PyObject *variant_level = NULL;
+
+ /* variant_level is immutable, so handle it in __new__ rather than
+ __init__ */
+ if (!self) return NULL;
+ Py_INCREF(Py_None);
+ self->signature = Py_None;
+ self->variant_level = 0;
+ if (kwargs) {
+ variant_level = PyDict_GetItem(kwargs, dbus_py_variant_level_const);
+ }
+ if (variant_level) {
+ long new_variant_level = PyLong_AsLong(variant_level);
+
+ if (new_variant_level == -1 && PyErr_Occurred()) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+ self->variant_level = new_variant_level;
+ }
+ return (PyObject *)self;
+}
+
+static int
+Dict_tp_init(DBusPyDict *self, PyObject *args, PyObject *kwargs)
+{
+ PyObject *obj = dbus_py_empty_tuple;
+ PyObject *signature = NULL;
+ PyObject *tuple;
+ PyObject *variant_level; /* ignored here - __new__ uses it */
+ static char *argnames[] = {"mapping_or_iterable", "signature",
+ "variant_level", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOO:__init__", argnames,
+ &obj, &signature, &variant_level)) {
+ return -1;
+ }
+
+ /* convert signature from a borrowed ref of unknown type to an owned ref
+ of type Signature (or None) */
+ if (!signature) signature = Py_None;
+ if (signature == Py_None
+ || PyObject_IsInstance(signature, (PyObject *)&DBusPySignature_Type)) {
+ Py_INCREF(signature);
+ }
+ else {
+ signature = PyObject_CallFunction((PyObject *)&DBusPySignature_Type,
+ "(O)", signature);
+ if (!signature) return -1;
+ }
+
+ if (signature != Py_None) {
+ const char *c_str;
+ PyObject *signature_as_bytes;
+
+ if (!PyUnicode_Check(signature)) {
+ PyErr_SetString(PyExc_TypeError, "str expected");
+ Py_CLEAR(signature);
+ return -1;
+ }
+ if (!(signature_as_bytes = PyUnicode_AsUTF8String(signature))) {
+ Py_CLEAR(signature);
+ return -1;
+ }
+
+ c_str = PyBytes_AS_STRING(signature_as_bytes);
+ switch (c_str[0]) {
+ case DBUS_TYPE_BYTE:
+ case DBUS_TYPE_BOOLEAN:
+ case DBUS_TYPE_INT16:
+ case DBUS_TYPE_UINT16:
+ case DBUS_TYPE_INT32:
+ case DBUS_TYPE_UINT32:
+ case DBUS_TYPE_INT64:
+ case DBUS_TYPE_UINT64:
+ case DBUS_TYPE_DOUBLE:
+#ifdef WITH_DBUS_FLOAT32
+ case DBUS_TYPE_FLOAT:
+#endif
+#ifdef DBUS_TYPE_UNIX_FD
+ case DBUS_TYPE_UNIX_FD:
+#endif
+ case DBUS_TYPE_STRING:
+ case DBUS_TYPE_OBJECT_PATH:
+ case DBUS_TYPE_SIGNATURE:
+ break;
+ default:
+ Py_CLEAR(signature);
+ Py_CLEAR(signature_as_bytes);
+ PyErr_SetString(PyExc_ValueError,
+ "The key type in a Dictionary's signature "
+ "must be a primitive type");
+ return -1;
+ }
+
+ if (!dbus_signature_validate_single(c_str + 1, NULL)) {
+ Py_CLEAR(signature);
+ Py_CLEAR(signature_as_bytes);
+ PyErr_SetString(PyExc_ValueError,
+ "There must be exactly two complete types in "
+ "a Dictionary's signature parameter");
+ return -1;
+ }
+ Py_CLEAR(signature_as_bytes);
+ }
+
+ tuple = Py_BuildValue("(O)", obj);
+ if (!tuple) {
+ Py_CLEAR(signature);
+ return -1;
+ }
+
+ if ((PyDict_Type.tp_init((PyObject *)self, tuple, NULL)) < 0) {
+ Py_CLEAR(tuple);
+ Py_CLEAR(signature);
+ return -1;
+ }
+ Py_CLEAR(tuple);
+
+ Py_CLEAR(self->signature);
+ self->signature = signature;
+ return 0;
+}
+
+PyTypeObject DBusPyDict_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.Dictionary",
+ sizeof(DBusPyDict),
+ 0,
+ (destructor)Dict_tp_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ (reprfunc)Dict_tp_repr, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ Dict_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ Dict_tp_members, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)Dict_tp_init, /* tp_init */
+ 0, /* tp_alloc */
+ Dict_tp_new, /* tp_new */
+};
+
+/* Struct =========================================================== */
+
+static PyObject *struct_signatures;
+
+PyDoc_STRVAR(Struct_tp_doc,
+"dbus.Struct(iterable, signature=None, variant_level=0)\n"
+"\n"
+"An structure containing items of possibly distinct types.\n"
+"\n"
+"D-Bus structs may not be empty, so the iterable argument is required and\n"
+"may not be an empty iterable.\n"
+"\n"
+"``signature`` is either None, or a string representing the contents of the\n"
+"struct as one or more complete type signatures. The overall signature of\n"
+"the struct will be the given signature enclosed in parentheses, ``()``.\n"
+"\n"
+"If the signature is None (default) it will be guessed\n"
+"from the types of the items during construction.\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing a struct, this is represented in Python by a\n"
+" Struct with variant_level==2.\n"
+);
+
+static PyObject *
+Struct_tp_repr(PyObject *self)
+{
+ PyObject *parent_repr = (PyTuple_Type.tp_repr)((PyObject *)self);
+ PyObject *sig;
+ PyObject *sig_repr = NULL;
+ PyObject *key;
+ long variant_level;
+ PyObject *my_repr = NULL;
+
+ if (!parent_repr) goto finally;
+ key = PyLong_FromVoidPtr(self);
+ if (!key) goto finally;
+ sig = PyDict_GetItem(struct_signatures, key);
+ Py_CLEAR(key);
+ if (!sig) sig = Py_None;
+ sig_repr = PyObject_Repr(sig);
+ if (!sig_repr) goto finally;
+
+ variant_level = dbus_py_variant_level_get(self);
+ if (variant_level < 0)
+ goto finally;
+
+ if (variant_level > 0) {
+ my_repr = PyUnicode_FromFormat("%s(%V, signature=%V, "
+ "variant_level=%ld)",
+ Py_TYPE(self)->tp_name,
+ REPRV(parent_repr),
+ REPRV(sig_repr),
+ variant_level);
+ }
+ else {
+ my_repr = PyUnicode_FromFormat("%s(%V, signature=%V)",
+ Py_TYPE(self)->tp_name,
+ REPRV(parent_repr),
+ REPRV(sig_repr));
+ }
+
+finally:
+ Py_CLEAR(parent_repr);
+ Py_CLEAR(sig_repr);
+ return my_repr;
+}
+
+static PyObject *
+Struct_tp_new (PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *signature = NULL;
+ long variantness = 0;
+ PyObject *self, *key;
+ static char *argnames[] = {"signature", "variant_level", NULL};
+
+ if (PyTuple_Size(args) != 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "__new__ takes exactly one positional parameter");
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(dbus_py_empty_tuple, kwargs,
+ "|Ol:__new__", argnames,
+ &signature, &variantness)) {
+ return NULL;
+ }
+ if (variantness < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "variant_level must be non-negative");
+ return NULL;
+ }
+
+ self = (PyTuple_Type.tp_new)(cls, args, NULL);
+ if (!self)
+ return NULL;
+ if (PyTuple_Size(self) < 1) {
+ PyErr_SetString(PyExc_ValueError, "D-Bus structs may not be empty");
+ Py_CLEAR(self);
+ return NULL;
+ }
+
+ if (!dbus_py_variant_level_set(self, variantness)) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+
+ /* convert signature from a borrowed ref of unknown type to an owned ref
+ of type Signature (or None) */
+ if (!signature) signature = Py_None;
+ if (signature == Py_None
+ || PyObject_IsInstance(signature, (PyObject *)&DBusPySignature_Type)) {
+ Py_INCREF(signature);
+ }
+ else {
+ signature = PyObject_CallFunction((PyObject *)&DBusPySignature_Type,
+ "(O)", signature);
+ if (!signature) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+ }
+
+ key = PyLong_FromVoidPtr(self);
+ if (!key) {
+ Py_CLEAR(self);
+ Py_CLEAR(signature);
+ return NULL;
+ }
+ if (PyDict_SetItem(struct_signatures, key, signature) < 0) {
+ Py_CLEAR(key);
+ Py_CLEAR(self);
+ Py_CLEAR(signature);
+ return NULL;
+ }
+
+ Py_CLEAR(key);
+ Py_CLEAR(signature);
+ return self;
+}
+
+static void
+Struct_tp_dealloc(PyObject *self)
+{
+ PyObject *et, *ev, *etb, *key;
+
+ dbus_py_variant_level_clear(self);
+ PyErr_Fetch(&et, &ev, &etb);
+
+ key = PyLong_FromVoidPtr(self);
+ if (key) {
+ if (PyDict_GetItem(struct_signatures, key)) {
+ if (PyDict_DelItem(struct_signatures, key) < 0) {
+ /* should never happen */
+ PyErr_WriteUnraisable(self);
+ }
+ }
+ Py_CLEAR(key);
+ }
+ else {
+ /* not enough memory to free all the memory... leak the signature,
+ * there's not much else we could do here */
+ PyErr_WriteUnraisable(self);
+ }
+
+ PyErr_Restore(et, ev, etb);
+ (PyTuple_Type.tp_dealloc)(self);
+}
+
+static PyObject *
+Struct_tp_getattro(PyObject *obj, PyObject *name)
+{
+ PyObject *key, *value;
+
+ if (PyUnicode_CompareWithASCIIString(name, "signature"))
+ return dbus_py_variant_level_getattro(obj, name);
+
+ key = PyLong_FromVoidPtr(obj);
+
+ if (!key) {
+ return NULL;
+ }
+
+ value = PyDict_GetItem(struct_signatures, key);
+ Py_CLEAR(key);
+
+ if (!value)
+ value = Py_None;
+ Py_INCREF(value);
+ return value;
+}
+
+PyTypeObject DBusPyStruct_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.Struct",
+ 0,
+ 0,
+ Struct_tp_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ (reprfunc)Struct_tp_repr, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ Struct_tp_getattro, /* tp_getattro */
+ dbus_py_immutable_setattro, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ Struct_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ Struct_tp_new, /* tp_new */
+};
+
+dbus_bool_t
+dbus_py_init_container_types(void)
+{
+ struct_signatures = PyDict_New();
+ if (!struct_signatures) return 0;
+
+ DBusPyArray_Type.tp_base = &PyList_Type;
+ if (PyType_Ready(&DBusPyArray_Type) < 0) return 0;
+
+ DBusPyDict_Type.tp_base = &PyDict_Type;
+ if (PyType_Ready(&DBusPyDict_Type) < 0) return 0;
+
+ DBusPyStruct_Type.tp_base = &PyTuple_Type;
+ if (PyType_Ready(&DBusPyStruct_Type) < 0) return 0;
+
+ return 1;
+}
+
+dbus_bool_t
+dbus_py_insert_container_types(PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF(&DBusPyArray_Type);
+ if (PyModule_AddObject(this_module, "Array",
+ (PyObject *)&DBusPyArray_Type) < 0) return 0;
+
+ Py_INCREF(&DBusPyDict_Type);
+ if (PyModule_AddObject(this_module, "Dictionary",
+ (PyObject *)&DBusPyDict_Type) < 0) return 0;
+
+ Py_INCREF(&DBusPyStruct_Type);
+ if (PyModule_AddObject(this_module, "Struct",
+ (PyObject *)&DBusPyStruct_Type) < 0) return 0;
+
+ return 1;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
+
diff --git a/dbus_bindings/dbus_bindings-internal.h b/dbus_bindings/dbus_bindings-internal.h
new file mode 100644
index 0000000..eb48897
--- /dev/null
+++ b/dbus_bindings/dbus_bindings-internal.h
@@ -0,0 +1,272 @@
+/* _dbus_bindings internal API. For use within _dbus_bindings only.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef DBUS_BINDINGS_INTERNAL_H
+#define DBUS_BINDINGS_INTERNAL_H
+
+#define PY_SSIZE_T_CLEAN 1
+#define PY_SIZE_T_CLEAN 1
+
+#include <Python.h>
+
+#define INSIDE_DBUS_PYTHON_BINDINGS
+#include "dbus/dbus-python.h"
+
+#if defined(__GNUC__)
+# if __GNUC__ >= 3
+# define UNUSED __attribute__((__unused__))
+# define NORETURN __attribute__((__noreturn__))
+# else
+# define UNUSED /*nothing*/
+# define NORETURN /*nothing*/
+# endif
+#else
+# define UNUSED /*nothing*/
+# define NORETURN /*nothing*/
+#endif
+
+/* no need for extern "C", this is only for internal use */
+
+/* on/off switch for debugging support (see below) */
+#undef USING_DBG
+#if 0 && !defined(DBG_IS_TOO_VERBOSE)
+# define USING_DBG 1
+#endif
+
+#define DEFINE_CHECK(type) \
+static inline int type##_Check (PyObject *o) \
+{ \
+ return (PyObject_TypeCheck (o, &type##_Type)); \
+} \
+static inline int type##_CheckExact (PyObject *o) \
+{ \
+ return (Py_TYPE(o) == &type##_Type); \
+}
+
+/* This is a clever little trick to make writing the various object reprs
+ * easier. It relies on Python's %V format option which consumes two
+ * arguments. The first is a unicode object which may be NULL, and the second
+ * is a char* which will be used if the first parameter is NULL.
+ *
+ * The issue is that we don't know whether the `parent_repr` at the call site
+ * is a unicode or a bytes (a.k.a. 8-bit string). Under Python 3, it will
+ * always be a unicode. Under Python 2 it will *probably* be a bytes/str, but
+ * could potentially be a unicode. So, we check the type, and if it's a
+ * unicode, we pass that as the first argument, leaving NULL as the second
+ * argument (since it will never be checked). However, if the object is not a
+ * unicode, it better be a bytes. In that case, we'll pass NULL as the first
+ * argument so that the second one gets used, and we'll dig the char* out of
+ * the bytes object for that purpose.
+ *
+ * You might think that this would crash if obj is neither a bytes/str or
+ * unicode, and you'd be right *except* that Python doesn't allow any other
+ * types to be returned in the reprs. Also, since obj will always be the repr
+ * of a built-in type, it will never be anything other than a bytes or a
+ * unicode in any version of Python. So in practice, this is safe.
+ */
+#define REPRV(obj) \
+ (PyUnicode_Check(obj) ? (obj) : NULL), \
+ (PyUnicode_Check(obj) ? NULL : PyBytes_AS_STRING(obj))
+
+PyMODINIT_FUNC PyInit__dbus_bindings(void);
+
+/* conn.c */
+extern PyTypeObject DBusPyConnection_Type;
+DEFINE_CHECK(DBusPyConnection)
+extern dbus_bool_t dbus_py_init_conn_types(void);
+extern dbus_bool_t dbus_py_insert_conn_types(PyObject *this_module);
+
+/* libdbusconn.c */
+extern PyTypeObject DBusPyLibDBusConnection_Type;
+DEFINE_CHECK(DBusPyLibDBusConnection)
+PyObject *DBusPyLibDBusConnection_New(DBusConnection *conn);
+extern dbus_bool_t dbus_py_init_libdbus_conn_types(void);
+extern dbus_bool_t dbus_py_insert_libdbus_conn_types(PyObject *this_module);
+
+/* bus.c */
+extern dbus_bool_t dbus_py_init_bus_types(void);
+extern dbus_bool_t dbus_py_insert_bus_types(PyObject *this_module);
+
+/* exceptions.c */
+extern PyObject *DBusPyException_SetString(const char *msg);
+extern PyObject *DBusPyException_ConsumeError(DBusError *error);
+extern dbus_bool_t dbus_py_init_exception_types(void);
+extern dbus_bool_t dbus_py_insert_exception_types(PyObject *this_module);
+
+/* types */
+extern PyTypeObject DBusPyBoolean_Type;
+DEFINE_CHECK(DBusPyBoolean)
+extern PyTypeObject DBusPyObjectPath_Type, DBusPySignature_Type;
+DEFINE_CHECK(DBusPyObjectPath)
+DEFINE_CHECK(DBusPySignature)
+extern PyTypeObject DBusPyArray_Type, DBusPyDict_Type, DBusPyStruct_Type;
+DEFINE_CHECK(DBusPyArray)
+DEFINE_CHECK(DBusPyDict)
+DEFINE_CHECK(DBusPyStruct)
+extern PyTypeObject DBusPyByte_Type, DBusPyByteArray_Type;
+DEFINE_CHECK(DBusPyByteArray)
+DEFINE_CHECK(DBusPyByte)
+extern PyTypeObject DBusPyString_Type;
+DEFINE_CHECK(DBusPyString)
+extern PyTypeObject DBusPyDouble_Type;
+DEFINE_CHECK(DBusPyDouble)
+extern PyTypeObject DBusPyInt16_Type, DBusPyUInt16_Type;
+DEFINE_CHECK(DBusPyInt16)
+DEFINE_CHECK(DBusPyUInt16)
+extern PyTypeObject DBusPyInt32_Type, DBusPyUInt32_Type;
+DEFINE_CHECK(DBusPyInt32)
+DEFINE_CHECK(DBusPyUInt32)
+extern PyTypeObject DBusPyUnixFd_Type;
+DEFINE_CHECK(DBusPyUnixFd)
+extern PyTypeObject DBusPyInt64_Type, DBusPyUInt64_Type;
+DEFINE_CHECK(DBusPyInt64)
+DEFINE_CHECK(DBusPyUInt64)
+extern dbus_bool_t dbus_py_init_abstract(void);
+extern dbus_bool_t dbus_py_init_signature(void);
+extern dbus_bool_t dbus_py_init_int_types(void);
+extern dbus_bool_t dbus_py_init_unixfd_type(void);
+extern dbus_bool_t dbus_py_init_string_types(void);
+extern dbus_bool_t dbus_py_init_float_types(void);
+extern dbus_bool_t dbus_py_init_container_types(void);
+extern dbus_bool_t dbus_py_init_byte_types(void);
+extern dbus_bool_t dbus_py_insert_abstract_types(PyObject *this_module);
+extern dbus_bool_t dbus_py_insert_signature(PyObject *this_module);
+extern dbus_bool_t dbus_py_insert_int_types(PyObject *this_module);
+extern dbus_bool_t dbus_py_insert_unixfd_type(PyObject *this_module);
+extern dbus_bool_t dbus_py_insert_string_types(PyObject *this_module);
+extern dbus_bool_t dbus_py_insert_float_types(PyObject *this_module);
+extern dbus_bool_t dbus_py_insert_container_types(PyObject *this_module);
+extern dbus_bool_t dbus_py_insert_byte_types(PyObject *this_module);
+
+int dbus_py_unix_fd_get_fd(PyObject *self);
+
+/* generic */
+extern void dbus_py_take_gil_and_xdecref(PyObject *);
+extern int dbus_py_immutable_setattro(PyObject *, PyObject *, PyObject *);
+extern PyObject *dbus_py_empty_tuple;
+extern dbus_bool_t dbus_py_init_generic(void);
+
+/* message.c */
+extern DBusMessage *DBusPyMessage_BorrowDBusMessage(PyObject *msg);
+extern PyObject *DBusPyMessage_ConsumeDBusMessage(DBusMessage *);
+extern dbus_bool_t dbus_py_init_message_types(void);
+extern dbus_bool_t dbus_py_insert_message_types(PyObject *this_module);
+
+/* pending-call.c */
+extern PyObject *DBusPyPendingCall_ConsumeDBusPendingCall(DBusPendingCall *,
+ PyObject *);
+extern dbus_bool_t dbus_py_init_pending_call(void);
+extern dbus_bool_t dbus_py_insert_pending_call(PyObject *this_module);
+
+/* mainloop.c */
+extern dbus_bool_t dbus_py_set_up_connection(PyObject *conn,
+ PyObject *mainloop);
+extern dbus_bool_t dbus_py_set_up_server(PyObject *server,
+ PyObject *mainloop);
+extern PyObject *dbus_py_get_default_main_loop(void);
+extern dbus_bool_t dbus_py_check_mainloop_sanity(PyObject *);
+extern dbus_bool_t dbus_py_init_mainloop(void);
+extern dbus_bool_t dbus_py_insert_mainloop_types(PyObject *);
+
+/* server.c */
+extern PyTypeObject DBusPyServer_Type;
+DEFINE_CHECK(DBusPyServer)
+extern dbus_bool_t dbus_py_init_server_types(void);
+extern dbus_bool_t dbus_py_insert_server_types(PyObject *this_module);
+extern DBusServer *DBusPyServer_BorrowDBusServer(PyObject *self);
+
+/* validation.c */
+dbus_bool_t dbus_py_validate_bus_name(const char *name,
+ dbus_bool_t may_be_unique,
+ dbus_bool_t may_be_not_unique);
+dbus_bool_t dbus_py_validate_member_name(const char *name);
+dbus_bool_t dbus_py_validate_interface_name(const char *name);
+dbus_bool_t dbus_py_validate_object_path(const char *path);
+#define dbus_py_validate_error_name dbus_py_validate_interface_name
+
+/* debugging support */
+void _dbus_py_assertion_failed(const char *) NORETURN;
+#define DBUS_PY_RAISE_VIA_NULL_IF_FAIL(assertion) \
+ do { if (!(assertion)) { \
+ _dbus_py_assertion_failed(#assertion); \
+ return NULL; \
+ } \
+ } while (0)
+
+#define DBUS_PY_RAISE_VIA_GOTO_IF_FAIL(assertion, label) \
+ do { if (!(assertion)) { \
+ _dbus_py_assertion_failed(#assertion); \
+ goto label; \
+ } \
+ } while (0)
+
+#define DBUS_PY_RAISE_VIA_RETURN_IF_FAIL(assertion, value) \
+ do { if (!(assertion)) { \
+ _dbus_py_assertion_failed(#assertion); \
+ return value; \
+ } \
+ } while (0)
+
+/* verbose debugging support */
+#ifdef USING_DBG
+
+# include <sys/types.h>
+# include <unistd.h>
+
+void _dbus_py_dbg_exc(void);
+void _dbus_py_whereami(void);
+void _dbus_py_dbg_dump_message(DBusMessage *);
+
+# define TRACE(self) do { \
+ fprintf(stderr, "TRACE: <%s at %p> in %s, " \
+ "%d refs\n", \
+ self ? Py_TYPE(self)->tp_name : "(null)", \
+ self, __func__, \
+ self ? (int)Py_REFCNT(self) : 0); \
+ } while (0)
+# define DBG(format, ...) fprintf(stderr, "DEBUG: " format "\n",\
+ __VA_ARGS__)
+# define DBG_EXC(format, ...) do {DBG(format, __VA_ARGS__); \
+ _dbus_py_dbg_exc();} while (0)
+# define DBG_DUMP_MESSAGE(x) _dbus_py_dbg_dump_message(x)
+# define DBG_WHEREAMI _dbus_py_whereami()
+
+#else /* !defined(USING_DBG) */
+
+# define TRACE(self) do {} while (0)
+# define DBG(format, ...) do {} while (0)
+# define DBG_EXC(format, ...) do {} while (0)
+# define DBG_DUMP_MESSAGE(x) do {} while (0)
+# define DBG_WHEREAMI do {} while (0)
+
+#endif /* !defined(USING_DBG) */
+
+/* General-purpose Python glue */
+
+#define DEFERRED_ADDRESS(ADDR) 0
+
+#endif
diff --git a/dbus_bindings/debug.c b/dbus_bindings/debug.c
new file mode 100644
index 0000000..614e541
--- /dev/null
+++ b/dbus_bindings/debug.c
@@ -0,0 +1,98 @@
+/* Debug code for _dbus_bindings.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include <stdlib.h>
+
+void
+_dbus_py_assertion_failed(const char *assertion)
+{
+ PyErr_SetString(PyExc_AssertionError, assertion);
+#if 1 || defined(USING_DBG) || defined(FATAL_ASSERTIONS)
+ /* print the Python stack, and dump core so we can see the C stack too */
+ PyErr_Print();
+ abort();
+#endif
+}
+
+#ifdef USING_DBG
+void
+_dbus_py_whereami(void)
+{
+ PyObject *c, *v, *t;
+ /* This is a little mad. We want to get the traceback without
+ clearing the error indicator, if any. */
+ PyErr_Fetch(&c, &v, &t); /* 3 new refs */
+ Py_XINCREF(c); Py_XINCREF(v); Py_XINCREF(t); /* now we own 6 refs */
+ PyErr_Restore(c, v, t); /* steals 3 refs */
+
+ if (!PyErr_Occurred()) {
+ PyErr_SetString(PyExc_AssertionError,
+ "No error, but plz provide traceback kthx");
+ }
+ PyErr_Print();
+
+ PyErr_Restore(c, v, t); /* steals another 3 refs */
+}
+
+void
+_dbus_py_dbg_exc(void)
+{
+ PyObject *c, *v, *t;
+ /* This is a little mad. We want to get the traceback without
+ clearing the error indicator. */
+ PyErr_Fetch(&c, &v, &t); /* 3 new refs */
+ Py_XINCREF(c); Py_XINCREF(v); Py_XINCREF(t); /* now we own 6 refs */
+ PyErr_Restore(c, v, t); /* steals 3 refs */
+ PyErr_Print();
+ PyErr_Restore(c, v, t); /* steals another 3 refs */
+}
+
+void
+_dbus_py_dbg_dump_message(DBusMessage *message)
+{
+ const char *s;
+ fprintf(stderr, "DBusMessage at %p\n", message);
+
+ s = dbus_message_get_destination(message);
+ if (!s) s = "(null)";
+ fprintf(stderr, "\tdestination %s\n", s);
+
+ s = dbus_message_get_interface(message);
+ if (!s) s = "(null)";
+ fprintf(stderr, "\tinterface %s\n", s);
+
+ s = dbus_message_get_member(message);
+ if (!s) s = "(null)";
+ fprintf(stderr, "\tmember %s\n", s);
+
+ s = dbus_message_get_path(message);
+ if (!s) s = "(null)";
+ fprintf(stderr, "\tpath %s\n", s);
+}
+#endif
diff --git a/dbus_bindings/exceptions.c b/dbus_bindings/exceptions.c
new file mode 100644
index 0000000..2940c2b
--- /dev/null
+++ b/dbus_bindings/exceptions.c
@@ -0,0 +1,106 @@
+/* D-Bus exception base classes.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+static PyObject *imported_dbus_exception = NULL;
+
+static dbus_bool_t
+import_exception(void)
+{
+ PyObject *name;
+ PyObject *exceptions;
+
+ if (imported_dbus_exception != NULL) {
+ return TRUE;
+ }
+
+ name = PyUnicode_FromString("dbus.exceptions");
+ if (name == NULL) {
+ return FALSE;
+ }
+ exceptions = PyImport_Import(name);
+ Py_CLEAR(name);
+ if (exceptions == NULL) {
+ return FALSE;
+ }
+ imported_dbus_exception = PyObject_GetAttrString(exceptions,
+ "DBusException");
+ Py_CLEAR(exceptions);
+
+ return (imported_dbus_exception != NULL);
+}
+
+PyObject *
+DBusPyException_SetString(const char *msg)
+{
+ if (imported_dbus_exception != NULL || import_exception()) {
+ PyErr_SetString(imported_dbus_exception, msg);
+ }
+ return NULL;
+}
+
+PyObject *
+DBusPyException_ConsumeError(DBusError *error)
+{
+ PyObject *exc_value = NULL;
+
+ if (imported_dbus_exception == NULL && !import_exception()) {
+ goto finally;
+ }
+
+ exc_value = PyObject_CallFunction(imported_dbus_exception,
+ "s",
+ error->message ? error->message
+ : "");
+
+ if (!exc_value) {
+ goto finally;
+ }
+
+ if (error->name) {
+ PyObject *name = PyUnicode_FromString(error->name);
+ int ret;
+
+ if (!name)
+ goto finally;
+ ret = PyObject_SetAttrString(exc_value, "_dbus_error_name", name);
+ Py_CLEAR(name);
+ if (ret < 0) {
+ goto finally;
+ }
+ }
+
+ PyErr_SetObject(imported_dbus_exception, exc_value);
+
+finally:
+ Py_CLEAR(exc_value);
+ dbus_error_free(error);
+ return NULL;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/float.c b/dbus_bindings/float.c
new file mode 100644
index 0000000..3ec1c13
--- /dev/null
+++ b/dbus_bindings/float.c
@@ -0,0 +1,156 @@
+/* Simple D-Bus types: Double and (with appropriate #defines) Float
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "types-internal.h"
+
+PyDoc_STRVAR(Double_tp_doc,
+"A double-precision floating point number (a subtype of float).");
+
+#ifdef WITH_DBUS_FLOAT32
+PyDoc_STRVAR(Float_tp_doc,
+"A single-precision floating point number (a subtype of float).");
+#endif
+
+PyTypeObject DBusPyDouble_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.Double",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ Double_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPythonFloatType), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+
+#ifdef WITH_DBUS_FLOAT32
+
+PyTypeObject DBusPyFloat_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.Float",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ Float_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPythonFloatType), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+#endif /* defined(WITH_DBUS_FLOAT32) */
+
+dbus_bool_t
+dbus_py_init_float_types(void)
+{
+ DBusPyDouble_Type.tp_base = &DBusPyFloatBase_Type;
+ if (PyType_Ready(&DBusPyDouble_Type) < 0) return 0;
+
+#ifdef WITH_DBUS_FLOAT32
+ DBusPyFloat_Type.tp_base = &DBusPyFloatBase_Type;
+ if (PyType_Ready(&DBusPyFloat_Type) < 0) return 0;
+#endif
+
+ return 1;
+}
+
+dbus_bool_t
+dbus_py_insert_float_types(PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF(&DBusPyDouble_Type);
+ if (PyModule_AddObject(this_module, "Double",
+ (PyObject *)&DBusPyDouble_Type) < 0) return 0;
+#ifdef WITH_DBUS_FLOAT32
+ Py_INCREF(&DBusPyFloat_Type);
+ if (PyModule_AddObject(this_module, "Float",
+ (PyObject *)&DBusPyFloat_Type) < 0) return 0;
+#endif
+
+ return 1;
+}
diff --git a/dbus_bindings/generic.c b/dbus_bindings/generic.c
new file mode 100644
index 0000000..385a2e4
--- /dev/null
+++ b/dbus_bindings/generic.c
@@ -0,0 +1,63 @@
+/* General Python glue code, used in _dbus_bindings but not actually anything
+ * to do with D-Bus.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+/* The empty tuple, held globally since dbus-python turns out to use it quite
+ * a lot
+ */
+PyObject *dbus_py_empty_tuple = NULL;
+
+int
+dbus_py_immutable_setattro(PyObject *obj UNUSED,
+ PyObject *name UNUSED,
+ PyObject *value UNUSED)
+{
+ PyErr_SetString(PyExc_AttributeError, "Object is immutable");
+ return -1;
+}
+
+/* Take the global interpreter lock and decrement the reference count.
+ * Suitable for calling from a C callback. */
+void
+dbus_py_take_gil_and_xdecref(PyObject *obj)
+{
+ PyGILState_STATE gil = PyGILState_Ensure();
+ Py_CLEAR(obj);
+ PyGILState_Release(gil);
+}
+
+dbus_bool_t
+dbus_py_init_generic(void)
+{
+ dbus_py_empty_tuple = PyTuple_New(0);
+ if (!dbus_py_empty_tuple) return 0;
+ return 1;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/int.c b/dbus_bindings/int.c
new file mode 100644
index 0000000..47d8404
--- /dev/null
+++ b/dbus_bindings/int.c
@@ -0,0 +1,771 @@
+/* Simple D-Bus types: integers of various sizes, and ObjectPath.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include "types-internal.h"
+
+/* Specific types =================================================== */
+
+/* Boolean, a subclass of DBusPythonInt ============================= */
+
+PyDoc_STRVAR(Boolean_tp_doc,
+"dbus.Boolean(value: bool[, variant_level: int])\n"
+"\n"
+"A boolean, represented as a subtype of ``int`` (not ``bool``, because ``bool``\n"
+"cannot be subclassed).\n"
+"\n"
+"``value`` is converted to 0 or 1 as if by ``int(bool(value))``.\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing a boolean, this is represented in Python by a\n"
+" Boolean with variant_level==2.\n"
+);
+
+static PyObject *
+Boolean_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *tuple, *self, *value = Py_None;
+ long variantness = 0;
+ static char *argnames[] = {"_", "variant_level", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|Ol:__new__", argnames,
+ &value, &variantness)) return NULL;
+ if (variantness < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "variant_level must be non-negative");
+ return NULL;
+ }
+ tuple = Py_BuildValue("(i)", PyObject_IsTrue(value) ? 1 : 0);
+ if (!tuple) return NULL;
+ self = (DBusPyLongBase_Type.tp_new)(cls, tuple, kwargs);
+ Py_CLEAR(tuple);
+ return self;
+}
+
+static PyObject *
+Boolean_tp_str(PyObject *self)
+{
+ return PyUnicode_FromString(PyObject_IsTrue(self) ? "1" : "0");
+}
+
+static PyObject *
+Boolean_tp_repr(PyObject *self)
+{
+ int is_true = PyObject_IsTrue(self);
+ long variant_level = dbus_py_variant_level_get(self);
+ if (variant_level < 0)
+ return NULL;
+
+ if (is_true == -1)
+ return NULL;
+
+ if (variant_level > 0) {
+ return PyUnicode_FromFormat("%s(%s, variant_level=%ld)",
+ Py_TYPE(self)->tp_name,
+ is_true ? "True" : "False",
+ variant_level);
+ }
+ return PyUnicode_FromFormat("%s(%s)",
+ Py_TYPE(self)->tp_name,
+ is_true ? "True" : "False");
+}
+
+PyTypeObject DBusPyBoolean_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.Boolean",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ Boolean_tp_repr, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ Boolean_tp_str, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ Boolean_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPyLongBase_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ Boolean_tp_new, /* tp_new */
+};
+
+/* Int16 ============================================================ */
+
+PyDoc_STRVAR(Int16_tp_doc,
+"dbus.Int16(value: int[, variant_level: int])\n"
+"\n"
+"A signed 16-bit integer between -0x8000 and +0x7FFF, represented as\n"
+"a subtype of `int`.\n"
+"\n"
+"value must be within the allowed range, or OverflowError will be\n"
+"raised.\n"
+"\n"
+" variant_level must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing an int16, this is represented in Python by an\n"
+" Int16 with variant_level==2.\n"
+);
+
+dbus_int16_t
+dbus_py_int16_range_check(PyObject *obj)
+{
+ long i = PyLong_AsLong(obj);
+ if (i == -1 && PyErr_Occurred())
+ return -1;
+
+ if (i < -0x8000 || i > 0x7fff) {
+ PyErr_Format(PyExc_OverflowError, "Value %d out of range for Int16",
+ (int)i);
+ return -1;
+ }
+ return (dbus_int16_t)i;
+}
+
+static PyObject *
+Int16_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *self = (DBusPyLongBase_Type.tp_new)(cls, args, kwargs);
+ if (self && dbus_py_int16_range_check(self) == -1 && PyErr_Occurred()) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+ return self;
+}
+
+PyTypeObject DBusPyInt16_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.Int16",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ Int16_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPyLongBase_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ Int16_tp_new, /* tp_new */
+};
+
+/* UInt16 =========================================================== */
+
+PyDoc_STRVAR(UInt16_tp_doc,
+"dbus.UInt16(value: int[, variant_level: int])\n"
+"\n"
+"An unsigned 16-bit integer between 0 and 0xFFFF, represented as\n"
+"a subtype of ``int``.\n"
+"\n"
+"``value`` must be within the allowed range, or `OverflowError` will be\n"
+"raised.\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing a uint16, this is represented in Python by a\n"
+" UInt16 with variant_level==2.\n"
+);
+
+dbus_uint16_t
+dbus_py_uint16_range_check(PyObject *obj)
+{
+ long i = PyLong_AsLong(obj);
+ if (i == -1 && PyErr_Occurred())
+ return (dbus_uint16_t)(-1);
+
+ if (i < 0 || i > 0xffff) {
+ PyErr_Format(PyExc_OverflowError, "Value %d out of range for UInt16",
+ (int)i);
+ return (dbus_uint16_t)(-1);
+ }
+ return (dbus_uint16_t)i;
+}
+
+static PyObject *
+UInt16_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *self = (DBusPyLongBase_Type.tp_new)(cls, args, kwargs);
+ if (self && dbus_py_uint16_range_check(self) == (dbus_uint16_t)(-1)
+ && PyErr_Occurred())
+ {
+ Py_CLEAR (self);
+ return NULL;
+ }
+ return self;
+}
+
+PyTypeObject DBusPyUInt16_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.UInt16",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ UInt16_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPyLongBase_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ UInt16_tp_new, /* tp_new */
+};
+
+/* Int32 ============================================================ */
+
+PyDoc_STRVAR(Int32_tp_doc,
+"dbus.Int32(value: int[, variant_level: int])\n"
+"\n"
+"A signed 32-bit integer between -0x8000 0000 and +0x7FFF FFFF, represented as\n"
+"a subtype of ``int``.\n"
+"\n"
+"``value`` must be within the allowed range, or `OverflowError` will be\n"
+"raised.\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing an int32, this is represented in Python by an\n"
+" Int32 with variant_level==2.\n"
+);
+
+dbus_int32_t
+dbus_py_int32_range_check(PyObject *obj)
+{
+ long i = PyLong_AsLong(obj);
+ if (i == -1 && PyErr_Occurred())
+ return -1;
+
+ if (i < INT32_MIN || i > INT32_MAX) {
+ PyErr_Format(PyExc_OverflowError, "Value %d out of range for Int32",
+ (int)i);
+ return -1;
+ }
+ return (dbus_int32_t)i;
+}
+
+static PyObject *
+Int32_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *self = (DBusPyLongBase_Type.tp_new)(cls, args, kwargs);
+ if (self && dbus_py_int32_range_check(self) == -1 && PyErr_Occurred()) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+ return self;
+}
+
+PyTypeObject DBusPyInt32_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.Int32",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ Int32_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPyLongBase_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ Int32_tp_new, /* tp_new */
+};
+
+/* UInt32 =========================================================== */
+
+PyDoc_STRVAR(UInt32_tp_doc,
+"dbus.UInt32(value: int[, variant_level: int])\n"
+"\n"
+"An unsigned 32-bit integer between 0 and 0xFFFF FFFF, represented as a\n"
+"subtype of ``long`` in Python 2 or ``int`` in Python 3.\n"
+"\n"
+"``value`` must be within the allowed range, or `OverflowError` will be\n"
+"raised.\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing a uint32, this is represented in Python by a\n"
+" UInt32 with variant_level==2.\n"
+);
+
+dbus_uint32_t
+dbus_py_uint32_range_check(PyObject *obj)
+{
+ unsigned long i;
+ PyObject *long_obj = PyNumber_Long(obj);
+
+ if (!long_obj) return (dbus_uint32_t)(-1);
+ i = PyLong_AsUnsignedLong(long_obj);
+ if (i == (unsigned long)(-1) && PyErr_Occurred()) {
+ Py_CLEAR(long_obj);
+ return (dbus_uint32_t)(-1);
+ }
+ if (i > UINT32_MAX) {
+ PyErr_Format(PyExc_OverflowError, "Value %d out of range for UInt32",
+ (int)i);
+ Py_CLEAR(long_obj);
+ return (dbus_uint32_t)(-1);
+ }
+ Py_CLEAR(long_obj);
+ return i;
+}
+
+static PyObject *
+UInt32_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *self = (DBusPyLongBase_Type.tp_new)(cls, args, kwargs);
+ if (self && dbus_py_uint32_range_check(self) == (dbus_uint32_t)(-1)
+ && PyErr_Occurred()) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+ return self;
+}
+
+PyTypeObject DBusPyUInt32_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.UInt32",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ UInt32_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPyLongBase_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ UInt32_tp_new, /* tp_new */
+};
+
+/* Int64 =========================================================== */
+
+PyDoc_STRVAR(Int64_tp_doc,
+"dbus.Int64(value: int[, variant_level: int])\n"
+"\n"
+"A signed 64-bit integer between -0x8000 0000 0000 0000 and\n"
+"+0x7FFF FFFF FFFF FFFF, represented as a\n"
+"subtype of ``long`` in Python 2 or ``int`` in Python 3.\n"
+"\n"
+"Note that this may be changed in future to be a subtype of `int` on\n"
+"64-bit platforms; applications should not rely on either behaviour.\n"
+"\n"
+"``value`` must be within the allowed range, or `OverflowError` will be\n"
+"raised.\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing an int64, this is represented in Python by an\n"
+" Int64 with variant_level==2.\n"
+);
+
+#ifdef DBUS_PYTHON_64_BIT_WORKS
+dbus_int64_t
+dbus_py_int64_range_check(PyObject *obj)
+{
+ PY_LONG_LONG i;
+ PyObject *long_obj = PyNumber_Long(obj);
+
+ if (!long_obj) return -1;
+ i = PyLong_AsLongLong(long_obj);
+ if (i == -1 && PyErr_Occurred()) {
+ Py_CLEAR(long_obj);
+ return -1;
+ }
+ if (i < INT64_MIN || i > INT64_MAX) {
+ PyErr_SetString(PyExc_OverflowError, "Value out of range for Int64");
+ Py_CLEAR(long_obj);
+ return -1;
+ }
+ Py_CLEAR(long_obj);
+ return i;
+}
+#endif
+
+static PyObject *
+Int64_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+#ifdef DBUS_PYTHON_64_BIT_WORKS
+ PyObject *self = (DBusPyLongBase_Type.tp_new)(cls, args, kwargs);
+ if (self && dbus_py_int64_range_check(self) == -1 && PyErr_Occurred()) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+ return self;
+#else
+ PyErr_SetString(PyExc_NotImplementedError,
+ "64-bit types are not available on this platform");
+ return NULL;
+#endif
+}
+
+PyTypeObject DBusPyInt64_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.Int64",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ Int64_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPyLongBase_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ Int64_tp_new, /* tp_new */
+};
+
+/* UInt64 =========================================================== */
+
+PyDoc_STRVAR(UInt64_tp_doc,
+"dbus.UInt64(value: int[, variant_level: int])\n"
+"\n"
+"An unsigned 64-bit integer between 0 and 0xFFFF FFFF FFFF FFFF,\n"
+"subtype of ``long`` in Python 2 or ``int`` in Python 3.\n"
+"\n"
+"``value`` must be within the allowed range, or `OverflowError` will be\n"
+"raised.\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing a uint64, this is represented in Python by a\n"
+" UInt64 with variant_level==2.\n"
+);
+
+dbus_uint64_t
+dbus_py_uint64_range_check(PyObject *obj)
+{
+ unsigned PY_LONG_LONG i;
+ PyObject *long_obj = PyNumber_Long(obj);
+
+ if (!long_obj) return (dbus_uint64_t)(-1);
+ i = PyLong_AsUnsignedLongLong(long_obj);
+ if (i == (unsigned PY_LONG_LONG)(-1) && PyErr_Occurred()) {
+ Py_CLEAR(long_obj);
+ return (dbus_uint64_t)(-1);
+ }
+ if (i > UINT64_MAX) {
+ PyErr_SetString(PyExc_OverflowError, "Value out of range for UInt64");
+ Py_CLEAR(long_obj);
+ return (dbus_uint64_t)(-1);
+ }
+ Py_CLEAR(long_obj);
+ return i;
+}
+
+static PyObject *
+UInt64_tp_new (PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+#ifdef DBUS_PYTHON_64_BIT_WORKS
+ PyObject *self = (DBusPyLongBase_Type.tp_new)(cls, args, kwargs);
+ if (self && dbus_py_uint64_range_check(self) == (dbus_uint64_t)(-1)
+ && PyErr_Occurred()) {
+ Py_CLEAR(self);
+ return NULL;
+ }
+ return self;
+#else
+ PyErr_SetString(PyExc_NotImplementedError,
+ "64-bit integer types are not supported on this platform");
+ return NULL;
+#endif
+}
+
+PyTypeObject DBusPyUInt64_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.UInt64",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ UInt64_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPyLongBase_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ UInt64_tp_new, /* tp_new */
+};
+
+dbus_bool_t
+dbus_py_init_int_types(void)
+{
+ DBusPyInt16_Type.tp_base = &DBusPyLongBase_Type;
+ if (PyType_Ready(&DBusPyInt16_Type) < 0) return 0;
+
+ DBusPyUInt16_Type.tp_base = &DBusPyLongBase_Type;
+ if (PyType_Ready(&DBusPyUInt16_Type) < 0) return 0;
+
+ DBusPyInt32_Type.tp_base = &DBusPyLongBase_Type;
+ if (PyType_Ready(&DBusPyInt32_Type) < 0) return 0;
+
+ DBusPyUInt32_Type.tp_base = &DBusPyLongBase_Type;
+ if (PyType_Ready(&DBusPyUInt32_Type) < 0) return 0;
+
+#if defined(DBUS_HAVE_INT64) && defined(HAVE_LONG_LONG)
+ DBusPyInt64_Type.tp_base = &DBusPyLongBase_Type;
+ if (PyType_Ready(&DBusPyInt64_Type) < 0) return 0;
+
+ DBusPyUInt64_Type.tp_base = &DBusPyLongBase_Type;
+ if (PyType_Ready(&DBusPyUInt64_Type) < 0) return 0;
+#endif
+
+ DBusPyBoolean_Type.tp_base = &DBusPyLongBase_Type;
+ if (PyType_Ready(&DBusPyBoolean_Type) < 0) return 0;
+
+ return 1;
+}
+
+dbus_bool_t
+dbus_py_insert_int_types(PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF(&DBusPyInt16_Type);
+ Py_INCREF(&DBusPyUInt16_Type);
+ Py_INCREF(&DBusPyInt32_Type);
+ Py_INCREF(&DBusPyUInt32_Type);
+ Py_INCREF(&DBusPyInt64_Type);
+ Py_INCREF(&DBusPyUInt64_Type);
+ Py_INCREF(&DBusPyBoolean_Type);
+ if (PyModule_AddObject(this_module, "Int16",
+ (PyObject *)&DBusPyInt16_Type) < 0) return 0;
+ if (PyModule_AddObject(this_module, "UInt16",
+ (PyObject *)&DBusPyUInt16_Type) < 0) return 0;
+ if (PyModule_AddObject(this_module, "Int32",
+ (PyObject *)&DBusPyInt32_Type) < 0) return 0;
+ if (PyModule_AddObject(this_module, "UInt32",
+ (PyObject *)&DBusPyUInt32_Type) < 0) return 0;
+ if (PyModule_AddObject(this_module, "Int64",
+ (PyObject *)&DBusPyInt64_Type) < 0) return 0;
+ if (PyModule_AddObject(this_module, "UInt64",
+ (PyObject *)&DBusPyUInt64_Type) < 0) return 0;
+ if (PyModule_AddObject(this_module, "Boolean",
+ (PyObject *)&DBusPyBoolean_Type) < 0) return 0;
+
+ return 1;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/libdbusconn.c b/dbus_bindings/libdbusconn.c
new file mode 100644
index 0000000..4ab548b
--- /dev/null
+++ b/dbus_bindings/libdbusconn.c
@@ -0,0 +1,128 @@
+/* An extremely thin wrapper around a libdbus Connection, for use by
+ * Server.
+ *
+ * Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+#include "conn-internal.h"
+
+PyDoc_STRVAR(DBusPyLibDBusConnection_tp_doc,
+"A reference to a ``DBusConnection`` from ``libdbus``, which might not\n"
+"have been attached to a `dbus.connection.Connection` yet.\n"
+"\n"
+"Cannot be instantiated from Python. The only use of this object is to\n"
+"pass it to the ``dbus.connection.Connection`` constructor instead of an\n"
+"address.\n"
+);
+
+/** Create a DBusPyLibDBusConnection from a DBusConnection.
+ */
+PyObject *
+DBusPyLibDBusConnection_New(DBusConnection *conn)
+{
+ DBusPyLibDBusConnection *self = NULL;
+
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(conn);
+
+ self = (DBusPyLibDBusConnection *)(DBusPyLibDBusConnection_Type.tp_alloc(
+ &DBusPyLibDBusConnection_Type, 0));
+
+ if (!self)
+ return NULL;
+
+ self->conn = dbus_connection_ref (conn);
+
+ return (PyObject *)self;
+}
+
+/* Destructor */
+static void
+DBusPyLibDBusConnection_tp_dealloc(Connection *self)
+{
+ DBusConnection *conn = self->conn;
+ PyObject *et, *ev, *etb;
+
+ /* avoid clobbering any pending exception */
+ PyErr_Fetch(&et, &ev, &etb);
+
+ self->conn = NULL;
+
+ if (conn) {
+ dbus_connection_unref(conn);
+ }
+
+ PyErr_Restore(et, ev, etb);
+ (Py_TYPE(self)->tp_free)((PyObject *) self);
+}
+
+PyTypeObject DBusPyLibDBusConnection_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "_dbus_bindings._LibDBusConnection",
+ sizeof(DBusPyLibDBusConnection),
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)DBusPyLibDBusConnection_tp_dealloc,
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT,
+ DBusPyLibDBusConnection_tp_doc,
+};
+
+dbus_bool_t
+dbus_py_init_libdbus_conn_types(void)
+{
+ if (PyType_Ready(&DBusPyLibDBusConnection_Type) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+dbus_bool_t
+dbus_py_insert_libdbus_conn_types(PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF (&DBusPyLibDBusConnection_Type);
+
+ if (PyModule_AddObject(this_module, "_LibDBusConnection",
+ (PyObject *)&DBusPyLibDBusConnection_Type) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/mainloop.c b/dbus_bindings/mainloop.c
new file mode 100644
index 0000000..b0e0255
--- /dev/null
+++ b/dbus_bindings/mainloop.c
@@ -0,0 +1,209 @@
+/* Implementation of main-loop integration for dbus-python.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2008 Huang Peng <phuang@redhat.com>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+/* Native mainloop wrapper ========================================= */
+
+PyDoc_STRVAR(NativeMainLoop_tp_doc,
+"Object representing D-Bus main loop integration done in native code.\n"
+"Cannot be instantiated directly.\n"
+);
+
+static PyTypeObject NativeMainLoop_Type;
+
+DEFINE_CHECK(NativeMainLoop)
+
+typedef struct {
+ PyObject_HEAD
+ /* Called with the GIL held, should set a Python exception on error */
+ dbus_bool_t (*set_up_connection_cb)(DBusConnection *, void *);
+ dbus_bool_t (*set_up_server_cb)(DBusServer *, void *);
+ /* Called in a destructor. Must not touch the exception state (use
+ * PyErr_Fetch and PyErr_Restore if necessary). */
+ void (*free_cb)(void *);
+ void *data;
+} NativeMainLoop;
+
+static void NativeMainLoop_tp_dealloc(NativeMainLoop *self)
+{
+ if (self->data && self->free_cb) {
+ (self->free_cb)(self->data);
+ }
+ PyObject_Del((PyObject *)self);
+}
+
+static PyTypeObject NativeMainLoop_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.mainloop.NativeMainLoop",
+ sizeof(NativeMainLoop),
+ 0,
+ (destructor)NativeMainLoop_tp_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
+ NativeMainLoop_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ /* deliberately not callable! */
+ 0, /* tp_new */
+};
+
+/* Internal C API for Connection, Bus, Server ======================= */
+
+dbus_bool_t
+dbus_py_check_mainloop_sanity(PyObject *mainloop)
+{
+ if (NativeMainLoop_Check(mainloop)) {
+ return TRUE;
+ }
+ PyErr_SetString(PyExc_TypeError,
+ "A dbus.mainloop.NativeMainLoop instance is required");
+ return FALSE;
+}
+
+dbus_bool_t
+dbus_py_set_up_connection(PyObject *conn, PyObject *mainloop)
+{
+ if (NativeMainLoop_Check(mainloop)) {
+ /* Native mainloops are allowed to do arbitrary strange things */
+ NativeMainLoop *nml = (NativeMainLoop *)mainloop;
+ DBusConnection *dbc = DBusPyConnection_BorrowDBusConnection(conn);
+
+ if (!dbc) {
+ return FALSE;
+ }
+ return (nml->set_up_connection_cb)(dbc, nml->data);
+ }
+ PyErr_SetString(PyExc_TypeError,
+ "A dbus.mainloop.NativeMainLoop instance is required");
+ return FALSE;
+}
+
+dbus_bool_t
+dbus_py_set_up_server(PyObject *server, PyObject *mainloop)
+{
+ if (NativeMainLoop_Check(mainloop)) {
+ /* Native mainloops are allowed to do arbitrary strange things */
+ NativeMainLoop *nml = (NativeMainLoop *)mainloop;
+ DBusServer *dbs = DBusPyServer_BorrowDBusServer(server);
+
+ if (!dbs) {
+ return FALSE;
+ }
+ return (nml->set_up_server_cb)(dbs, nml->data);
+ }
+ PyErr_SetString(PyExc_TypeError,
+ "A dbus.mainloop.NativeMainLoop instance is required");
+ return FALSE;
+}
+
+/* C API ============================================================ */
+
+PyObject *
+DBusPyNativeMainLoop_New4(dbus_bool_t (*conn_cb)(DBusConnection *, void *),
+ dbus_bool_t (*server_cb)(DBusServer *, void *),
+ void (*free_cb)(void *),
+ void *data)
+{
+ NativeMainLoop *self = PyObject_New(NativeMainLoop, &NativeMainLoop_Type);
+ if (self) {
+ self->data = data;
+ self->free_cb = free_cb;
+ self->set_up_connection_cb = conn_cb;
+ self->set_up_server_cb = server_cb;
+ }
+ return (PyObject *)self;
+}
+
+/* Null mainloop implementation ===================================== */
+
+static dbus_bool_t
+noop_main_loop_cb(void *conn_or_server UNUSED, void *data UNUSED)
+{
+ return TRUE;
+}
+
+#define noop_conn_cb ((dbus_bool_t (*)(DBusConnection *, void *))(noop_main_loop_cb))
+#define noop_server_cb ((dbus_bool_t (*)(DBusServer *, void *))(noop_main_loop_cb))
+
+/* Initialization =================================================== */
+
+dbus_bool_t
+dbus_py_init_mainloop(void)
+{
+ if (PyType_Ready (&NativeMainLoop_Type) < 0) return 0;
+
+ return 1;
+}
+
+dbus_bool_t
+dbus_py_insert_mainloop_types(PyObject *this_module)
+{
+ PyObject *null_main_loop = DBusPyNativeMainLoop_New4(noop_conn_cb,
+ noop_server_cb,
+ NULL,
+ NULL);
+ if (!null_main_loop) return 0;
+
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF (&NativeMainLoop_Type);
+ if (PyModule_AddObject (this_module, "NativeMainLoop",
+ (PyObject *)&NativeMainLoop_Type) < 0) return 0;
+ if (PyModule_AddObject (this_module, "NULL_MAIN_LOOP",
+ null_main_loop) < 0) return 0;
+ return 1;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/message-append.c b/dbus_bindings/message-append.c
new file mode 100644
index 0000000..9c6b5f7
--- /dev/null
+++ b/dbus_bindings/message-append.c
@@ -0,0 +1,1225 @@
+/* D-Bus Message serialization. This contains all the logic to map from
+ * Python objects to D-Bus types.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include <assert.h>
+
+#define DBG_IS_TOO_VERBOSE
+#include "compat-internal.h"
+#include "types-internal.h"
+#include "message-internal.h"
+
+/* Return the number of variants wrapping the given object. Return 0
+ * if the object is not a D-Bus type.
+ */
+static long
+get_variant_level(PyObject *obj)
+{
+ if (DBusPyString_Check(obj)) {
+ return ((DBusPyString *)obj)->variant_level;
+ }
+ else if (DBusPyFloatBase_Check(obj)) {
+ return ((DBusPyFloatBase *)obj)->variant_level;
+ }
+ else if (DBusPyArray_Check(obj)) {
+ return ((DBusPyArray *)obj)->variant_level;
+ }
+ else if (DBusPyDict_Check(obj)) {
+ return ((DBusPyDict *)obj)->variant_level;
+ }
+ else if (DBusPyLongBase_Check(obj) ||
+ DBusPyBytesBase_Check(obj) ||
+ DBusPyStrBase_Check(obj) ||
+ DBusPyStruct_Check(obj)) {
+ return dbus_py_variant_level_get(obj);
+ }
+ else {
+ return 0;
+ }
+}
+
+char dbus_py_Message_append__doc__[] = (
+"message.append(*args, **kwargs)\n"
+"\n"
+"Set the message's arguments from the positional parameter, according to\n"
+"the signature given by the ``signature`` keyword parameter.\n"
+"\n"
+"The following type conversions are supported:\n\n"
+"=============================== ===========================\n"
+"D-Bus (in signature) Python\n"
+"=============================== ===========================\n"
+"boolean (b) any object (via bool())\n"
+"byte (y) string of length 1\n"
+" any integer\n"
+"any integer type any integer\n"
+"double (d) any float\n"
+"object path anything with a __dbus_object_path__ attribute\n"
+"string, signature, object path str (must be UTF-8) or unicode\n"
+"dict (a{...}) any mapping\n"
+"array (a...) any iterable over appropriate objects\n"
+"struct ((...)) any iterable over appropriate objects\n"
+"variant any object above (guess type as below)\n"
+"=============================== ===========================\n"
+"\n"
+"Here 'any integer' means anything on which int() or long()\n"
+"(as appropriate) will work, except for basestring subclasses.\n"
+"'Any float' means anything on which float() will work, except\n"
+"for basestring subclasses.\n"
+"\n"
+"If there is no signature, guess from the arguments using\n"
+"the static method `Message.guess_signature`.\n"
+);
+
+char dbus_py_Message_guess_signature__doc__[] = (
+"guess_signature(*args) -> Signature [static method]\n\n"
+"Guess a D-Bus signature which should be used to encode the given\n"
+"Python objects.\n"
+"\n"
+"The signature is constructed as follows:\n\n"
+"+-------------------------------+---------------------------+\n"
+"|Python |D-Bus |\n"
+"+===============================+===========================+\n"
+"|D-Bus type, variant_level > 0 |variant (v) |\n"
+"+-------------------------------+---------------------------+\n"
+"|D-Bus type, variant_level == 0 |the corresponding type |\n"
+"+-------------------------------+---------------------------+\n"
+"|anything with a |object path |\n"
+"|__dbus_object_path__ attribute | |\n"
+"+-------------------------------+---------------------------+\n"
+"|bool |boolean (y) |\n"
+"+-------------------------------+---------------------------+\n"
+"|any other int subclass |int32 (i) |\n"
+"+-------------------------------+---------------------------+\n"
+"|any other long subclass |int64 (x) |\n"
+"+-------------------------------+---------------------------+\n"
+"|any other float subclass |double (d) |\n"
+"+-------------------------------+---------------------------+\n"
+"|any other str subclass |string (s) |\n"
+"+-------------------------------+---------------------------+\n"
+"|any other unicode subclass |string (s) |\n"
+"+-------------------------------+---------------------------+\n"
+"|any other tuple subclass |struct ((...)) |\n"
+"+-------------------------------+---------------------------+\n"
+"|any other list subclass |array (a...), guess |\n"
+"| |contents' type according to|\n"
+"| |type of first item |\n"
+"+-------------------------------+---------------------------+\n"
+"|any other dict subclass |dict (a{...}), guess key, |\n"
+"| |value type according to |\n"
+"| |types for an arbitrary item|\n"
+"+-------------------------------+---------------------------+\n"
+"|anything else |raise TypeError |\n"
+"+-------------------------------+---------------------------+\n"
+);
+
+/* return a new reference, possibly to None */
+static PyObject *
+get_object_path(PyObject *obj)
+{
+ PyObject *magic_attr = PyObject_GetAttr(obj, dbus_py__dbus_object_path__const);
+
+ if (magic_attr) {
+ if (PyUnicode_Check(magic_attr) || PyBytes_Check(magic_attr)) {
+ return magic_attr;
+ }
+ else {
+ Py_CLEAR(magic_attr);
+ PyErr_SetString(PyExc_TypeError, "__dbus_object_path__ must be "
+ "a string");
+ return NULL;
+ }
+ }
+ else {
+ /* Ignore exceptions, except for SystemExit and KeyboardInterrupt */
+ if (PyErr_ExceptionMatches(PyExc_SystemExit) ||
+ PyErr_ExceptionMatches(PyExc_KeyboardInterrupt))
+ return NULL;
+ PyErr_Clear();
+ Py_RETURN_NONE;
+ }
+}
+
+/* Return a new reference. If the object is a variant and variant_level_ptr
+ * is not NULL, put the variant level in the variable pointed to, and
+ * return the contained type instead of "v". */
+static PyObject *
+_signature_string_from_pyobject(PyObject *obj, long *variant_level_ptr)
+{
+ PyObject *magic_attr;
+ long variant_level = get_variant_level(obj);
+
+ if (variant_level < 0)
+ return NULL;
+
+ if (variant_level_ptr) {
+ *variant_level_ptr = variant_level;
+ }
+ else if (variant_level > 0) {
+ return PyUnicode_FromString(DBUS_TYPE_VARIANT_AS_STRING);
+ }
+
+ if (obj == Py_True || obj == Py_False) {
+ return PyUnicode_FromString(DBUS_TYPE_BOOLEAN_AS_STRING);
+ }
+
+ magic_attr = get_object_path(obj);
+ if (!magic_attr)
+ return NULL;
+ if (magic_attr != Py_None) {
+ Py_CLEAR(magic_attr);
+ return PyUnicode_FromString(DBUS_TYPE_OBJECT_PATH_AS_STRING);
+ }
+ Py_CLEAR(magic_attr);
+
+ /* Ordering is important: some of these are subclasses of each other. */
+ if (PyLong_Check(obj)) {
+ if (DBusPyUInt64_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_UINT64_AS_STRING);
+ else if (DBusPyInt64_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_INT64_AS_STRING);
+ else if (DBusPyUInt32_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_UINT32_AS_STRING);
+ else if (DBusPyInt32_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_INT32_AS_STRING);
+ else if (DBusPyUInt16_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_UINT16_AS_STRING);
+ else if (DBusPyInt16_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_INT16_AS_STRING);
+ else if (DBusPyByte_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_BYTE_AS_STRING);
+ else if (DBusPyBoolean_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_BOOLEAN_AS_STRING);
+ else
+ return PyUnicode_FromString(DBUS_TYPE_INT32_AS_STRING);
+ }
+ else if (PyUnicode_Check(obj)) {
+ /* Object paths and signatures are unicode subtypes in Python 3
+ * (the first two cases will never be true in Python 2) */
+ if (DBusPyObjectPath_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_OBJECT_PATH_AS_STRING);
+ else if (DBusPySignature_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_SIGNATURE_AS_STRING);
+ else
+ return PyUnicode_FromString(DBUS_TYPE_STRING_AS_STRING);
+ }
+#if defined(DBUS_TYPE_UNIX_FD)
+ else if (DBusPyUnixFd_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_UNIX_FD_AS_STRING);
+#endif
+ else if (PyFloat_Check(obj)) {
+#ifdef WITH_DBUS_FLOAT32
+ if (DBusPyDouble_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_DOUBLE_AS_STRING);
+ else if (DBusPyFloat_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_FLOAT_AS_STRING);
+ else
+#endif
+ return PyUnicode_FromString(DBUS_TYPE_DOUBLE_AS_STRING);
+ }
+ else if (PyBytes_Check(obj)) {
+ /* Object paths and signatures are bytes subtypes in Python 2
+ * (the first two cases will never be true in Python 3) */
+ if (DBusPyObjectPath_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_OBJECT_PATH_AS_STRING);
+ else if (DBusPySignature_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_SIGNATURE_AS_STRING);
+ else if (DBusPyByteArray_Check(obj))
+ return PyUnicode_FromString(DBUS_TYPE_ARRAY_AS_STRING
+ DBUS_TYPE_BYTE_AS_STRING);
+ else
+ return PyUnicode_FromString(DBUS_TYPE_STRING_AS_STRING);
+ }
+ else if (PyTuple_Check(obj)) {
+ Py_ssize_t len = PyTuple_GET_SIZE(obj);
+ PyObject *list = PyList_New(len + 2); /* new ref */
+ PyObject *item; /* temporary new ref */
+ PyObject *empty_str; /* temporary new ref */
+ PyObject *ret;
+ Py_ssize_t i;
+
+ if (!list) return NULL;
+ if (len == 0) {
+ PyErr_SetString(PyExc_ValueError, "D-Bus structs cannot be empty");
+ Py_CLEAR(list);
+ return NULL;
+ }
+ /* Set the first and last elements of list to be the parentheses */
+ item = PyUnicode_FromString(DBUS_STRUCT_BEGIN_CHAR_AS_STRING);
+ if (PyList_SetItem(list, 0, item) < 0) {
+ Py_CLEAR(list);
+ return NULL;
+ }
+ item = PyUnicode_FromString(DBUS_STRUCT_END_CHAR_AS_STRING);
+ if (PyList_SetItem(list, len + 1, item) < 0) {
+ Py_CLEAR(list);
+ return NULL;
+ }
+ if (!item || !PyList_GET_ITEM(list, 0)) {
+ Py_CLEAR(list);
+ return NULL;
+ }
+ item = NULL;
+
+ for (i = 0; i < len; i++) {
+ item = PyTuple_GetItem(obj, i);
+ if (!item) {
+ Py_CLEAR(list);
+ return NULL;
+ }
+ item = _signature_string_from_pyobject(item, NULL);
+ if (!item) {
+ Py_CLEAR(list);
+ return NULL;
+ }
+ if (PyList_SetItem(list, i + 1, item) < 0) {
+ Py_CLEAR(list);
+ return NULL;
+ }
+ item = NULL;
+ }
+ empty_str = PyUnicode_FromString("");
+ if (!empty_str) {
+ /* really shouldn't happen */
+ Py_CLEAR(list);
+ return NULL;
+ }
+ ret = PyObject_CallMethod(empty_str, "join", "(O)", list); /* new ref */
+ /* whether ret is NULL or not, */
+ Py_CLEAR(empty_str);
+ Py_CLEAR(list);
+ return ret;
+ }
+ else if (PyList_Check(obj)) {
+ PyObject *tmp;
+ PyObject *ret = PyUnicode_FromString(DBUS_TYPE_ARRAY_AS_STRING);
+ if (!ret) return NULL;
+ if (DBusPyArray_Check(obj) &&
+ PyUnicode_Check(((DBusPyArray *)obj)->signature))
+ {
+ PyObject *concat = PyUnicode_Concat(
+ ret, ((DBusPyArray *)obj)->signature);
+ Py_CLEAR(ret);
+ return concat;
+ }
+ if (PyList_GET_SIZE(obj) == 0) {
+ /* No items, so fail. Or should we guess "av"? */
+ PyErr_SetString(PyExc_ValueError, "Unable to guess signature "
+ "from an empty list");
+ return NULL;
+ }
+ tmp = PyList_GetItem(obj, 0);
+ tmp = _signature_string_from_pyobject(tmp, NULL);
+ if (!tmp) return NULL;
+ {
+ PyObject *concat = PyUnicode_Concat(ret, tmp);
+ Py_CLEAR(ret);
+ Py_CLEAR(tmp);
+ return concat;
+ }
+ }
+ else if (PyDict_Check(obj)) {
+ PyObject *key, *value, *keysig, *valuesig;
+ Py_ssize_t pos = 0;
+ PyObject *ret = NULL;
+
+ if (DBusPyDict_Check(obj) &&
+ PyUnicode_Check(((DBusPyDict *)obj)->signature))
+ {
+ return PyUnicode_FromFormat((DBUS_TYPE_ARRAY_AS_STRING
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ "%U"
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING),
+ ((DBusPyDict *)obj)->signature);
+ }
+ if (!PyDict_Next(obj, &pos, &key, &value)) {
+ /* No items, so fail. Or should we guess "a{vv}"? */
+ PyErr_SetString(PyExc_ValueError, "Unable to guess signature "
+ "from an empty dict");
+ return NULL;
+ }
+ keysig = _signature_string_from_pyobject(key, NULL);
+ valuesig = _signature_string_from_pyobject(value, NULL);
+ if (keysig && valuesig) {
+ ret = PyUnicode_FromFormat((DBUS_TYPE_ARRAY_AS_STRING
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ "%U%U"
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING),
+ keysig, valuesig);
+ }
+ Py_CLEAR(keysig);
+ Py_CLEAR(valuesig);
+ return ret;
+ }
+ else {
+ PyErr_Format(PyExc_TypeError, "Don't know which D-Bus type "
+ "to use to encode type \"%s\"",
+ Py_TYPE(obj)->tp_name);
+ return NULL;
+ }
+}
+
+PyObject *
+dbus_py_Message_guess_signature(PyObject *unused UNUSED, PyObject *args)
+{
+ PyObject *tmp, *ret = NULL;
+
+ if (!args) {
+ if (!PyErr_Occurred()) {
+ PyErr_BadInternalCall();
+ }
+ return NULL;
+ }
+
+#ifdef USING_DBG
+ fprintf(stderr, "DBG/%ld: called Message_guess_signature", (long)getpid());
+ PyObject_Print(args, stderr, 0);
+ fprintf(stderr, "\n");
+#endif
+
+ if (!PyTuple_Check(args)) {
+ DBG("%s", "Message_guess_signature: args not a tuple");
+ PyErr_BadInternalCall();
+ return NULL;
+ }
+
+ /* if there were no args, easy */
+ if (PyTuple_GET_SIZE(args) == 0) {
+ DBG("%s", "Message_guess_signature: no args, so return Signature('')");
+ return PyObject_CallFunction((PyObject *)&DBusPySignature_Type, "(s)", "");
+ }
+
+ /* if there were args, the signature we want is, by construction,
+ * exactly the signature we get for the tuple args, except that we don't
+ * want the parentheses. */
+ tmp = _signature_string_from_pyobject(args, NULL);
+ if (!tmp) {
+ DBG("%s", "Message_guess_signature: failed");
+ return NULL;
+ }
+ if (PyUnicode_Check(tmp)) {
+ PyObject *as_bytes = PyUnicode_AsUTF8String(tmp);
+ Py_CLEAR(tmp);
+ if (!as_bytes)
+ return NULL;
+ if (PyBytes_GET_SIZE(as_bytes) < 2) {
+ PyErr_SetString(PyExc_RuntimeError, "Internal error: "
+ "_signature_string_from_pyobject returned "
+ "a bad result");
+ Py_CLEAR(as_bytes);
+ return NULL;
+ }
+ tmp = as_bytes;
+ }
+ if (!PyBytes_Check(tmp) || PyBytes_GET_SIZE(tmp) < 2) {
+ PyErr_SetString(PyExc_RuntimeError, "Internal error: "
+ "_signature_string_from_pyobject returned "
+ "a bad result");
+ Py_CLEAR(tmp);
+ return NULL;
+ }
+ ret = PyObject_CallFunction((PyObject *)&DBusPySignature_Type, "(s#)",
+ PyBytes_AS_STRING(tmp) + 1,
+ PyBytes_GET_SIZE(tmp) - 2);
+ Py_CLEAR(tmp);
+ return ret;
+}
+
+static int _message_iter_append_pyobject(DBusMessageIter *appender,
+ DBusSignatureIter *sig_iter,
+ PyObject *obj,
+ dbus_bool_t *more);
+static int _message_iter_append_variant(DBusMessageIter *appender,
+ PyObject *obj);
+
+static int
+_message_iter_append_string(DBusMessageIter *appender,
+ int sig_type, PyObject *obj,
+ dbus_bool_t allow_object_path_attr)
+{
+ char *s;
+ PyObject *utf8;
+
+ if (sig_type == DBUS_TYPE_OBJECT_PATH && allow_object_path_attr) {
+ PyObject *object_path = get_object_path (obj);
+
+ if (object_path == Py_None) {
+ Py_CLEAR(object_path);
+ }
+ else if (!object_path) {
+ return -1;
+ }
+ else {
+ int ret = _message_iter_append_string(appender, sig_type,
+ object_path, FALSE);
+ Py_CLEAR(object_path);
+ return ret;
+ }
+ }
+
+ if (PyBytes_Check(obj)) {
+ utf8 = obj;
+ Py_INCREF(obj);
+ }
+ else if (PyUnicode_Check(obj)) {
+ utf8 = PyUnicode_AsUTF8String(obj);
+ if (!utf8) return -1;
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ "Expected a string or unicode object");
+ return -1;
+ }
+
+ /* Raise TypeError if the string has embedded NULs */
+ if (PyBytes_AsStringAndSize(utf8, &s, NULL) < 0)
+ return -1;
+
+ /* Validate UTF-8, strictly */
+ if (!dbus_validate_utf8(s, NULL)) {
+ PyErr_SetString(PyExc_UnicodeError, "String parameters "
+ "to be sent over D-Bus must be valid UTF-8 "
+ "with no noncharacter code points");
+ return -1;
+ }
+
+ DBG("Performing actual append: string (from unicode) %s", s);
+ if (!dbus_message_iter_append_basic(appender, sig_type, &s)) {
+ Py_CLEAR(utf8);
+ PyErr_NoMemory();
+ return -1;
+ }
+
+ Py_CLEAR(utf8);
+ return 0;
+}
+
+static int
+_message_iter_append_byte(DBusMessageIter *appender, PyObject *obj)
+{
+ unsigned char y;
+
+ if (PyBytes_Check(obj)) {
+ if (PyBytes_GET_SIZE(obj) != 1) {
+ PyErr_Format(PyExc_ValueError,
+ "Expected a length-1 bytes but found %d bytes",
+ (int)PyBytes_GET_SIZE(obj));
+ return -1;
+ }
+ y = *(unsigned char *)PyBytes_AS_STRING(obj);
+ }
+ else {
+ /* on Python 2 this accepts either int or long */
+ long i = PyLong_AsLong(obj);
+
+ if (i == -1 && PyErr_Occurred()) return -1;
+ if (i < 0 || i > 0xff) {
+ PyErr_Format(PyExc_ValueError,
+ "%d outside range for a byte value",
+ (int)i);
+ return -1;
+ }
+ y = i;
+ }
+ DBG("Performing actual append: byte \\x%02x", (unsigned)y);
+ if (!dbus_message_iter_append_basic(appender, DBUS_TYPE_BYTE, &y)) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ return 0;
+}
+
+static dbus_bool_t
+dbuspy_message_iter_close_container(DBusMessageIter *iter,
+ DBusMessageIter *sub,
+ dbus_bool_t is_ok)
+{
+ if (!is_ok) {
+ dbus_message_iter_abandon_container(iter, sub);
+ return TRUE;
+ }
+ return dbus_message_iter_close_container(iter, sub);
+}
+
+#if defined(DBUS_TYPE_UNIX_FD)
+static int
+_message_iter_append_unixfd(DBusMessageIter *appender, PyObject *obj)
+{
+ int fd;
+ long original_fd;
+
+ if (PyLong_Check(obj))
+ {
+ /* on Python 2 this accepts either int or long */
+ original_fd = PyLong_AsLong(obj);
+ if (original_fd == -1 && PyErr_Occurred())
+ return -1;
+ if (original_fd < INT_MIN || original_fd > INT_MAX) {
+ PyErr_Format(PyExc_ValueError, "out of int range: %ld",
+ original_fd);
+ return -1;
+ }
+ fd = (int)original_fd;
+ }
+ else if (PyObject_IsInstance(obj, (PyObject*) &DBusPyUnixFd_Type)) {
+ fd = dbus_py_unix_fd_get_fd(obj);
+ }
+ else {
+ return -1;
+ }
+
+ DBG("Performing actual append: fd %d", fd);
+ if (!dbus_message_iter_append_basic(appender, DBUS_TYPE_UNIX_FD, &fd)) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ return 0;
+}
+#endif
+
+static int
+_message_iter_append_dictentry(DBusMessageIter *appender,
+ DBusSignatureIter *sig_iter,
+ PyObject *dict, PyObject *key)
+{
+ DBusSignatureIter sub_sig_iter;
+ DBusMessageIter sub;
+ int ret = -1;
+ PyObject *value = PyObject_GetItem(dict, key);
+ dbus_bool_t more;
+
+ if (!value) return -1;
+
+#ifdef USING_DBG
+ fprintf(stderr, "Append dictentry: ");
+ PyObject_Print(key, stderr, 0);
+ fprintf(stderr, " => ");
+ PyObject_Print(value, stderr, 0);
+ fprintf(stderr, "\n");
+#endif
+
+ DBG("Recursing signature iterator %p -> %p", sig_iter, &sub_sig_iter);
+ dbus_signature_iter_recurse(sig_iter, &sub_sig_iter);
+#ifdef USING_DBG
+ {
+ char *s;
+ s = dbus_signature_iter_get_signature(sig_iter);
+ DBG("Signature of parent iterator %p is %s", sig_iter, s);
+ dbus_free(s);
+ s = dbus_signature_iter_get_signature(&sub_sig_iter);
+ DBG("Signature of sub-iterator %p is %s", &sub_sig_iter, s);
+ dbus_free(s);
+ }
+#endif
+
+ DBG("%s", "Opening DICT_ENTRY container");
+ if (!dbus_message_iter_open_container(appender, DBUS_TYPE_DICT_ENTRY,
+ NULL, &sub)) {
+ PyErr_NoMemory();
+ goto out;
+ }
+ ret = _message_iter_append_pyobject(&sub, &sub_sig_iter, key, &more);
+ if (ret == 0) {
+ ret = _message_iter_append_pyobject(&sub, &sub_sig_iter, value, &more);
+ }
+ DBG("%s", "Closing DICT_ENTRY container");
+ if (!dbuspy_message_iter_close_container(appender, &sub, (ret == 0))) {
+ PyErr_NoMemory();
+ ret = -1;
+ }
+out:
+ Py_CLEAR(value);
+ return ret;
+}
+
+static int
+_message_iter_append_multi(DBusMessageIter *appender,
+ const DBusSignatureIter *sig_iter,
+ int mode, PyObject *obj)
+{
+ DBusMessageIter sub_appender;
+ DBusSignatureIter sub_sig_iter;
+ PyObject *contents;
+ int ret;
+ PyObject *iterator = PyObject_GetIter(obj);
+ char *sig = NULL;
+ int container = mode;
+ dbus_bool_t is_byte_array = DBusPyByteArray_Check(obj);
+ int inner_type;
+ dbus_bool_t more;
+
+ assert(mode == DBUS_TYPE_DICT_ENTRY || mode == DBUS_TYPE_ARRAY ||
+ mode == DBUS_TYPE_STRUCT);
+
+#ifdef USING_DBG
+ fprintf(stderr, "Appending multiple: ");
+ PyObject_Print(obj, stderr, 0);
+ fprintf(stderr, "\n");
+#endif
+
+ if (!iterator) return -1;
+ if (mode == DBUS_TYPE_DICT_ENTRY) container = DBUS_TYPE_ARRAY;
+
+ DBG("Recursing signature iterator %p -> %p", sig_iter, &sub_sig_iter);
+ dbus_signature_iter_recurse(sig_iter, &sub_sig_iter);
+#ifdef USING_DBG
+ {
+ char *s;
+ s = dbus_signature_iter_get_signature(sig_iter);
+ DBG("Signature of parent iterator %p is %s", sig_iter, s);
+ dbus_free(s);
+ s = dbus_signature_iter_get_signature(&sub_sig_iter);
+ DBG("Signature of sub-iterator %p is %s", &sub_sig_iter, s);
+ dbus_free(s);
+ }
+#endif
+ inner_type = dbus_signature_iter_get_current_type(&sub_sig_iter);
+
+ if (mode == DBUS_TYPE_ARRAY || mode == DBUS_TYPE_DICT_ENTRY) {
+ sig = dbus_signature_iter_get_signature(&sub_sig_iter);
+ if (!sig) {
+ PyErr_NoMemory();
+ ret = -1;
+ goto out;
+ }
+ }
+ /* else leave sig set to NULL. */
+
+ DBG("Opening '%c' container", container);
+ if (!dbus_message_iter_open_container(appender, container,
+ sig, &sub_appender)) {
+ PyErr_NoMemory();
+ ret = -1;
+ goto out;
+ }
+ ret = 0;
+ more = TRUE;
+ while ((contents = PyIter_Next(iterator))) {
+
+ if (mode == DBUS_TYPE_ARRAY || mode == DBUS_TYPE_DICT_ENTRY) {
+ DBG("Recursing signature iterator %p -> %p", sig_iter, &sub_sig_iter);
+ dbus_signature_iter_recurse(sig_iter, &sub_sig_iter);
+#ifdef USING_DBG
+ {
+ char *s;
+ s = dbus_signature_iter_get_signature(sig_iter);
+ DBG("Signature of parent iterator %p is %s", sig_iter, s);
+ dbus_free(s);
+ s = dbus_signature_iter_get_signature(&sub_sig_iter);
+ DBG("Signature of sub-iterator %p is %s", &sub_sig_iter, s);
+ dbus_free(s);
+ }
+#endif
+ }
+ else /* struct */ {
+ if (!more) {
+ PyErr_Format(PyExc_TypeError, "Fewer items found in struct's "
+ "D-Bus signature than in Python arguments ");
+ ret = -1;
+ break;
+ }
+ }
+
+ if (mode == DBUS_TYPE_DICT_ENTRY) {
+ ret = _message_iter_append_dictentry(&sub_appender, &sub_sig_iter,
+ obj, contents);
+ }
+ else if (mode == DBUS_TYPE_ARRAY && is_byte_array
+ && inner_type == DBUS_TYPE_VARIANT) {
+ /* Subscripting a ByteArray gives a str of length 1, but if the
+ * container is a ByteArray and the parameter is an array of
+ * variants, we want to produce an array of variants containing
+ * bytes, not strings.
+ */
+ PyObject *args = Py_BuildValue("(O)", contents);
+ PyObject *byte;
+
+ if (!args)
+ break;
+ byte = PyObject_Call((PyObject *)&DBusPyByte_Type, args, NULL);
+ Py_CLEAR(args);
+ if (!byte)
+ break;
+ ret = _message_iter_append_variant(&sub_appender, byte);
+ Py_CLEAR(byte);
+ }
+ else {
+ /* advances sub_sig_iter and sets more on success - for array
+ * this doesn't matter, for struct it's essential */
+ ret = _message_iter_append_pyobject(&sub_appender, &sub_sig_iter,
+ contents, &more);
+ }
+
+ Py_CLEAR(contents);
+ if (ret < 0) {
+ break;
+ }
+ }
+
+ if (PyErr_Occurred()) {
+ ret = -1;
+ }
+ else if (mode == DBUS_TYPE_STRUCT && more) {
+ PyErr_Format(PyExc_TypeError, "More items found in struct's D-Bus "
+ "signature than in Python arguments ");
+ ret = -1;
+ }
+
+ /* This must be run as cleanup, even on failure. */
+ DBG("Closing '%c' container", container);
+ if (!dbuspy_message_iter_close_container(appender, &sub_appender, (ret == 0))) {
+ PyErr_NoMemory();
+ ret = -1;
+ }
+
+out:
+ Py_CLEAR(iterator);
+ dbus_free(sig);
+ return ret;
+}
+
+static int
+_message_iter_append_string_as_byte_array(DBusMessageIter *appender,
+ PyObject *obj)
+{
+ /* a bit of a faster path for byte arrays that are strings */
+ Py_ssize_t len = PyBytes_GET_SIZE(obj);
+ const char *s;
+ DBusMessageIter sub;
+ int ret;
+
+ s = PyBytes_AS_STRING(obj);
+ DBG("%s", "Opening ARRAY container");
+ if (!dbus_message_iter_open_container(appender, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_BYTE_AS_STRING, &sub)) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ DBG("Appending fixed array of %d bytes", (int)len);
+ if (dbus_message_iter_append_fixed_array(&sub, DBUS_TYPE_BYTE, &s, len)) {
+ ret = 0;
+ }
+ else {
+ PyErr_NoMemory();
+ ret = -1;
+ }
+ DBG("%s", "Closing ARRAY container");
+ if (!dbus_message_iter_close_container(appender, &sub)) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ return ret;
+}
+
+/* Encode some Python object into a D-Bus variant slot. */
+static int
+_message_iter_append_variant(DBusMessageIter *appender, PyObject *obj)
+{
+ DBusSignatureIter obj_sig_iter;
+ const char *obj_sig_str;
+ PyObject *obj_sig;
+ int ret;
+ long variant_level;
+ dbus_bool_t dummy;
+ DBusMessageIter *variant_iters = NULL;
+
+ /* Separate the object into the contained object, and the number of
+ * variants it's wrapped in. */
+ obj_sig = _signature_string_from_pyobject(obj, &variant_level);
+ if (!obj_sig) return -1;
+
+ if (PyUnicode_Check(obj_sig)) {
+ PyObject *obj_sig_as_bytes = PyUnicode_AsUTF8String(obj_sig);
+ Py_CLEAR(obj_sig);
+ if (!obj_sig_as_bytes)
+ return -1;
+ obj_sig = obj_sig_as_bytes;
+ }
+ obj_sig_str = PyBytes_AsString(obj_sig);
+ if (!obj_sig_str) {
+ Py_CLEAR(obj_sig);
+ return -1;
+ }
+
+ if (variant_level < 1) {
+ variant_level = 1;
+ }
+
+ dbus_signature_iter_init(&obj_sig_iter, obj_sig_str);
+
+ {
+ long i;
+
+ variant_iters = calloc (variant_level, sizeof (DBusMessageIter));
+
+ if (!variant_iters) {
+ PyErr_NoMemory();
+ ret = -1;
+ goto out;
+ }
+
+ for (i = 0; i < variant_level; i++) {
+ DBusMessageIter *child = &variant_iters[i];
+ /* The first is a special case: its parent is the iter passed in
+ * to this function, instead of being the previous one in the
+ * stack
+ */
+ DBusMessageIter *parent = (i == 0
+ ? appender
+ : &(variant_iters[i-1]));
+ /* The last is also a special case: it contains the actual
+ * object, rather than another variant
+ */
+ const char *sig_str = (i == variant_level-1
+ ? obj_sig_str
+ : DBUS_TYPE_VARIANT_AS_STRING);
+
+ DBG("Opening VARIANT container %p inside %p containing '%s'",
+ child, parent, sig_str);
+ if (!dbus_message_iter_open_container(parent, DBUS_TYPE_VARIANT,
+ sig_str, child)) {
+ PyErr_NoMemory();
+ ret = -1;
+ goto out;
+ }
+ }
+
+ /* Put the object itself into the innermost variant */
+ ret = _message_iter_append_pyobject(&variant_iters[variant_level-1],
+ &obj_sig_iter, obj, &dummy);
+
+ /* here we rely on i (and variant_level) being a signed long */
+ for (i = variant_level - 1; i >= 0; i--) {
+ DBusMessageIter *child = &variant_iters[i];
+ /* The first is a special case: its parent is the iter passed in
+ * to this function, instead of being the previous one in the
+ * stack
+ */
+ DBusMessageIter *parent = (i == 0 ? appender
+ : &(variant_iters[i-1]));
+
+ DBG("Closing VARIANT container %p inside %p", child, parent);
+ if (!dbus_message_iter_close_container(parent, child)) {
+ PyErr_NoMemory();
+ ret = -1;
+ goto out;
+ }
+ }
+ }
+
+out:
+ if (variant_iters != NULL)
+ free (variant_iters);
+
+ Py_CLEAR(obj_sig);
+ return ret;
+}
+
+/* On success, *more is set to whether there's more in the signature. */
+static int
+_message_iter_append_pyobject(DBusMessageIter *appender,
+ DBusSignatureIter *sig_iter,
+ PyObject *obj,
+ dbus_bool_t *more)
+{
+ int sig_type = dbus_signature_iter_get_current_type(sig_iter);
+ DBusBasicValue u;
+ int ret = -1;
+
+#ifdef USING_DBG
+ fprintf(stderr, "Appending object at %p: ", obj);
+ PyObject_Print(obj, stderr, 0);
+ fprintf(stderr, " into appender at %p, dbus wants type %c\n",
+ appender, sig_type);
+#endif
+
+ switch (sig_type) {
+ /* The numeric types are relatively simple to deal with, so are
+ * inlined here. */
+
+ case DBUS_TYPE_BOOLEAN:
+ if (PyObject_IsTrue(obj)) {
+ u.bool_val = 1;
+ }
+ else {
+ u.bool_val = 0;
+ }
+ DBG("Performing actual append: bool(%ld)", (long)u.bool_val);
+ if (!dbus_message_iter_append_basic(appender, sig_type, &u.bool_val)) {
+ PyErr_NoMemory();
+ ret = -1;
+ break;
+ }
+ ret = 0;
+ break;
+
+ case DBUS_TYPE_DOUBLE:
+ u.dbl = PyFloat_AsDouble(obj);
+ if (PyErr_Occurred()) {
+ ret = -1;
+ break;
+ }
+ DBG("Performing actual append: double(%f)", u.dbl);
+ if (!dbus_message_iter_append_basic(appender, sig_type, &u.dbl)) {
+ PyErr_NoMemory();
+ ret = -1;
+ break;
+ }
+ ret = 0;
+ break;
+
+#ifdef WITH_DBUS_FLOAT32
+ case DBUS_TYPE_FLOAT:
+ u.dbl = PyFloat_AsDouble(obj);
+ if (PyErr_Occurred()) {
+ ret = -1;
+ break;
+ }
+ /* FIXME: DBusBasicValue will need to grow a float member if
+ * float32 becomes supported */
+ u.f = (float)u.dbl;
+ DBG("Performing actual append: float(%f)", u.f);
+ if (!dbus_message_iter_append_basic(appender, sig_type, &u.f)) {
+ PyErr_NoMemory();
+ ret = -1;
+ break;
+ }
+ ret = 0;
+ break;
+#endif
+
+ /* The integer types are all basically the same - we delegate to
+ intNN_range_check() */
+#define PROCESS_INTEGER(size, member) \
+ u.member = dbus_py_##size##_range_check(obj);\
+ if (u.member == (dbus_##size##_t)(-1) && PyErr_Occurred()) {\
+ ret = -1; \
+ break; \
+ }\
+ DBG("Performing actual append: " #size "(%lld)", (long long)u.member); \
+ if (!dbus_message_iter_append_basic(appender, sig_type, &u.member)) {\
+ PyErr_NoMemory();\
+ ret = -1;\
+ break;\
+ } \
+ ret = 0;
+
+ case DBUS_TYPE_INT16:
+ PROCESS_INTEGER(int16, i16)
+ break;
+ case DBUS_TYPE_UINT16:
+ PROCESS_INTEGER(uint16, u16)
+ break;
+ case DBUS_TYPE_INT32:
+ PROCESS_INTEGER(int32, i32)
+ break;
+ case DBUS_TYPE_UINT32:
+ PROCESS_INTEGER(uint32, u32)
+ break;
+#if defined(DBUS_HAVE_INT64) && defined(HAVE_LONG_LONG)
+ case DBUS_TYPE_INT64:
+ PROCESS_INTEGER(int64, i64)
+ break;
+ case DBUS_TYPE_UINT64:
+ PROCESS_INTEGER(uint64, u64)
+ break;
+#else
+ case DBUS_TYPE_INT64:
+ case DBUS_TYPE_UINT64:
+ PyErr_SetString(PyExc_NotImplementedError, "64-bit integer "
+ "types are not supported on this platform");
+ ret = -1;
+ break;
+#endif
+#undef PROCESS_INTEGER
+
+ /* Now the more complicated cases, which are delegated to helper
+ * functions (although in practice, the compiler will hopefully
+ * inline them anyway). */
+
+ case DBUS_TYPE_STRING:
+ case DBUS_TYPE_SIGNATURE:
+ case DBUS_TYPE_OBJECT_PATH:
+ ret = _message_iter_append_string(appender, sig_type, obj, TRUE);
+ break;
+
+ case DBUS_TYPE_BYTE:
+ ret = _message_iter_append_byte(appender, obj);
+ break;
+
+ case DBUS_TYPE_ARRAY:
+ /* 3 cases - it might actually be a dict, or it might be a byte array
+ * being copied from a string (for which we have a faster path),
+ * or it might be a generic array. */
+
+ sig_type = dbus_signature_iter_get_element_type(sig_iter);
+ if (sig_type == DBUS_TYPE_DICT_ENTRY)
+ ret = _message_iter_append_multi(appender, sig_iter,
+ DBUS_TYPE_DICT_ENTRY, obj);
+ else if (sig_type == DBUS_TYPE_BYTE && PyBytes_Check(obj))
+ ret = _message_iter_append_string_as_byte_array(appender, obj);
+ else
+ ret = _message_iter_append_multi(appender, sig_iter,
+ DBUS_TYPE_ARRAY, obj);
+ DBG("_message_iter_append_multi(): %d", ret);
+ break;
+
+ case DBUS_TYPE_STRUCT:
+ ret = _message_iter_append_multi(appender, sig_iter, sig_type, obj);
+ break;
+
+ case DBUS_TYPE_VARIANT:
+ ret = _message_iter_append_variant(appender, obj);
+ break;
+
+ case DBUS_TYPE_INVALID:
+ PyErr_SetString(PyExc_TypeError, "Fewer items found in D-Bus "
+ "signature than in Python arguments");
+ ret = -1;
+ break;
+
+#if defined(DBUS_TYPE_UNIX_FD)
+ case DBUS_TYPE_UNIX_FD:
+ ret = _message_iter_append_unixfd(appender, obj);
+ break;
+#endif
+
+ default:
+ PyErr_Format(PyExc_TypeError, "Unknown type '\\x%x' in D-Bus "
+ "signature", sig_type);
+ ret = -1;
+ break;
+ }
+ if (ret < 0) return -1;
+
+ DBG("Advancing signature iter at %p", sig_iter);
+ *more = dbus_signature_iter_next(sig_iter);
+#ifdef USING_DBG
+ DBG("- result: %ld, type %02x '%c'", (long)(*more),
+ (int)dbus_signature_iter_get_current_type(sig_iter),
+ (int)dbus_signature_iter_get_current_type(sig_iter));
+#endif
+ return 0;
+}
+
+
+PyObject *
+dbus_py_Message_append(Message *self, PyObject *args, PyObject *kwargs)
+{
+ const char *signature = NULL;
+ PyObject *signature_obj = NULL;
+ DBusSignatureIter sig_iter;
+ DBusMessageIter appender;
+ static char *argnames[] = {"signature", NULL};
+ dbus_bool_t more;
+
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+
+#ifdef USING_DBG
+ fprintf(stderr, "DBG/%ld: called Message_append(*", (long)getpid());
+ PyObject_Print(args, stderr, 0);
+ if (kwargs) {
+ fprintf(stderr, ", **");
+ PyObject_Print(kwargs, stderr, 0);
+ }
+ fprintf(stderr, ")\n");
+#endif
+
+ /* only use kwargs for this step: deliberately ignore args for now */
+ if (!PyArg_ParseTupleAndKeywords(dbus_py_empty_tuple, kwargs, "|z:append",
+ argnames, &signature)) return NULL;
+
+ if (!signature) {
+ DBG("%s", "No signature for message, guessing...");
+ signature_obj = dbus_py_Message_guess_signature(NULL, args);
+ if (!signature_obj) return NULL;
+ if (PyUnicode_Check(signature_obj)) {
+ PyObject *signature_as_bytes;
+ signature_as_bytes = PyUnicode_AsUTF8String(signature_obj);
+ Py_CLEAR(signature_obj);
+ if (!signature_as_bytes)
+ return NULL;
+ signature_obj = signature_as_bytes;
+ }
+ else {
+ assert(PyBytes_Check(signature_obj));
+ }
+ signature = PyBytes_AS_STRING(signature_obj);
+ }
+ /* from here onwards, you have to do a goto rather than returning NULL
+ to make sure signature_obj gets freed */
+
+ /* iterate over args and the signature, together */
+ if (!dbus_signature_validate(signature, NULL)) {
+ PyErr_SetString(PyExc_ValueError, "Corrupt type signature");
+ goto err;
+ }
+ dbus_message_iter_init_append(self->msg, &appender);
+
+ if (signature[0] != '\0') {
+ int i = 0;
+
+ more = TRUE;
+ dbus_signature_iter_init(&sig_iter, signature);
+ while (more) {
+ if (i >= PyTuple_GET_SIZE(args)) {
+ PyErr_SetString(PyExc_TypeError, "More items found in D-Bus "
+ "signature than in Python arguments");
+ goto hosed;
+ }
+ if (_message_iter_append_pyobject(&appender, &sig_iter,
+ PyTuple_GET_ITEM(args, i),
+ &more) < 0) {
+ goto hosed;
+ }
+ i++;
+ }
+ if (i < PyTuple_GET_SIZE(args)) {
+ PyErr_SetString(PyExc_TypeError, "Fewer items found in D-Bus "
+ "signature than in Python arguments");
+ goto hosed;
+ }
+ }
+
+ /* success! */
+ Py_CLEAR(signature_obj);
+ Py_RETURN_NONE;
+
+hosed:
+ /* "If appending any of the arguments fails due to lack of memory,
+ * generally the message is hosed and you have to start over" -libdbus docs
+ * Enforce this by throwing away the message structure.
+ */
+ dbus_message_unref(self->msg);
+ self->msg = NULL;
+err:
+ Py_CLEAR(signature_obj);
+ return NULL;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/message-get-args.c b/dbus_bindings/message-get-args.c
new file mode 100644
index 0000000..6dad272
--- /dev/null
+++ b/dbus_bindings/message-get-args.c
@@ -0,0 +1,524 @@
+/* D-Bus Message unserialization. This contains all the logic to map from
+ * D-Bus types to Python objects.
+ *
+ * Copyright (C) 2006-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#define DBG_IS_TOO_VERBOSE
+#include "compat-internal.h"
+#include "types-internal.h"
+#include "message-internal.h"
+
+char dbus_py_Message_get_args_list__doc__[] = (
+"get_args_list(**kwargs) -> list\n\n"
+"Return the message's arguments. Keyword arguments control the translation\n"
+"of D-Bus types to Python:\n"
+"\n"
+":Keywords:\n"
+" `byte_arrays` : bool\n"
+" If true, convert arrays of byte (signature 'ay') into dbus.ByteArray,\n"
+" a str subclass. In practice, this is usually what you want, but\n"
+" it's off by default for consistency.\n"
+"\n"
+" If false (default), convert them into a dbus.Array of Bytes.\n"
+"\n"
+"Most of the type mappings should be fairly obvious:\n"
+"\n"
+"=============== ===================================================\n"
+"D-Bus Python\n"
+"=============== ===================================================\n"
+"byte (y) dbus.Byte (int subclass)\n"
+"bool (b) dbus.Boolean (int subclass)\n"
+"Signature (g) dbus.Signature (str subclass)\n"
+"intNN, uintNN dbus.IntNN, dbus.UIntNN (int or long subclasses)\n"
+"double (d) dbus.Double\n"
+"string (s) dbus.String (unicode subclass)\n"
+" (or dbus.UTF8String, bytes subclass, if utf8_strings set)\n"
+"Object path (o) dbus.ObjectPath (str subclass)\n"
+"dict (a{...}) dbus.Dictionary\n"
+"array (a...) dbus.Array (list subclass) containing appropriate types\n"
+"byte array (ay) dbus.ByteArray (str subclass) if byte_arrays set; or\n"
+" list of Byte\n"
+"struct ((...)) dbus.Struct (tuple subclass) of appropriate types\n"
+"variant (v) contained type, but with variant_level > 0\n"
+"=============== ===================================================\n"
+);
+
+typedef struct {
+ int byte_arrays;
+} Message_get_args_options;
+
+static PyObject *_message_iter_get_pyobject(DBusMessageIter *iter,
+ Message_get_args_options *opts,
+ long extra_variants);
+
+/* Append all the items iterated over to the given Python list object.
+ * Return 0 on success/-1 with exception on failure. */
+static int
+_message_iter_append_all_to_list(DBusMessageIter *iter, PyObject *list,
+ Message_get_args_options *opts)
+{
+ int ret, type;
+ while ((type = dbus_message_iter_get_arg_type(iter))
+ != DBUS_TYPE_INVALID) {
+ PyObject *item;
+ DBG("type == %d '%c'", type, type);
+
+ item = _message_iter_get_pyobject(iter, opts, 0);
+ if (!item) return -1;
+#ifdef USING_DBG
+ fprintf(stderr, "DBG/%ld: appending to list: %p == ", (long)getpid(), item);
+ PyObject_Print(item, stderr, 0);
+ fprintf(stderr, " of type %p\n", Py_TYPE(item));
+#endif
+ ret = PyList_Append(list, item);
+ Py_CLEAR(item);
+ if (ret < 0) return -1;
+#ifdef USING_DBG
+ fprintf(stderr, "DBG/%ld: list now contains: ", (long)getpid());
+ PyObject_Print(list, stderr, 0);
+ fprintf(stderr, "\n");
+#endif
+ dbus_message_iter_next(iter);
+ }
+ return 0;
+}
+
+static inline PyObject *
+_message_iter_get_dict(DBusMessageIter *iter,
+ Message_get_args_options *opts,
+ PyObject *kwargs)
+{
+ DBusMessageIter entries;
+ char *sig_str = dbus_message_iter_get_signature(iter);
+ PyObject *sig;
+ PyObject *ret;
+ int status;
+
+ if (!sig_str) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ sig = PyObject_CallFunction((PyObject *)&DBusPySignature_Type,
+ "(s#)", sig_str+2,
+ (Py_ssize_t)strlen(sig_str)-3);
+ dbus_free(sig_str);
+ if (!sig) {
+ return NULL;
+ }
+ status = PyDict_SetItem(kwargs, dbus_py_signature_const, sig);
+ Py_CLEAR(sig);
+ if (status < 0) {
+ return NULL;
+ }
+
+ ret = PyObject_Call((PyObject *)&DBusPyDict_Type, dbus_py_empty_tuple, kwargs);
+ if (!ret) {
+ return NULL;
+ }
+
+ dbus_message_iter_recurse(iter, &entries);
+ while (dbus_message_iter_get_arg_type(&entries) == DBUS_TYPE_DICT_ENTRY) {
+ PyObject *key = NULL;
+ PyObject *value = NULL;
+ DBusMessageIter kv;
+
+ DBG("%s", "dict entry...");
+
+ dbus_message_iter_recurse(&entries, &kv);
+
+ key = _message_iter_get_pyobject(&kv, opts, 0);
+ if (!key) {
+ Py_CLEAR(ret);
+ return NULL;
+ }
+ dbus_message_iter_next(&kv);
+
+ value = _message_iter_get_pyobject(&kv, opts, 0);
+ if (!value) {
+ Py_CLEAR(key);
+ Py_CLEAR(ret);
+ return NULL;
+ }
+
+ status = PyDict_SetItem(ret, key, value);
+ Py_CLEAR(key);
+ Py_CLEAR(value);
+
+ if (status < 0) {
+ Py_CLEAR(ret);
+ return NULL;
+ }
+ dbus_message_iter_next(&entries);
+ }
+
+ return ret;
+}
+
+/* Returns a new reference. */
+static PyObject *
+_message_iter_get_pyobject(DBusMessageIter *iter,
+ Message_get_args_options *opts,
+ long variant_level)
+{
+ DBusBasicValue u;
+ int type = dbus_message_iter_get_arg_type(iter);
+ PyObject *args = NULL;
+ PyObject *kwargs = NULL;
+ PyObject *ret = NULL;
+
+ /* If the variant-level is >0, prepare a dict for the kwargs.
+ * For variant wrappers optimize slightly by skipping this.
+ */
+ if (variant_level > 0 && type != DBUS_TYPE_VARIANT) {
+ PyObject *variant_level_int;
+
+ variant_level_int = PyLong_FromLong(variant_level);
+ if (!variant_level_int) {
+ return NULL;
+ }
+ kwargs = PyDict_New();
+ if (!kwargs) {
+ Py_CLEAR(variant_level_int);
+ return NULL;
+ }
+ if (PyDict_SetItem(kwargs, dbus_py_variant_level_const,
+ variant_level_int) < 0) {
+ Py_CLEAR(variant_level_int);
+ Py_CLEAR(kwargs);
+ return NULL;
+ }
+ Py_CLEAR(variant_level_int);
+ }
+ /* From here down you need to break from the switch to exit, so the
+ * dict is freed if necessary
+ */
+
+ switch (type) {
+ PyObject *unicode;
+
+ case DBUS_TYPE_STRING:
+ DBG("%s", "found a string");
+ dbus_message_iter_get_basic(iter, &u.str);
+ unicode = PyUnicode_DecodeUTF8(u.str, strlen(u.str), NULL);
+ if (!unicode) {
+ break;
+ }
+ args = Py_BuildValue("(N)", unicode);
+ if (!args) {
+ break;
+ }
+ ret = PyObject_Call((PyObject *)&DBusPyString_Type,
+ args, kwargs);
+ break;
+
+ case DBUS_TYPE_SIGNATURE:
+ DBG("%s", "found a signature");
+ dbus_message_iter_get_basic(iter, &u.str);
+ args = Py_BuildValue("(s)", u.str);
+ if (!args) break;
+ ret = PyObject_Call((PyObject *)&DBusPySignature_Type, args, kwargs);
+ break;
+
+ case DBUS_TYPE_OBJECT_PATH:
+ DBG("%s", "found an object path");
+ dbus_message_iter_get_basic(iter, &u.str);
+ args = Py_BuildValue("(s)", u.str);
+ if (!args) break;
+ ret = PyObject_Call((PyObject *)&DBusPyObjectPath_Type, args, kwargs);
+ break;
+
+ case DBUS_TYPE_DOUBLE:
+ DBG("%s", "found a double");
+ dbus_message_iter_get_basic(iter, &u.dbl);
+ args = Py_BuildValue("(f)", u.dbl);
+ if (!args) break;
+ ret = PyObject_Call((PyObject *)&DBusPyDouble_Type, args, kwargs);
+ break;
+
+#ifdef WITH_DBUS_FLOAT32
+ case DBUS_TYPE_FLOAT:
+ DBG("%s", "found a float");
+ /* FIXME: DBusBasicValue will need to grow a float member if
+ * float32 becomes supported */
+ dbus_message_iter_get_basic(iter, &u.f);
+ args = Py_BuildValue("(f)", (double)u.f);
+ if (!args) break;
+ ret = PyObject_Call((PyObject *)&DBusPyFloat_Type, args, kwargs);
+ break;
+#endif
+
+ case DBUS_TYPE_INT16:
+ DBG("%s", "found an int16");
+ dbus_message_iter_get_basic(iter, &u.i16);
+ args = Py_BuildValue("(i)", (int)u.i16);
+ if (!args) break;
+ ret = PyObject_Call((PyObject *)&DBusPyInt16_Type, args, kwargs);
+ break;
+
+ case DBUS_TYPE_UINT16:
+ DBG("%s", "found a uint16");
+ dbus_message_iter_get_basic(iter, &u.u16);
+ args = Py_BuildValue("(i)", (int)u.u16);
+ if (!args) break;
+ ret = PyObject_Call((PyObject *)&DBusPyUInt16_Type, args, kwargs);
+ break;
+
+ case DBUS_TYPE_INT32:
+ DBG("%s", "found an int32");
+ dbus_message_iter_get_basic(iter, &u.i32);
+ args = Py_BuildValue("(l)", (long)u.i32);
+ if (!args) break;
+ ret = PyObject_Call((PyObject *)&DBusPyInt32_Type, args, kwargs);
+ break;
+
+ case DBUS_TYPE_UINT32:
+ DBG("%s", "found a uint32");
+ dbus_message_iter_get_basic(iter, &u.u32);
+ args = Py_BuildValue("(k)", (unsigned long)u.u32);
+ if (!args) break;
+ ret = PyObject_Call((PyObject *)&DBusPyUInt32_Type, args, kwargs);
+ break;
+
+#ifdef DBUS_TYPE_UNIX_FD
+ case DBUS_TYPE_UNIX_FD:
+ DBG("%s", "found an unix fd");
+ dbus_message_iter_get_basic(iter, &u.fd);
+ args = Py_BuildValue("(i)", u.fd);
+ if (args) {
+ ret = PyObject_Call((PyObject *)&DBusPyUnixFd_Type, args,
+ kwargs);
+ }
+ if (u.fd >= 0) {
+ close(u.fd);
+ }
+ break;
+#endif
+
+#if defined(DBUS_HAVE_INT64) && defined(HAVE_LONG_LONG)
+ case DBUS_TYPE_INT64:
+ DBG("%s", "found an int64");
+ dbus_message_iter_get_basic(iter, &u.i64);
+ args = Py_BuildValue("(L)", (PY_LONG_LONG)u.i64);
+ if (!args) break;
+ ret = PyObject_Call((PyObject *)&DBusPyInt64_Type, args, kwargs);
+ break;
+
+ case DBUS_TYPE_UINT64:
+ DBG("%s", "found a uint64");
+ dbus_message_iter_get_basic(iter, &u.u64);
+ args = Py_BuildValue("(K)", (unsigned PY_LONG_LONG)u.u64);
+ if (!args) break;
+ ret = PyObject_Call((PyObject *)&DBusPyUInt64_Type, args, kwargs);
+ break;
+#else
+ case DBUS_TYPE_INT64:
+ case DBUS_TYPE_UINT64:
+ PyErr_SetString(PyExc_NotImplementedError,
+ "64-bit integer types are not supported on "
+ "this platform");
+ break;
+#endif
+
+ case DBUS_TYPE_BYTE:
+ DBG("%s", "found a byte");
+ dbus_message_iter_get_basic(iter, &u.byt);
+ args = Py_BuildValue("(l)", (long)u.byt);
+ if (!args)
+ break;
+ ret = PyObject_Call((PyObject *)&DBusPyByte_Type, args, kwargs);
+ break;
+
+ case DBUS_TYPE_BOOLEAN:
+ DBG("%s", "found a bool");
+ dbus_message_iter_get_basic(iter, &u.bool_val);
+ args = Py_BuildValue("(l)", (long)u.bool_val);
+ if (!args)
+ break;
+ ret = PyObject_Call((PyObject *)&DBusPyBoolean_Type, args, kwargs);
+ break;
+
+ case DBUS_TYPE_ARRAY:
+ DBG("%s", "found an array...");
+ /* Dicts are arrays of DBUS_TYPE_DICT_ENTRY on the wire.
+ Also, we special-case arrays of DBUS_TYPE_BYTE sometimes. */
+ type = dbus_message_iter_get_element_type(iter);
+ if (type == DBUS_TYPE_DICT_ENTRY) {
+ DBG("%s", "no, actually it's a dict...");
+ if (!kwargs) {
+ kwargs = PyDict_New();
+ if (!kwargs) break;
+ }
+ ret = _message_iter_get_dict(iter, opts, kwargs);
+ }
+ else if (opts->byte_arrays && type == DBUS_TYPE_BYTE) {
+ DBusMessageIter sub;
+ int n;
+
+ DBG("%s", "actually, a byte array...");
+ dbus_message_iter_recurse(iter, &sub);
+ dbus_message_iter_get_fixed_array(&sub,
+ (const unsigned char **)&u.str,
+ &n);
+ if (n == 0 && u.str == NULL) {
+ /* fd.o #21831: s# turns (NULL, 0) into None, but
+ * dbus_message_iter_get_fixed_array produces (NULL, 0)
+ * for an empty byte-blob... */
+ u.str = "";
+ }
+ args = Py_BuildValue("(y#)", u.str, (Py_ssize_t)n);
+ if (!args) break;
+ ret = PyObject_Call((PyObject *)&DBusPyByteArray_Type,
+ args, kwargs);
+ }
+ else {
+ DBusMessageIter sub;
+ char *sig;
+ PyObject *sig_obj;
+ int status;
+
+ DBG("%s", "a normal array...");
+ if (!kwargs) {
+ kwargs = PyDict_New();
+ if (!kwargs) break;
+ }
+ dbus_message_iter_recurse(iter, &sub);
+ sig = dbus_message_iter_get_signature(&sub);
+ if (!sig) break;
+ sig_obj = PyObject_CallFunction((PyObject *)&DBusPySignature_Type,
+ "(s)", sig);
+ dbus_free(sig);
+ if (!sig_obj) break;
+ status = PyDict_SetItem(kwargs, dbus_py_signature_const, sig_obj);
+ Py_CLEAR(sig_obj);
+ if (status < 0) break;
+ ret = PyObject_Call((PyObject *)&DBusPyArray_Type,
+ dbus_py_empty_tuple, kwargs);
+ if (!ret) break;
+ if (_message_iter_append_all_to_list(&sub, ret, opts) < 0) {
+ Py_CLEAR(ret);
+ }
+ }
+ break;
+
+ case DBUS_TYPE_STRUCT:
+ {
+ DBusMessageIter sub;
+ PyObject *list = PyList_New(0);
+ PyObject *tuple;
+
+ DBG("%s", "found a struct...");
+ if (!list) break;
+ dbus_message_iter_recurse(iter, &sub);
+ if (_message_iter_append_all_to_list(&sub, list, opts) < 0) {
+ Py_CLEAR(list);
+ break;
+ }
+ tuple = Py_BuildValue("(O)", list);
+ if (tuple) {
+ ret = PyObject_Call((PyObject *)&DBusPyStruct_Type, tuple, kwargs);
+ }
+ else {
+ ret = NULL;
+ }
+ /* whether successful or not, we take the same action: */
+ Py_CLEAR(list);
+ Py_CLEAR(tuple);
+ }
+ break;
+
+ case DBUS_TYPE_VARIANT:
+ {
+ DBusMessageIter sub;
+
+ DBG("%s", "found a variant...");
+ dbus_message_iter_recurse(iter, &sub);
+ ret = _message_iter_get_pyobject(&sub, opts, variant_level+1);
+ }
+ break;
+
+ default:
+ PyErr_Format(PyExc_TypeError, "Unknown type '\\%x' in D-Bus "
+ "message", type);
+ }
+
+ Py_CLEAR(args);
+ Py_CLEAR(kwargs);
+ return ret;
+}
+
+PyObject *
+dbus_py_Message_get_args_list(Message *self, PyObject *args, PyObject *kwargs)
+{
+ Message_get_args_options opts = { 0 };
+ static char *argnames[] = { "byte_arrays", NULL };
+ PyObject *list;
+ DBusMessageIter iter;
+
+#ifdef USING_DBG
+ fprintf(stderr, "DBG/%ld: called Message_get_args_list(self, *",
+ (long)getpid());
+ PyObject_Print(args, stderr, 0);
+ if (kwargs) {
+ fprintf(stderr, ", **");
+ PyObject_Print(kwargs, stderr, 0);
+ }
+ fprintf(stderr, ")\n");
+#endif
+
+ if (PyTuple_Size(args) != 0) {
+ PyErr_SetString(PyExc_TypeError, "get_args_list takes no positional "
+ "arguments");
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i:get_args_list",
+ argnames,
+ &(opts.byte_arrays))) return NULL;
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+
+ list = PyList_New(0);
+ if (!list) return NULL;
+
+ /* Iterate over args, if any, appending to list */
+ if (dbus_message_iter_init(self->msg, &iter)) {
+ if (_message_iter_append_all_to_list(&iter, list, &opts) < 0) {
+ Py_CLEAR(list);
+ DBG_EXC("%s", "Message_get_args: appending all to list failed:");
+ return NULL;
+ }
+ }
+
+#ifdef USING_DBG
+ fprintf(stderr, "DBG/%ld: message has args list ", (long)getpid());
+ PyObject_Print(list, stderr, 0);
+ fprintf(stderr, "\n");
+#endif
+
+ return list;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/message-internal.h b/dbus_bindings/message-internal.h
new file mode 100644
index 0000000..0d99cf3
--- /dev/null
+++ b/dbus_bindings/message-internal.h
@@ -0,0 +1,51 @@
+/* D-Bus message: implementation internals
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef DBUS_BINDINGS_MESSAGE_INTERNAL_H
+#define DBUS_BINDINGS_MESSAGE_INTERNAL_H
+
+#include "dbus_bindings-internal.h"
+
+#include <Python.h>
+
+typedef struct {
+ PyObject_HEAD
+ DBusMessage *msg;
+} Message;
+
+extern char dbus_py_Message_append__doc__[];
+extern PyObject *dbus_py_Message_append(Message *, PyObject *, PyObject *);
+extern char dbus_py_Message_guess_signature__doc__[];
+extern PyObject *dbus_py_Message_guess_signature(PyObject *, PyObject *);
+extern char dbus_py_Message_get_args_list__doc__[];
+extern PyObject *dbus_py_Message_get_args_list(Message *,
+ PyObject *,
+ PyObject *);
+
+extern PyObject *DBusPy_RaiseUnusableMessage(void);
+
+#endif
diff --git a/dbus_bindings/message.c b/dbus_bindings/message.c
new file mode 100644
index 0000000..5162c11
--- /dev/null
+++ b/dbus_bindings/message.c
@@ -0,0 +1,1142 @@
+/* Implementation of D-Bus Message and subclasses (but see message-get-args.h
+ * and message-append.h for unserialization and serialization code).
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+#include "message-internal.h"
+
+static PyTypeObject MessageType, SignalMessageType, ErrorMessageType;
+static PyTypeObject MethodReturnMessageType, MethodCallMessageType;
+
+static inline int Message_Check(PyObject *o)
+{
+ return (Py_TYPE(o) == &MessageType)
+ || PyObject_IsInstance(o, (PyObject *)&MessageType);
+}
+
+PyObject *
+DBusPy_RaiseUnusableMessage(void)
+{
+ DBusPyException_SetString("Message object is uninitialized, or has become "
+ "unusable due to error while appending "
+ "arguments");
+ return NULL;
+}
+
+PyDoc_STRVAR(Message_tp_doc,
+"A message to be sent or received over a D-Bus Connection.\n");
+
+static void Message_tp_dealloc(Message *self)
+{
+ if (self->msg) {
+ dbus_message_unref(self->msg);
+ }
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static PyObject *
+Message_tp_new(PyTypeObject *type,
+ PyObject *args UNUSED,
+ PyObject *kwargs UNUSED)
+{
+ Message *self;
+
+ self = (Message *)type->tp_alloc(type, 0);
+ if (!self) return NULL;
+ self->msg = NULL;
+ return (PyObject *)self;
+}
+
+static PyObject *
+MethodCallMessage_tp_repr(PyObject *self)
+{
+ DBusMessage *msg = ((Message *)self)->msg;
+ const char *destination = dbus_message_get_destination(msg);
+ const char *path = dbus_message_get_path(msg);
+ const char *interface = dbus_message_get_interface(msg);
+ const char *member = dbus_message_get_member(msg);
+
+ if (!path)
+ path = "n/a";
+ if (!interface)
+ interface = "n/a";
+ if (!member)
+ member = "n/a";
+ if (!destination)
+ destination = "n/a";
+
+ return PyUnicode_FromFormat(
+ "<%s path: %s, iface: %s, member: %s dest: %s>",
+ Py_TYPE(self)->tp_name,
+ path, interface, member, destination);
+}
+
+PyDoc_STRVAR(MethodCallMessage_tp_doc,
+"dbus.lowlevel.MethodCallMessage(destination: str or None, path: str, "
+"interface: str or None, method: str)\n"
+"\n"
+"A method-call message.\n"
+"\n"
+"``destination`` is the destination bus name, or None to send the\n"
+"message directly to the peer (usually the bus daemon).\n"
+"\n"
+"``path`` is the object-path of the object whose method is to be called.\n"
+"\n"
+"``interface`` is the interface qualifying the method name, or None to omit\n"
+"the interface from the message header.\n"
+"\n"
+"``method`` is the method name (member name).\n"
+);
+
+static int
+MethodCallMessage_tp_init(Message *self, PyObject *args, PyObject *kwargs)
+{
+ const char *destination, *path, *interface, *method;
+ static char *kwlist[] = {"destination", "path", "interface", "method", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "zszs:__init__", kwlist,
+ &destination, &path, &interface,
+ &method)) {
+ return -1;
+ }
+ if (destination && !dbus_py_validate_bus_name(destination, 1, 1)) return -1;
+ if (!dbus_py_validate_object_path(path)) return -1;
+ if (interface && !dbus_py_validate_interface_name(interface)) return -1;
+ if (!dbus_py_validate_member_name(method)) return -1;
+ if (self->msg) {
+ dbus_message_unref(self->msg);
+ self->msg = NULL;
+ }
+ self->msg = dbus_message_new_method_call(destination, path, interface,
+ method);
+ if (!self->msg) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ return 0;
+}
+
+PyDoc_STRVAR(MethodReturnMessage_tp_doc,
+"dbus.lowlevel.MethodReturnMessage(method_call: MethodCallMessage)\n"
+"\n"
+"A method-return message.");
+
+static int
+MethodReturnMessage_tp_init(Message *self, PyObject *args, PyObject *kwargs)
+{
+ Message *other;
+ static char *kwlist[] = {"method_call", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!:__init__", kwlist,
+ &MessageType, &other)) {
+ return -1;
+ }
+ if (self->msg) {
+ dbus_message_unref(self->msg);
+ self->msg = NULL;
+ }
+ self->msg = dbus_message_new_method_return(other->msg);
+ if (!self->msg) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ return 0;
+}
+
+PyDoc_STRVAR(SignalMessage_tp_doc,
+"dbus.lowlevel.SignalMessage(path: str, interface: str, method: str)\n"
+"\n"
+"A signal message.\n");
+static int
+SignalMessage_tp_init(Message *self, PyObject *args, PyObject *kwargs)
+{
+ const char *path, *interface, *name;
+ static char *kwlist[] = {"path", "interface", "name", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sss:__init__", kwlist,
+ &path, &interface, &name)) {
+ return -1;
+ }
+ if (!dbus_py_validate_object_path(path)) return -1;
+ if (!dbus_py_validate_interface_name(interface)) return -1;
+ if (!dbus_py_validate_member_name(name)) return -1;
+ if (self->msg) {
+ dbus_message_unref(self->msg);
+ self->msg = NULL;
+ }
+ self->msg = dbus_message_new_signal(path, interface, name);
+ if (!self->msg) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *
+SignalMessage_tp_repr(PyObject *self)
+{
+ DBusMessage *msg = ((Message *)self)->msg;
+ const char *path = dbus_message_get_path(msg);
+ const char *interface = dbus_message_get_interface(msg);
+ const char *member = dbus_message_get_member(msg);
+ const char *destination = dbus_message_get_destination(msg);
+
+ if (!path)
+ path = "n/a";
+ if (!interface)
+ interface = "n/a";
+ if (!member)
+ member = "n/a";
+ if (!destination)
+ destination = "(broadcast)";
+
+ return PyUnicode_FromFormat("<%s path: %s, iface: %s, member: %s, dest: %s>",
+ Py_TYPE(self)->tp_name,
+ path, interface, member, destination);
+}
+
+PyDoc_STRVAR(ErrorMessage_tp_doc,
+"dbus.lowlevel.ErrorMessage(reply_to: Message, error_name: str, "
+"error_message: str or None)\n"
+"\n"
+"An error message.\n");
+static int
+ErrorMessage_tp_init(Message *self, PyObject *args, PyObject *kwargs)
+{
+ Message *reply_to;
+ const char *error_name, *error_message;
+ static char *kwlist[] = {"reply_to", "error_name", "error_message", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!sz:__init__", kwlist,
+ &MessageType, &reply_to, &error_name,
+ &error_message)) {
+ return -1;
+ }
+ if (!dbus_py_validate_error_name(error_name)) return -1;
+ if (self->msg) {
+ dbus_message_unref(self->msg);
+ self->msg = NULL;
+ }
+ self->msg = dbus_message_new_error(reply_to->msg, error_name, error_message);
+ if (!self->msg) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ return 0;
+}
+
+DBusMessage *
+DBusPyMessage_BorrowDBusMessage(PyObject *msg)
+{
+ if (!Message_Check(msg)) {
+ PyErr_SetString(PyExc_TypeError,
+ "A dbus.lowlevel.Message instance is required");
+ return NULL;
+ }
+ if (!((Message *)msg)->msg) {
+ DBusPy_RaiseUnusableMessage();
+ return NULL;
+ }
+ return ((Message *)msg)->msg;
+}
+
+PyObject *
+DBusPyMessage_ConsumeDBusMessage(DBusMessage *msg)
+{
+ PyTypeObject *type;
+ Message *self;
+
+ switch (dbus_message_get_type(msg)) {
+ case DBUS_MESSAGE_TYPE_METHOD_CALL:
+ type = &MethodCallMessageType;
+ break;
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ type = &MethodReturnMessageType;
+ break;
+ case DBUS_MESSAGE_TYPE_ERROR:
+ type = &ErrorMessageType;
+ break;
+ case DBUS_MESSAGE_TYPE_SIGNAL:
+ type = &SignalMessageType;
+ break;
+ default:
+ type = &MessageType;
+ }
+
+ self = (Message *)(type->tp_new) (type, dbus_py_empty_tuple, NULL);
+ if (!self) {
+ dbus_message_unref(msg);
+ return NULL;
+ }
+ self->msg = msg;
+ return (PyObject *)self;
+}
+
+PyDoc_STRVAR(Message_copy__doc__,
+"message.copy() -> Message (or subclass)\n"
+"Deep-copy the message, resetting the serial number to zero.\n");
+static PyObject *
+Message_copy(Message *self, PyObject *args UNUSED)
+{
+ DBusMessage *msg;
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ msg = dbus_message_copy(self->msg);
+ if (!msg) return PyErr_NoMemory();
+ return DBusPyMessage_ConsumeDBusMessage(msg);
+}
+
+PyDoc_STRVAR(Message_get_auto_start__doc__,
+"message.get_auto_start() -> bool\n"
+"Return true if this message will cause an owner for the destination name\n"
+"to be auto-started.\n");
+static PyObject *
+Message_get_auto_start(Message *self, PyObject *unused UNUSED)
+{
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_get_auto_start(self->msg));
+}
+
+PyDoc_STRVAR(Message_set_auto_start__doc__,
+"message.set_auto_start(bool) -> None\n"
+"Set whether this message will cause an owner for the destination name\n"
+"to be auto-started.\n");
+static PyObject *
+Message_set_auto_start(Message *self, PyObject *args)
+{
+ int value;
+ if (!PyArg_ParseTuple(args, "i", &value)) return NULL;
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ dbus_message_set_auto_start(self->msg, value ? TRUE : FALSE);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyDoc_STRVAR(Message_get_no_reply__doc__,
+"message.get_no_reply() -> bool\n"
+"Return true if this message need not be replied to.\n");
+static PyObject *
+Message_get_no_reply(Message *self, PyObject *unused UNUSED)
+{
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_get_no_reply(self->msg));
+}
+
+PyDoc_STRVAR(Message_set_no_reply__doc__,
+"message.set_no_reply(bool) -> None\n"
+"Set whether no reply to this message is required.\n");
+static PyObject *
+Message_set_no_reply(Message *self, PyObject *args)
+{
+ int value;
+ if (!PyArg_ParseTuple(args, "i", &value)) return NULL;
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ dbus_message_set_no_reply(self->msg, value ? TRUE : FALSE);
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Message_get_allow_interactive_authorization__doc__,
+"message.get_allow_interactive_authorization(bool) -> None\n"
+"Get allow interactive authorization flag.\n");
+static PyObject *
+Message_get_allow_interactive_authorization(Message *self, PyObject *unused UNUSED)
+{
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_get_allow_interactive_authorization(self->msg));
+}
+
+
+PyDoc_STRVAR(Message_set_allow_interactive_authorization__doc__,
+"message.set_allow_interactive_authorization(bool) -> None\n"
+"Set allow interactive authorization flag to this message.\n");
+static PyObject *
+Message_set_allow_interactive_authorization(Message *self, PyObject *args)
+{
+ int value;
+ if (!PyArg_ParseTuple(args, "i", &value)) return NULL;
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ dbus_message_set_allow_interactive_authorization(self->msg, value ? TRUE : FALSE);
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Message_get_reply_serial__doc__,
+"message.get_reply_serial() -> long\n"
+"Returns the serial that the message is a reply to or 0 if none.\n");
+static PyObject *
+Message_get_reply_serial(Message *self, PyObject *unused UNUSED)
+{
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyLong_FromUnsignedLong(dbus_message_get_reply_serial(self->msg));
+}
+
+PyDoc_STRVAR(Message_set_reply_serial__doc__,
+"message.set_reply_serial(bool) -> None\n"
+"Set the serial that this message is a reply to.\n");
+static PyObject *
+Message_set_reply_serial(Message *self, PyObject *args)
+{
+ dbus_uint32_t value;
+
+ if (!PyArg_ParseTuple(args, "k", &value)) return NULL;
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ if (!dbus_message_set_reply_serial(self->msg, value)) {
+ return PyErr_NoMemory();
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyDoc_STRVAR(Message_get_type__doc__,
+"message.get_type() -> int\n\n"
+"Returns the type of the message.\n");
+static PyObject *
+Message_get_type(Message *self, PyObject *unused UNUSED)
+{
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyLong_FromLong(dbus_message_get_type(self->msg));
+}
+
+PyDoc_STRVAR(Message_get_serial__doc__,
+"message.get_serial() -> long\n"
+"Returns the serial of a message or 0 if none has been specified.\n"
+"\n"
+"The message's serial number is provided by the application sending the\n"
+"message and is used to identify replies to this message. All messages\n"
+"received on a connection will have a serial, but messages you haven't\n"
+"sent yet may return 0.\n");
+static PyObject *
+Message_get_serial(Message *self, PyObject *unused UNUSED)
+{
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyLong_FromUnsignedLong(dbus_message_get_serial(self->msg));
+}
+
+PyDoc_STRVAR(Message_is_method_call__doc__,
+"is_method_call(interface: str, member: str) -> bool");
+static PyObject *
+Message_is_method_call(Message *self, PyObject *args)
+{
+ const char *interface, *method;
+
+ if (!PyArg_ParseTuple(args, "ss:is_method_call", &interface, &method)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_is_method_call(self->msg, interface,
+ method));
+}
+
+PyDoc_STRVAR(Message_is_error__doc__,
+"is_error(error: str) -> bool");
+static PyObject *
+Message_is_error(Message *self, PyObject *args)
+{
+ const char *error_name;
+
+ if (!PyArg_ParseTuple(args, "s:is_error", &error_name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_is_error(self->msg, error_name));
+}
+
+PyDoc_STRVAR(Message_is_signal__doc__,
+"is_signal(interface: str, member: str) -> bool");
+static PyObject *
+Message_is_signal(Message *self, PyObject *args)
+{
+ const char *interface, *signal_name;
+
+ if (!PyArg_ParseTuple(args, "ss:is_signal", &interface, &signal_name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_is_signal(self->msg, interface,
+ signal_name));
+}
+
+PyDoc_STRVAR(Message_get_member__doc__,
+"get_member() -> str or None");
+static PyObject *
+Message_get_member(Message *self, PyObject *unused UNUSED)
+{
+ const char *c_str;
+
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ c_str = dbus_message_get_member(self->msg);
+ if (!c_str) {
+ Py_RETURN_NONE;
+ }
+ return PyUnicode_FromString(c_str);
+}
+
+PyDoc_STRVAR(Message_has_member__doc__,
+"has_member(name: str or None) -> bool");
+static PyObject *
+Message_has_member(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "z:has_member", &name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_has_member(self->msg, name));
+}
+
+PyDoc_STRVAR(Message_set_member__doc__,
+"set_member(unique_name: str or None)");
+static PyObject *
+Message_set_member(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "z:set_member", &name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ if (!dbus_py_validate_member_name(name)) return NULL;
+ if (!dbus_message_set_member(self->msg, name)) return PyErr_NoMemory();
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Message_get_path__doc__,
+"get_path() -> ObjectPath or None\n\n"
+"Return the message's destination object path (if it's a method call) or\n"
+"source object path (if it's a method reply or a signal) or None (if it\n"
+"has no path).\n");
+static PyObject *
+Message_get_path(Message *self, PyObject *unused UNUSED)
+{
+ const char *c_str;
+
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ c_str = dbus_message_get_path(self->msg);
+ if (!c_str) {
+ Py_RETURN_NONE;
+ }
+ return PyObject_CallFunction((PyObject *)&DBusPyObjectPath_Type, "(s)", c_str);
+}
+
+PyDoc_STRVAR(Message_get_path_decomposed__doc__,
+"get_path_decomposed() -> list of str, or None\n\n"
+"Return a list of path components (e.g. /foo/bar -> ['foo','bar'], / -> [])\n"
+"or None if the message has no associated path.\n");
+static PyObject *
+Message_get_path_decomposed(Message *self, PyObject *unused UNUSED)
+{
+ char **paths, **ptr;
+ PyObject *ret = PyList_New(0);
+
+ if (!ret) return NULL;
+ if (!self->msg) {
+ Py_CLEAR(ret);
+ return DBusPy_RaiseUnusableMessage();
+ }
+ if (!dbus_message_get_path_decomposed(self->msg, &paths)) {
+ Py_CLEAR(ret);
+ return PyErr_NoMemory();
+ }
+ if (!paths) {
+ Py_CLEAR(ret);
+ Py_RETURN_NONE;
+ }
+ for (ptr = paths; *ptr; ptr++) {
+ PyObject *str = PyUnicode_FromString(*ptr);
+
+ if (!str) {
+ Py_CLEAR(ret);
+ break;
+ }
+ if (PyList_Append(ret, str) < 0) {
+ Py_CLEAR(ret);
+ break;
+ }
+ Py_CLEAR(str);
+ str = NULL;
+ }
+ dbus_free_string_array(paths);
+ return ret;
+}
+
+PyDoc_STRVAR(Message_has_path__doc__,
+"has_path(name: str or None) -> bool");
+static PyObject *
+Message_has_path(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "z:has_path", &name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_has_path(self->msg, name));
+}
+
+PyDoc_STRVAR(Message_set_path__doc__,
+"set_path(name: str or None)");
+static PyObject *
+Message_set_path(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "z:set_path", &name)) return NULL;
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ if (!dbus_message_has_path(self->msg, name)) return PyErr_NoMemory();
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Message_get_signature__doc__,
+"get_signature() -> Signature or None");
+static PyObject *
+Message_get_signature(Message *self, PyObject *unused UNUSED)
+{
+ const char *c_str;
+
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ c_str = dbus_message_get_signature(self->msg);
+ if (!c_str) {
+ return PyObject_CallFunction((PyObject *)&DBusPySignature_Type, "(s)", "");
+ }
+ return PyObject_CallFunction((PyObject *)&DBusPySignature_Type, "(s)", c_str);
+}
+
+PyDoc_STRVAR(Message_has_signature__doc__,
+"has_signature(signature: str) -> bool");
+static PyObject *
+Message_has_signature(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "s:has_signature", &name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_has_signature(self->msg, name));
+}
+
+PyDoc_STRVAR(Message_get_sender__doc__,
+"get_sender() -> str or None\n\n"
+"Return the message's sender unique name, or None if none.\n");
+static PyObject *
+Message_get_sender(Message *self, PyObject *unused UNUSED)
+{
+ const char *c_str;
+
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ c_str = dbus_message_get_sender(self->msg);
+ if (!c_str) {
+ Py_RETURN_NONE;
+ }
+ return PyUnicode_FromString(c_str);
+}
+
+PyDoc_STRVAR(Message_has_sender__doc__,
+"has_sender(unique_name: str) -> bool");
+static PyObject *
+Message_has_sender(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "s:has_sender", &name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_has_sender(self->msg, name));
+}
+
+PyDoc_STRVAR(Message_set_sender__doc__,
+"set_sender(unique_name: str or None)");
+static PyObject *
+Message_set_sender(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "z:set_sender", &name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ if (!dbus_py_validate_bus_name(name, 1, 1)) return NULL;
+ if (!dbus_message_set_sender(self->msg, name)) return PyErr_NoMemory();
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Message_get_destination__doc__,
+"get_destination() -> str or None\n\n"
+"Return the message's destination bus name, or None if none.\n");
+static PyObject *
+Message_get_destination(Message *self, PyObject *unused UNUSED)
+{
+ const char *c_str;
+
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ c_str = dbus_message_get_destination(self->msg);
+ if (!c_str) {
+ Py_RETURN_NONE;
+ }
+ return PyUnicode_FromString(c_str);
+}
+
+PyDoc_STRVAR(Message_has_destination__doc__,
+"has_destination(bus_name: str) -> bool");
+static PyObject *
+Message_has_destination(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "s:has_destination", &name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_has_destination(self->msg, name));
+}
+
+PyDoc_STRVAR(Message_set_destination__doc__,
+"set_destination(bus_name: str or None)");
+static PyObject *
+Message_set_destination(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "z:set_destination", &name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ if (!dbus_py_validate_bus_name(name, 1, 1)) return NULL;
+ if (!dbus_message_set_destination(self->msg, name)) return PyErr_NoMemory();
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Message_get_interface__doc__,
+"get_interface() -> str or None");
+static PyObject *
+Message_get_interface(Message *self, PyObject *unused UNUSED)
+{
+ const char *c_str;
+
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ c_str = dbus_message_get_interface(self->msg);
+ if (!c_str) {
+ Py_RETURN_NONE;
+ }
+ return PyUnicode_FromString(c_str);
+}
+
+PyDoc_STRVAR(Message_has_interface__doc__,
+"has_interface(interface: str or None) -> bool");
+static PyObject *
+Message_has_interface(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "z:has_interface", &name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ return PyBool_FromLong(dbus_message_has_interface(self->msg, name));
+}
+
+PyDoc_STRVAR(Message_set_interface__doc__,
+"set_interface(name: str or None)");
+static PyObject *
+Message_set_interface(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "z:set_interface", &name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ if (!dbus_py_validate_interface_name(name)) return NULL;
+ if (!dbus_message_set_interface(self->msg, name)) return PyErr_NoMemory();
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Message_get_error_name__doc__,
+"get_error_name() -> str or None");
+static PyObject *
+Message_get_error_name(Message *self, PyObject *unused UNUSED)
+{
+ const char *c_str;
+
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ c_str = dbus_message_get_error_name(self->msg);
+ if (!c_str) {
+ Py_RETURN_NONE;
+ }
+ return PyUnicode_FromString(c_str);
+}
+
+PyDoc_STRVAR(Message_set_error_name__doc__,
+"set_error_name(name: str or None)");
+static PyObject *
+Message_set_error_name(Message *self, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "z:set_error_name", &name)) {
+ return NULL;
+ }
+ if (!self->msg) return DBusPy_RaiseUnusableMessage();
+ if (!dbus_py_validate_error_name(name)) return NULL;
+ if (!dbus_message_set_error_name(self->msg, name)) return PyErr_NoMemory();
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef Message_tp_methods[] = {
+ {"copy", (PyCFunction) (void (*)(void)) Message_copy,
+ METH_NOARGS, Message_copy__doc__},
+ {"is_method_call", (PyCFunction) (void (*)(void)) Message_is_method_call,
+ METH_VARARGS, Message_is_method_call__doc__},
+ {"is_signal", (PyCFunction) (void (*)(void)) Message_is_signal,
+ METH_VARARGS, Message_is_signal__doc__},
+ {"is_error", (PyCFunction) (void (*)(void)) Message_is_error,
+ METH_VARARGS, Message_is_error__doc__},
+
+ {"get_args_list", (PyCFunction) (void (*)(void)) dbus_py_Message_get_args_list,
+ METH_VARARGS|METH_KEYWORDS, dbus_py_Message_get_args_list__doc__},
+ {"guess_signature", (PyCFunction) (void (*)(void)) dbus_py_Message_guess_signature,
+ METH_VARARGS|METH_STATIC, dbus_py_Message_guess_signature__doc__},
+ {"append", (PyCFunction) (void (*)(void)) dbus_py_Message_append,
+ METH_VARARGS|METH_KEYWORDS, dbus_py_Message_append__doc__},
+
+ {"get_auto_start", (PyCFunction) (void (*)(void)) Message_get_auto_start,
+ METH_NOARGS, Message_get_auto_start__doc__},
+ {"set_auto_start", (PyCFunction) (void (*)(void)) Message_set_auto_start,
+ METH_VARARGS, Message_set_auto_start__doc__},
+ {"get_destination", (PyCFunction) (void (*)(void)) Message_get_destination,
+ METH_NOARGS, Message_get_destination__doc__},
+ {"set_destination", (PyCFunction) (void (*)(void)) Message_set_destination,
+ METH_VARARGS, Message_set_destination__doc__},
+ {"has_destination", (PyCFunction) (void (*)(void)) Message_has_destination,
+ METH_VARARGS, Message_has_destination__doc__},
+ {"get_error_name", (PyCFunction) (void (*)(void)) Message_get_error_name,
+ METH_NOARGS, Message_get_error_name__doc__},
+ {"set_error_name", (PyCFunction) (void (*)(void)) Message_set_error_name,
+ METH_VARARGS, Message_set_error_name__doc__},
+ {"get_interface", (PyCFunction) (void (*)(void)) Message_get_interface,
+ METH_NOARGS, Message_get_interface__doc__},
+ {"set_interface", (PyCFunction) (void (*)(void))Message_set_interface,
+ METH_VARARGS, Message_set_interface__doc__},
+ {"has_interface", (PyCFunction) (void (*)(void))Message_has_interface,
+ METH_VARARGS, Message_has_interface__doc__},
+ {"get_member", (PyCFunction) (void (*)(void))Message_get_member,
+ METH_NOARGS, Message_get_member__doc__},
+ {"set_member", (PyCFunction) (void (*)(void))Message_set_member,
+ METH_VARARGS, Message_set_member__doc__},
+ {"has_member", (PyCFunction) (void (*)(void))Message_has_member,
+ METH_VARARGS, Message_has_member__doc__},
+ {"get_path", (PyCFunction) (void (*)(void))Message_get_path,
+ METH_NOARGS, Message_get_path__doc__},
+ {"get_path_decomposed", (PyCFunction) (void (*)(void))Message_get_path_decomposed,
+ METH_NOARGS, Message_get_path_decomposed__doc__},
+ {"set_path", (PyCFunction) (void (*)(void))Message_set_path,
+ METH_VARARGS, Message_set_path__doc__},
+ {"has_path", (PyCFunction) (void (*)(void))Message_has_path,
+ METH_VARARGS, Message_has_path__doc__},
+ {"get_no_reply", (PyCFunction) (void (*)(void))Message_get_no_reply,
+ METH_NOARGS, Message_get_no_reply__doc__},
+ {"set_no_reply", (PyCFunction) (void (*)(void))Message_set_no_reply,
+ METH_VARARGS, Message_set_no_reply__doc__},
+ {"get_allow_interactive_authorization", (PyCFunction) (void (*)(void))Message_get_allow_interactive_authorization,
+ METH_NOARGS, Message_get_allow_interactive_authorization__doc__},
+ {"set_allow_interactive_authorization", (PyCFunction) (void (*)(void))Message_set_allow_interactive_authorization,
+ METH_VARARGS, Message_set_allow_interactive_authorization__doc__},
+ {"get_reply_serial", (PyCFunction) (void (*)(void))Message_get_reply_serial,
+ METH_NOARGS, Message_get_reply_serial__doc__},
+ {"set_reply_serial", (PyCFunction) (void (*)(void))Message_set_reply_serial,
+ METH_VARARGS, Message_set_reply_serial__doc__},
+ {"get_sender", (PyCFunction) (void (*)(void))Message_get_sender,
+ METH_NOARGS, Message_get_sender__doc__},
+ {"set_sender", (PyCFunction) (void (*)(void))Message_set_sender,
+ METH_VARARGS, Message_set_sender__doc__},
+ {"has_sender", (PyCFunction) (void (*)(void))Message_has_sender,
+ METH_VARARGS, Message_has_sender__doc__},
+ {"get_serial", (PyCFunction) (void (*)(void))Message_get_serial,
+ METH_NOARGS, Message_get_serial__doc__},
+ {"get_signature", (PyCFunction) (void (*)(void))Message_get_signature,
+ METH_NOARGS, Message_get_signature__doc__},
+ {"has_signature", (PyCFunction) (void (*)(void))Message_has_signature,
+ METH_VARARGS, Message_has_signature__doc__},
+ {"get_type", (PyCFunction) (void (*)(void))Message_get_type,
+ METH_NOARGS, Message_get_type__doc__},
+ {NULL, NULL, 0, NULL}
+};
+
+static PyTypeObject MessageType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dbus.lowlevel.Message", /*tp_name*/
+ sizeof(Message), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ (destructor)Message_tp_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ Message_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ Message_tp_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ Message_tp_new, /* tp_new */
+};
+
+static PyTypeObject MethodCallMessageType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dbus.lowlevel.MethodCallMessage", /*tp_name*/
+ 0, /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ 0, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ MethodCallMessage_tp_repr, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ MethodCallMessage_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&MessageType), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)MethodCallMessage_tp_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+
+static PyTypeObject MethodReturnMessageType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dbus.lowlevel.MethodReturnMessage", /*tp_name*/
+ 0, /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ 0, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ MethodReturnMessage_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&MessageType), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)MethodReturnMessage_tp_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+
+static PyTypeObject SignalMessageType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dbus.lowlevel.SignalMessage", /*tp_name*/
+ 0, /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ 0, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ SignalMessage_tp_repr, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ SignalMessage_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&MessageType), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)SignalMessage_tp_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+
+static PyTypeObject ErrorMessageType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dbus.lowlevel.ErrorMessage", /*tp_name*/
+ 0, /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ 0, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+ ErrorMessage_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&MessageType), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)ErrorMessage_tp_init, /* tp_init */
+ 0, /* tp_alloc */
+ 0, /* tp_new */
+};
+
+dbus_bool_t
+dbus_py_init_message_types(void)
+{
+ if (PyType_Ready(&MessageType) < 0) return 0;
+
+ MethodCallMessageType.tp_base = &MessageType;
+ if (PyType_Ready(&MethodCallMessageType) < 0) return 0;
+
+ MethodReturnMessageType.tp_base = &MessageType;
+ if (PyType_Ready(&MethodReturnMessageType) < 0) return 0;
+
+ SignalMessageType.tp_base = &MessageType;
+ if (PyType_Ready(&SignalMessageType) < 0) return 0;
+
+ ErrorMessageType.tp_base = &MessageType;
+ if (PyType_Ready(&ErrorMessageType) < 0) return 0;
+
+ return 1;
+}
+
+dbus_bool_t
+dbus_py_insert_message_types(PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF (&MessageType);
+ Py_INCREF (&MethodCallMessageType);
+ Py_INCREF (&MethodReturnMessageType);
+ Py_INCREF (&ErrorMessageType);
+ Py_INCREF (&SignalMessageType);
+
+ if (PyModule_AddObject(this_module, "Message",
+ (PyObject *)&MessageType) < 0) return 0;
+
+ if (PyModule_AddObject(this_module, "MethodCallMessage",
+ (PyObject *)&MethodCallMessageType) < 0) return 0;
+
+ if (PyModule_AddObject(this_module, "MethodReturnMessage",
+ (PyObject *)&MethodReturnMessageType) < 0) return 0;
+
+ if (PyModule_AddObject(this_module, "ErrorMessage",
+ (PyObject *)&ErrorMessageType) < 0) return 0;
+
+ if (PyModule_AddObject(this_module, "SignalMessage",
+ (PyObject *)&SignalMessageType) < 0) return 0;
+
+ return 1;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/module.c b/dbus_bindings/module.c
new file mode 100644
index 0000000..8ca99a5
--- /dev/null
+++ b/dbus_bindings/module.c
@@ -0,0 +1,414 @@
+/* Main module source for the _dbus_bindings extension.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include <Python.h>
+#include <structmember.h>
+
+PyDoc_STRVAR(module_doc,
+"Low-level Python bindings for libdbus. Don't use this module directly -\n"
+"the public API is provided by the `dbus`, `dbus.service`, `dbus.mainloop`\n"
+"and `dbus.mainloop.glib` modules, with a lower-level API provided by the\n"
+"`dbus.lowlevel` module.\n"
+);
+
+/* Global functions - validation wrappers ===========================*/
+
+PyDoc_STRVAR(validate_bus_name__doc__,
+"validate_bus_name(name, allow_unique=True, allow_well_known=True)\n"
+"\n"
+"Raise ValueError if the argument is not a valid bus name.\n"
+"\n"
+"By default both unique and well-known names are accepted.\n"
+"\n"
+":Parameters:\n"
+" `name` : str\n"
+" The name to be validated\n"
+" `allow_unique` : bool\n"
+" If False, unique names of the form :1.123 will be rejected\n"
+" `allow_well_known` : bool\n"
+" If False, well-known names of the form com.example.Foo\n"
+" will be rejected\n"
+":Since: 0.80\n"
+);
+
+static PyObject *
+validate_bus_name(PyObject *unused UNUSED, PyObject *args, PyObject *kwargs)
+{
+ const char *name;
+ int allow_unique = 1;
+ int allow_well_known = 1;
+ static char *argnames[] = { "name", "allow_unique", "allow_well_known",
+ NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "s|ii:validate_bus_name", argnames,
+ &name, &allow_unique,
+ &allow_well_known)) {
+ return NULL;
+ }
+ if (!dbus_py_validate_bus_name(name, !!allow_unique, !!allow_well_known)) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(validate_member_name__doc__,
+"validate_member_name(name)\n"
+"\n"
+"Raise ValueError if the argument is not a valid member (signal or method) "
+"name.\n"
+"\n"
+":Since: 0.80\n"
+);
+
+static PyObject *
+validate_member_name(PyObject *unused UNUSED, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "s:validate_member_name", &name)) {
+ return NULL;
+ }
+ if (!dbus_py_validate_member_name(name)) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(validate_interface_name__doc__,
+"validate_interface_name(name)\n\n"
+"Raise ValueError if the given string is not a valid interface name.\n"
+"\n"
+":Since: 0.80\n"
+);
+
+PyDoc_STRVAR(validate_error_name__doc__,
+"validate_error_name(name)\n\n"
+"Raise ValueError if the given string is not a valid error name.\n"
+"\n"
+":Since: 0.80\n"
+);
+
+static PyObject *
+validate_interface_name(PyObject *unused UNUSED, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "s:validate_interface_name", &name)) {
+ return NULL;
+ }
+ if (!dbus_py_validate_interface_name(name)) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(validate_object_path__doc__,
+"validate_object_path(name)\n\n"
+"Raise ValueError if the given string is not a valid object path.\n"
+"\n"
+":Since: 0.80\n"
+);
+
+static PyObject *
+validate_object_path(PyObject *unused UNUSED, PyObject *args)
+{
+ const char *name;
+
+ if (!PyArg_ParseTuple(args, "s:validate_object_path", &name)) {
+ return NULL;
+ }
+ if (!dbus_py_validate_object_path(name)) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+/* Global functions - main loop =====================================*/
+
+/* The main loop if none is passed to the constructor */
+static PyObject *default_main_loop = NULL;
+
+/* Return a new reference to the default main loop */
+PyObject *
+dbus_py_get_default_main_loop(void)
+{
+ if (!default_main_loop) {
+ Py_RETURN_NONE;
+ }
+ Py_INCREF(default_main_loop);
+ return default_main_loop;
+}
+
+PyDoc_STRVAR(get_default_main_loop__doc__,
+"get_default_main_loop() -> object\n\n"
+"Return the global default dbus-python main loop wrapper, which is used\n"
+"when no main loop wrapper is passed to the Connection constructor.\n"
+"\n"
+"If None, there is no default and you should always pass the mainloop\n"
+"parameter to the constructor - if you don't, then asynchronous calls,\n"
+"connecting to signals and exporting objects will raise an exception.\n"
+"There is no default until set_default_main_loop is called.\n");
+static PyObject *
+get_default_main_loop(PyObject *always_null UNUSED,
+ PyObject *no_args UNUSED)
+{
+ return dbus_py_get_default_main_loop();
+}
+
+PyDoc_STRVAR(set_default_main_loop__doc__,
+"set_default_main_loop(object)\n\n"
+"Change the global default dbus-python main loop wrapper, which is used\n"
+"when no main loop wrapper is passed to the Connection constructor.\n"
+"\n"
+"If None, return to the initial situation: there is no default, and you\n"
+"must always pass the mainloop parameter to the constructor.\n"
+"\n"
+"Two types of main loop wrapper are planned in dbus-python.\n"
+"Native main-loop wrappers are instances of `dbus.mainloop.NativeMainLoop`\n"
+"supplied by extension modules like `dbus.mainloop.glib`: they have no\n"
+"Python API, but connect themselves to ``libdbus`` using native code.\n"
+
+"Python main-loop wrappers are not yet implemented. They will be objects\n"
+"supporting the interface defined by `dbus.mainloop.MainLoop`, with an\n"
+"API entirely based on Python methods.\n"
+"\n"
+);
+static PyObject *
+set_default_main_loop(PyObject *always_null UNUSED,
+ PyObject *args)
+{
+ PyObject *new_loop, *old_loop;
+
+ if (!PyArg_ParseTuple(args, "O", &new_loop)) {
+ return NULL;
+ }
+ if (!dbus_py_check_mainloop_sanity(new_loop)) {
+ return NULL;
+ }
+ old_loop = default_main_loop;
+ Py_INCREF(new_loop);
+ default_main_loop = new_loop;
+ Py_CLEAR(old_loop);
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef module_functions[] = {
+#define ENTRY(name,flags) {#name, (PyCFunction) (void (*)(void))name, flags, name##__doc__}
+ ENTRY(validate_interface_name, METH_VARARGS),
+ ENTRY(validate_member_name, METH_VARARGS),
+ ENTRY(validate_bus_name, METH_VARARGS|METH_KEYWORDS),
+ ENTRY(validate_object_path, METH_VARARGS),
+ ENTRY(set_default_main_loop, METH_VARARGS),
+ ENTRY(get_default_main_loop, METH_NOARGS),
+ /* validate_error_name is just implemented as validate_interface_name */
+ {"validate_error_name", validate_interface_name,
+ METH_VARARGS, validate_error_name__doc__},
+#undef ENTRY
+ {NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC
+PyInit__dbus_bindings(void)
+{
+ PyObject *this_module = NULL, *c_api;
+ static const int API_count = DBUS_BINDINGS_API_COUNT;
+ static _dbus_py_func_ptr dbus_bindings_API[DBUS_BINDINGS_API_COUNT];
+
+ static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "_dbus_bindings", /* m_name */
+ module_doc, /* m_doc */
+ -1, /* m_size */
+ module_functions, /* m_methods */
+ NULL, /* m_reload */
+ NULL, /* m_traverse */
+ NULL, /* m_clear */
+ NULL /* m_free */
+ };
+
+ dbus_bindings_API[0] = (_dbus_py_func_ptr)&API_count;
+ dbus_bindings_API[1] = (_dbus_py_func_ptr)DBusPyConnection_BorrowDBusConnection;
+ dbus_bindings_API[2] = (_dbus_py_func_ptr)DBusPyNativeMainLoop_New4;
+
+ default_main_loop = NULL;
+
+ if (!dbus_py_init_generic()) goto init_error;
+ if (!dbus_py_init_abstract()) goto init_error;
+ if (!dbus_py_init_signature()) goto init_error;
+ if (!dbus_py_init_int_types()) goto init_error;
+ if (!dbus_py_init_unixfd_type()) goto init_error;
+ if (!dbus_py_init_string_types()) goto init_error;
+ if (!dbus_py_init_float_types()) goto init_error;
+ if (!dbus_py_init_container_types()) goto init_error;
+ if (!dbus_py_init_byte_types()) goto init_error;
+ if (!dbus_py_init_message_types()) goto init_error;
+ if (!dbus_py_init_pending_call()) goto init_error;
+ if (!dbus_py_init_mainloop()) goto init_error;
+ if (!dbus_py_init_libdbus_conn_types()) goto init_error;
+ if (!dbus_py_init_conn_types()) goto init_error;
+ if (!dbus_py_init_server_types()) goto init_error;
+
+ this_module = PyModule_Create(&moduledef);
+ if (!this_module) goto init_error;
+
+ if (!dbus_py_insert_abstract_types(this_module)) goto init_error;
+ if (!dbus_py_insert_signature(this_module)) goto init_error;
+ if (!dbus_py_insert_int_types(this_module)) goto init_error;
+ if (!dbus_py_insert_unixfd_type(this_module)) goto init_error;
+ if (!dbus_py_insert_string_types(this_module)) goto init_error;
+ if (!dbus_py_insert_float_types(this_module)) goto init_error;
+ if (!dbus_py_insert_container_types(this_module)) goto init_error;
+ if (!dbus_py_insert_byte_types(this_module)) goto init_error;
+ if (!dbus_py_insert_message_types(this_module)) goto init_error;
+ if (!dbus_py_insert_pending_call(this_module)) goto init_error;
+ if (!dbus_py_insert_mainloop_types(this_module)) goto init_error;
+ if (!dbus_py_insert_libdbus_conn_types(this_module)) goto init_error;
+ if (!dbus_py_insert_conn_types(this_module)) goto init_error;
+ if (!dbus_py_insert_server_types(this_module)) goto init_error;
+
+ if (PyModule_AddStringConstant(this_module, "BUS_DAEMON_NAME",
+ DBUS_SERVICE_DBUS) < 0) goto init_error;
+ if (PyModule_AddStringConstant(this_module, "BUS_DAEMON_PATH",
+ DBUS_PATH_DBUS) < 0) goto init_error;
+ if (PyModule_AddStringConstant(this_module, "BUS_DAEMON_IFACE",
+ DBUS_INTERFACE_DBUS) < 0) goto init_error;
+ if (PyModule_AddStringConstant(this_module, "LOCAL_PATH",
+ DBUS_PATH_LOCAL) < 0) goto init_error;
+ if (PyModule_AddStringConstant(this_module, "LOCAL_IFACE",
+ DBUS_INTERFACE_LOCAL) < 0) goto init_error;
+ if (PyModule_AddStringConstant(this_module, "INTROSPECTABLE_IFACE",
+ DBUS_INTERFACE_INTROSPECTABLE) < 0)
+ goto init_error;
+ if (PyModule_AddStringConstant(this_module, "PEER_IFACE",
+ DBUS_INTERFACE_PEER) < 0) goto init_error;
+ if (PyModule_AddStringConstant(this_module, "PROPERTIES_IFACE",
+ DBUS_INTERFACE_PROPERTIES) < 0)
+ goto init_error;
+ if (PyModule_AddStringConstant(this_module,
+ "DBUS_INTROSPECT_1_0_XML_PUBLIC_IDENTIFIER",
+ DBUS_INTROSPECT_1_0_XML_PUBLIC_IDENTIFIER) < 0)
+ goto init_error;
+ if (PyModule_AddStringConstant(this_module,
+ "DBUS_INTROSPECT_1_0_XML_SYSTEM_IDENTIFIER",
+ DBUS_INTROSPECT_1_0_XML_SYSTEM_IDENTIFIER) < 0)
+ goto init_error;
+ if (PyModule_AddStringConstant(this_module,
+ "DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE",
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE) < 0)
+ goto init_error;
+
+#define ADD_CONST_VAL(x, v) \
+ if (PyModule_AddIntConstant(this_module, x, v) < 0) goto init_error;
+#define ADD_CONST_PREFIXED(x) ADD_CONST_VAL(#x, DBUS_##x)
+#define ADD_CONST(x) ADD_CONST_VAL(#x, x)
+
+ ADD_CONST(DBUS_START_REPLY_SUCCESS)
+ ADD_CONST(DBUS_START_REPLY_ALREADY_RUNNING)
+
+ ADD_CONST_PREFIXED(RELEASE_NAME_REPLY_RELEASED)
+ ADD_CONST_PREFIXED(RELEASE_NAME_REPLY_NON_EXISTENT)
+ ADD_CONST_PREFIXED(RELEASE_NAME_REPLY_NOT_OWNER)
+
+ ADD_CONST_PREFIXED(REQUEST_NAME_REPLY_PRIMARY_OWNER)
+ ADD_CONST_PREFIXED(REQUEST_NAME_REPLY_IN_QUEUE)
+ ADD_CONST_PREFIXED(REQUEST_NAME_REPLY_EXISTS)
+ ADD_CONST_PREFIXED(REQUEST_NAME_REPLY_ALREADY_OWNER)
+
+ ADD_CONST_PREFIXED(NAME_FLAG_ALLOW_REPLACEMENT)
+ ADD_CONST_PREFIXED(NAME_FLAG_REPLACE_EXISTING)
+ ADD_CONST_PREFIXED(NAME_FLAG_DO_NOT_QUEUE)
+
+ ADD_CONST_PREFIXED(BUS_SESSION)
+ ADD_CONST_PREFIXED(BUS_SYSTEM)
+ ADD_CONST_PREFIXED(BUS_STARTER)
+
+ ADD_CONST_PREFIXED(MESSAGE_TYPE_INVALID)
+ ADD_CONST_PREFIXED(MESSAGE_TYPE_METHOD_CALL)
+ ADD_CONST_PREFIXED(MESSAGE_TYPE_METHOD_RETURN)
+ ADD_CONST_PREFIXED(MESSAGE_TYPE_ERROR)
+ ADD_CONST_PREFIXED(MESSAGE_TYPE_SIGNAL)
+
+ ADD_CONST_PREFIXED(TYPE_INVALID)
+ ADD_CONST_PREFIXED(TYPE_BYTE)
+ ADD_CONST_PREFIXED(TYPE_BOOLEAN)
+ ADD_CONST_PREFIXED(TYPE_INT16)
+ ADD_CONST_PREFIXED(TYPE_UINT16)
+ ADD_CONST_PREFIXED(TYPE_INT32)
+#ifdef DBUS_TYPE_UNIX_FD
+ ADD_CONST_PREFIXED(TYPE_UNIX_FD)
+#endif
+ ADD_CONST_PREFIXED(TYPE_UINT32)
+ ADD_CONST_PREFIXED(TYPE_INT64)
+ ADD_CONST_PREFIXED(TYPE_UINT64)
+ ADD_CONST_PREFIXED(TYPE_DOUBLE)
+ ADD_CONST_PREFIXED(TYPE_STRING)
+ ADD_CONST_PREFIXED(TYPE_OBJECT_PATH)
+ ADD_CONST_PREFIXED(TYPE_SIGNATURE)
+ ADD_CONST_PREFIXED(TYPE_ARRAY)
+ ADD_CONST_PREFIXED(TYPE_STRUCT)
+ ADD_CONST_VAL("STRUCT_BEGIN", DBUS_STRUCT_BEGIN_CHAR)
+ ADD_CONST_VAL("STRUCT_END", DBUS_STRUCT_END_CHAR)
+ ADD_CONST_PREFIXED(TYPE_VARIANT)
+ ADD_CONST_PREFIXED(TYPE_DICT_ENTRY)
+ ADD_CONST_VAL("DICT_ENTRY_BEGIN", DBUS_DICT_ENTRY_BEGIN_CHAR)
+ ADD_CONST_VAL("DICT_ENTRY_END", DBUS_DICT_ENTRY_END_CHAR)
+
+ ADD_CONST_PREFIXED(HANDLER_RESULT_HANDLED)
+ ADD_CONST_PREFIXED(HANDLER_RESULT_NOT_YET_HANDLED)
+ ADD_CONST_PREFIXED(HANDLER_RESULT_NEED_MEMORY)
+
+ ADD_CONST_PREFIXED(WATCH_READABLE)
+ ADD_CONST_PREFIXED(WATCH_WRITABLE)
+ ADD_CONST_PREFIXED(WATCH_HANGUP)
+ ADD_CONST_PREFIXED(WATCH_ERROR)
+
+ if (PyModule_AddStringConstant(this_module, "__docformat__",
+ "restructuredtext") < 0) goto init_error;
+
+ if (PyModule_AddStringConstant(this_module, "__version__",
+ PACKAGE_VERSION) < 0) goto init_error;
+
+ if (PyModule_AddIntConstant(this_module, "_python_version",
+ PY_VERSION_HEX) < 0) goto init_error;
+
+ c_api = PyCapsule_New((void *)dbus_bindings_API,
+ PYDBUS_CAPSULE_NAME, NULL);
+ if (!c_api) {
+ goto init_error;
+ }
+ PyModule_AddObject(this_module, "_C_API", c_api);
+
+ return this_module;
+ init_error:
+ Py_CLEAR(this_module);
+ return NULL;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/pending-call.c b/dbus_bindings/pending-call.c
new file mode 100644
index 0000000..636095f
--- /dev/null
+++ b/dbus_bindings/pending-call.c
@@ -0,0 +1,296 @@
+/* Implementation of PendingCall helper type for D-Bus bindings.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+PyDoc_STRVAR(PendingCall_tp_doc,
+"Object representing a pending D-Bus call, returned by\n"
+"Connection.send_message_with_reply(). Cannot be instantiated directly.\n"
+);
+
+static PyTypeObject PendingCallType;
+
+static inline int PendingCall_Check (PyObject *o)
+{
+ return (Py_TYPE(o) == &PendingCallType)
+ || PyObject_IsInstance(o, (PyObject *)&PendingCallType);
+}
+
+typedef struct {
+ PyObject_HEAD
+ DBusPendingCall *pc;
+} PendingCall;
+
+PyDoc_STRVAR(PendingCall_cancel__doc__,
+"cancel()\n\n"
+"Cancel this pending call. Its reply will be ignored and the associated\n"
+"reply handler will never be called.\n");
+static PyObject *
+PendingCall_cancel(PendingCall *self, PyObject *unused UNUSED)
+{
+ Py_BEGIN_ALLOW_THREADS
+ dbus_pending_call_cancel(self->pc);
+ Py_END_ALLOW_THREADS
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(PendingCall_block__doc__,
+"block()\n\n"
+"Block until this pending call has completed and the associated\n"
+"reply handler has been called.\n"
+"\n"
+"This can lead to a deadlock, if the called method tries to make a\n"
+"synchronous call to a method in this application.\n");
+static PyObject *
+PendingCall_block(PendingCall *self, PyObject *unused UNUSED)
+{
+ Py_BEGIN_ALLOW_THREADS
+ dbus_pending_call_block(self->pc);
+ Py_END_ALLOW_THREADS
+ Py_RETURN_NONE;
+}
+
+static void
+_pending_call_notify_function(DBusPendingCall *pc,
+ PyObject *list)
+{
+ PyGILState_STATE gil = PyGILState_Ensure();
+ /* BEGIN CRITICAL SECTION
+ * While holding the GIL, make sure the callback only gets called once
+ * by deleting it from the 1-item list that's held by libdbus.
+ */
+ PyObject *handler = PyList_GetItem(list, 0);
+ DBusMessage *msg;
+
+ if (!handler) {
+ PyErr_Print();
+ goto release;
+ }
+ if (handler == Py_None) {
+ /* We've already called (and thrown away) the callback */
+ goto release;
+ }
+ Py_INCREF(handler); /* previously borrowed from the list, now owned */
+ Py_INCREF(Py_None); /* take a ref so SetItem can steal it */
+ PyList_SetItem(list, 0, Py_None);
+ /* END CRITICAL SECTION */
+
+ msg = dbus_pending_call_steal_reply(pc);
+
+ if (!msg) {
+ /* omg, what happened here? the notify should only get called
+ * when we have a reply */
+ PyErr_Warn(PyExc_UserWarning, "D-Bus notify function was called "
+ "for an incomplete pending call (shouldn't happen)");
+ } else {
+ PyObject *msg_obj = DBusPyMessage_ConsumeDBusMessage(msg);
+
+ if (msg_obj) {
+ PyObject *ret = PyObject_CallFunctionObjArgs(handler, msg_obj, NULL);
+
+ if (!ret) {
+ PyErr_Print();
+ }
+ Py_CLEAR(ret);
+ Py_CLEAR(msg_obj);
+ }
+ /* else OOM has happened - not a lot we can do about that,
+ * except possibly making it fatal (FIXME?) */
+ }
+
+release:
+ Py_CLEAR(handler);
+ PyGILState_Release(gil);
+}
+
+PyDoc_STRVAR(PendingCall_get_completed__doc__,
+"get_completed() -> bool\n\n"
+"Return true if this pending call has completed.\n\n"
+"If so, its associated reply handler has been called and it is no\n"
+"longer meaningful to cancel it.\n");
+static PyObject *
+PendingCall_get_completed(PendingCall *self, PyObject *unused UNUSED)
+{
+ dbus_bool_t ret;
+
+ Py_BEGIN_ALLOW_THREADS
+ ret = dbus_pending_call_get_completed(self->pc);
+ Py_END_ALLOW_THREADS
+ return PyBool_FromLong(ret);
+}
+
+/* Steals the reference to the pending call. */
+PyObject *
+DBusPyPendingCall_ConsumeDBusPendingCall(DBusPendingCall *pc,
+ PyObject *callable)
+{
+ dbus_bool_t ret;
+ PyObject *list = PyList_New(1);
+ PendingCall *self = PyObject_New(PendingCall, &PendingCallType);
+
+ if (!list || !self) {
+ Py_CLEAR(list);
+ Py_CLEAR(self);
+ Py_BEGIN_ALLOW_THREADS
+ dbus_pending_call_cancel(pc);
+ dbus_pending_call_unref(pc);
+ Py_END_ALLOW_THREADS
+ return NULL;
+ }
+
+ /* INCREF because SET_ITEM steals a ref */
+ Py_INCREF(callable);
+ PyList_SET_ITEM(list, 0, callable);
+
+ /* INCREF so we can give a ref to set_notify and still have one */
+ Py_INCREF(list);
+
+ Py_BEGIN_ALLOW_THREADS
+ ret = dbus_pending_call_set_notify(pc,
+ (DBusPendingCallNotifyFunction)_pending_call_notify_function,
+ (void *)list, (DBusFreeFunction)dbus_py_take_gil_and_xdecref);
+ Py_END_ALLOW_THREADS
+
+ if (!ret) {
+ PyErr_NoMemory();
+ /* DECREF twice - one for the INCREF and one for the allocation */
+ Py_DECREF(list);
+ Py_CLEAR(list);
+ Py_CLEAR(self);
+ Py_BEGIN_ALLOW_THREADS
+ dbus_pending_call_cancel(pc);
+ dbus_pending_call_unref(pc);
+ Py_END_ALLOW_THREADS
+ return NULL;
+ }
+
+ /* As Alexander Larsson pointed out on dbus@lists.fd.o on 2006-11-30,
+ * the API has a race condition if set_notify runs in one thread and a
+ * mail loop runs in another - if the reply gets in before set_notify
+ * runs, the notify isn't called and there is no indication of error.
+ *
+ * The workaround is to check for completion immediately, but this also
+ * has a race which might lead to getting the notify called twice if
+ * we're unlucky. So I use the list to arrange for the notify to be
+ * deleted before it's called for the second time. The GIL protects
+ * the critical section in which I delete the callback from the list.
+ */
+ if (dbus_pending_call_get_completed(pc)) {
+ /* the first race condition happened, so call the callable here.
+ * FIXME: we ought to arrange for the callable to run from the
+ * mainloop thread, like it would if the race hadn't happened...
+ * this needs a better mainloop abstraction, though.
+ */
+ _pending_call_notify_function(pc, list);
+ }
+
+ Py_CLEAR(list);
+ self->pc = pc;
+ return (PyObject *)self;
+}
+
+static void
+PendingCall_tp_dealloc (PendingCall *self)
+{
+ if (self->pc) {
+ Py_BEGIN_ALLOW_THREADS
+ dbus_pending_call_unref(self->pc);
+ Py_END_ALLOW_THREADS
+ }
+ PyObject_Del (self);
+}
+
+static PyMethodDef PendingCall_tp_methods[] = {
+ {"block", (PyCFunction) (void (*)(void))PendingCall_block, METH_NOARGS,
+ PendingCall_block__doc__},
+ {"cancel", (PyCFunction) (void (*)(void))PendingCall_cancel, METH_NOARGS,
+ PendingCall_cancel__doc__},
+ {"get_completed", (PyCFunction) (void (*)(void))PendingCall_get_completed, METH_NOARGS,
+ PendingCall_get_completed__doc__},
+ {NULL, NULL, 0, NULL}
+};
+
+static PyTypeObject PendingCallType = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.lowlevel.PendingCall",
+ sizeof(PendingCall),
+ 0,
+ (destructor)PendingCall_tp_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
+ PendingCall_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ PendingCall_tp_methods, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ /* deliberately not callable! Use PendingCall_ConsumeDBusPendingCall */
+ 0, /* tp_new */
+};
+
+dbus_bool_t
+dbus_py_init_pending_call (void)
+{
+ if (PyType_Ready (&PendingCallType) < 0) return 0;
+ return 1;
+}
+
+dbus_bool_t
+dbus_py_insert_pending_call (PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF (&PendingCallType);
+ if (PyModule_AddObject (this_module, "PendingCall",
+ (PyObject *)&PendingCallType) < 0) return 0;
+ return 1;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/server.c b/dbus_bindings/server.c
new file mode 100644
index 0000000..705ba2f
--- /dev/null
+++ b/dbus_bindings/server.c
@@ -0,0 +1,616 @@
+/* Implementation of the _dbus_bindings Server type, a Python wrapper
+ * for DBusServer.
+ *
+ * Copyright (C) 2008 Openismus GmbH <http://openismus.com/>
+ * Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+/* Server definition ================================================ */
+
+typedef struct {
+ PyObject_HEAD
+ DBusServer *server;
+
+ /* The Connection subtype for which this Server is a factory */
+ PyObject *conn_class;
+
+ /* Weak-references list to make server weakly referenceable */
+ PyObject *weaklist;
+
+ PyObject *mainloop;
+} Server;
+
+PyDoc_STRVAR(Server_tp_doc,
+"A D-Bus server.\n"
+"\n"
+"::\n"
+"\n"
+" Server(address, connection_subtype, mainloop=None, auth_mechanisms=None)\n"
+" -> Server\n"
+);
+
+/* D-Bus Server user data slot, containing an owned reference to either
+ * the Server, or a weakref to the Server.
+ */
+static dbus_int32_t _server_python_slot;
+
+/* C API for main-loop hooks ======================================== */
+
+/* Return a borrowed reference to the DBusServer which underlies this
+ * Server. */
+DBusServer *
+DBusPyServer_BorrowDBusServer(PyObject *self)
+{
+ DBusServer *dbs;
+
+ TRACE(self);
+ if (!DBusPyServer_Check(self)) {
+ PyErr_SetString(PyExc_TypeError, "A dbus.server.Server is required");
+ return NULL;
+ }
+ dbs = ((Server *)self)->server;
+ if (!dbs) {
+ PyErr_SetString(PyExc_RuntimeError, "Server is in an invalid "
+ "state: no DBusServer");
+ return NULL;
+ }
+ return dbs;
+}
+
+/* Internal C API =================================================== */
+
+static dbus_bool_t
+DBusPyServer_set_auth_mechanisms(Server *self,
+ PyObject *auth_mechanisms)
+{
+ PyObject *fast_seq = NULL, *references = NULL;
+ Py_ssize_t length;
+ Py_ssize_t i;
+ /* a mutable array of constant strings */
+ const char **list = NULL;
+ dbus_bool_t ret = FALSE;
+
+ fast_seq = PySequence_Fast(auth_mechanisms,
+ "Expecting sequence for auth_mechanisms parameter");
+
+ if (!fast_seq)
+ return FALSE;
+
+ length = PySequence_Fast_GET_SIZE(fast_seq);
+
+ list = calloc (length + 1, sizeof (char *));
+
+ if (!list) {
+ PyErr_NoMemory();
+ goto finally;
+ }
+
+ if (!(references = PyTuple_New(length)))
+ goto finally;
+
+ for (i = 0; i < length; ++i) {
+ PyObject *am, *am_as_bytes;
+
+ am = PySequence_Fast_GET_ITEM(auth_mechanisms, i);
+ if (!am)
+ goto finally;
+
+ if (PyUnicode_Check(am)) {
+ am_as_bytes = PyUnicode_AsUTF8String(am);
+ if (!am_as_bytes)
+ goto finally;
+ }
+ else {
+ am_as_bytes = am;
+ Py_INCREF(am_as_bytes);
+ }
+ list[i] = PyBytes_AsString(am_as_bytes);
+ if (!list[i])
+ goto finally;
+
+ PyTuple_SET_ITEM(references, i, am_as_bytes);
+ }
+
+ list[length] = NULL;
+
+ Py_BEGIN_ALLOW_THREADS
+ dbus_server_set_auth_mechanisms(self->server, list);
+ Py_END_ALLOW_THREADS
+
+ ret = TRUE;
+
+finally:
+ if (list)
+ free (list);
+ Py_CLEAR(fast_seq);
+ Py_CLEAR(references);
+ return ret;
+}
+
+/* Return a new reference to a Python Server or subclass corresponding
+ * to the DBusServer server. For use in callbacks.
+ *
+ * Raises AssertionError if the DBusServer does not have a Server.
+ */
+static PyObject *
+DBusPyServer_ExistingFromDBusServer(DBusServer *server)
+{
+ PyObject *self, *ref;
+
+ Py_BEGIN_ALLOW_THREADS
+ ref = (PyObject *)dbus_server_get_data(server,
+ _server_python_slot);
+ Py_END_ALLOW_THREADS
+ if (ref) {
+ DBG("(DBusServer *)%p has weak reference at %p", server, ref);
+ self = PyWeakref_GetObject(ref); /* still a borrowed ref */
+ if (self && self != Py_None && DBusPyServer_Check(self)) {
+ DBG("(DBusServer *)%p has weak reference at %p pointing to %p",
+ server, ref, self);
+ TRACE(self);
+ Py_INCREF(self);
+ TRACE(self);
+ return self;
+ }
+ }
+
+ PyErr_SetString(PyExc_AssertionError,
+ "D-Bus server does not have a Server "
+ "instance associated with it");
+ return NULL;
+}
+
+static void
+DBusPyServer_new_connection_cb(DBusServer *server,
+ DBusConnection *conn,
+ void *data UNUSED)
+{
+ PyGILState_STATE gil = PyGILState_Ensure();
+ PyObject *self = NULL;
+ PyObject *method = NULL;
+
+ self = DBusPyServer_ExistingFromDBusServer(server);
+ if (!self) goto out;
+ TRACE(self);
+
+ method = PyObject_GetAttrString(self, "_on_new_connection");
+ TRACE(method);
+
+ if (method) {
+ PyObject *conn_class = ((Server *)self)->conn_class;
+ PyObject *wrapper = DBusPyLibDBusConnection_New(conn);
+ PyObject *conn_obj;
+ PyObject *result;
+
+ if (!wrapper)
+ goto out;
+
+ conn_obj = PyObject_CallFunctionObjArgs((PyObject *)conn_class,
+ wrapper, ((Server*) self)->mainloop, NULL);
+ Py_CLEAR(wrapper);
+
+ if (!conn_obj)
+ goto out;
+
+ result = PyObject_CallFunctionObjArgs(method, conn_obj, NULL);
+ Py_CLEAR (conn_obj);
+
+ /* discard result if not NULL, and fall through regardless */
+ Py_CLEAR(result);
+ }
+
+out:
+ Py_CLEAR(method);
+ Py_CLEAR(self);
+
+ if (PyErr_Occurred())
+ PyErr_Print();
+
+ PyGILState_Release(gil);
+}
+
+/* Return a new reference to a Python Server or subclass (given by cls)
+ * corresponding to the DBusServer server, which must have been newly
+ * created. For use by the Server constructor.
+ *
+ * Raises AssertionError if the DBusServer already has a Server.
+ *
+ * One reference to server is stolen - either the returned DBusPyServer
+ * claims it, or it's unreffed.
+ */
+static PyObject *
+DBusPyServer_NewConsumingDBusServer(PyTypeObject *cls,
+ DBusServer *server,
+ PyObject *conn_class,
+ PyObject *mainloop,
+ PyObject *auth_mechanisms)
+{
+ Server *self = NULL;
+ PyObject *ref;
+ dbus_bool_t ok;
+
+ DBG("%s(cls=%p, server=%p, mainloop=%p, auth_mechanisms=%p)",
+ __func__, cls, server, mainloop, auth_mechanisms);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(server);
+
+ Py_BEGIN_ALLOW_THREADS
+ ref = (PyObject *)dbus_server_get_data(server,
+ _server_python_slot);
+ Py_END_ALLOW_THREADS
+ if (ref) {
+ self = (Server *)PyWeakref_GetObject(ref);
+ ref = NULL;
+ if (self && (PyObject *)self != Py_None) {
+ self = NULL;
+ PyErr_SetString(PyExc_AssertionError,
+ "Newly created D-Bus server already has a "
+ "Server instance associated with it");
+ DBG("%s() fail - assertion failed, DBusPyServer has a DBusServer already", __func__);
+ DBG_WHEREAMI;
+ return NULL;
+ }
+ }
+ ref = NULL;
+
+ /* Change mainloop from a borrowed reference to an owned reference */
+ if (!mainloop || mainloop == Py_None) {
+ mainloop = dbus_py_get_default_main_loop();
+
+ if (!mainloop || mainloop == Py_None) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "To run a D-Bus server, you need to either "
+ "pass mainloop=... to the constructor or call "
+ "dbus.set_default_main_loop(...)");
+ goto err;
+ }
+ }
+ else {
+ Py_INCREF(mainloop);
+ }
+
+ DBG("Constructing Server from DBusServer at %p", server);
+
+ self = (Server *)(cls->tp_alloc(cls, 0));
+ if (!self) goto err;
+ TRACE(self);
+
+ DBG_WHEREAMI;
+
+ self->server = NULL;
+
+ Py_INCREF(conn_class);
+ self->conn_class = conn_class;
+
+ self->mainloop = mainloop;
+ mainloop = NULL; /* don't DECREF it - the DBusServer owns it now */
+
+ ref = PyWeakref_NewRef((PyObject *)self, NULL);
+ if (!ref) goto err;
+ DBG("Created weak ref %p to (Server *)%p for (DBusServer *)%p",
+ ref, self, server);
+
+ Py_BEGIN_ALLOW_THREADS
+ ok = dbus_server_set_data(server, _server_python_slot,
+ (void *)ref,
+ (DBusFreeFunction)dbus_py_take_gil_and_xdecref);
+ Py_END_ALLOW_THREADS
+
+ if (ok) {
+ DBG("Attached weak ref %p ((Server *)%p) to (DBusServer *)%p",
+ ref, self, server);
+
+ ref = NULL; /* don't DECREF it - the DBusServer owns it now */
+ }
+ else {
+ DBG("Failed to attached weak ref %p ((Server *)%p) to "
+ "(DBusServer *)%p - will dispose of it", ref, self, server);
+ PyErr_NoMemory();
+ goto err;
+ }
+
+ DBUS_PY_RAISE_VIA_GOTO_IF_FAIL(server, err);
+ self->server = server;
+ /* the DBusPyServer will close it now */
+ server = NULL;
+
+ if (self->mainloop != Py_None &&
+ !dbus_py_set_up_server((PyObject *)self, self->mainloop))
+ goto err;
+
+ if (auth_mechanisms && auth_mechanisms != Py_None &&
+ !DBusPyServer_set_auth_mechanisms(self, auth_mechanisms))
+ goto err;
+
+ Py_BEGIN_ALLOW_THREADS
+ dbus_server_set_new_connection_function(self->server,
+ DBusPyServer_new_connection_cb,
+ NULL, NULL);
+ Py_END_ALLOW_THREADS
+
+ DBG("%s() -> %p", __func__, self);
+ TRACE(self);
+ return (PyObject *)self;
+
+err:
+ DBG("Failed to construct Server from DBusServer at %p", server);
+ Py_CLEAR(mainloop);
+ Py_CLEAR(self);
+ Py_CLEAR(ref);
+
+ if (server) {
+ Py_BEGIN_ALLOW_THREADS
+ dbus_server_disconnect(server);
+ dbus_server_unref(server);
+ Py_END_ALLOW_THREADS
+ }
+
+ DBG("%s() fail", __func__);
+ DBG_WHEREAMI;
+ return NULL;
+}
+
+/* Server type-methods ============================================== */
+
+static PyObject *
+Server_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ DBusServer *server;
+ const char *address;
+ DBusError error;
+ PyObject *self, *conn_class, *mainloop = NULL, *auth_mechanisms = NULL;
+ static char *argnames[] = { "address", "connection_class", "mainloop",
+ "auth_mechanisms", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|OO", argnames,
+ &address, &conn_class, &mainloop, &auth_mechanisms)) {
+ return NULL;
+ }
+
+ if (!PyType_Check(conn_class) ||
+ !PyType_IsSubtype((PyTypeObject *) conn_class, &DBusPyConnection_Type)) {
+ /* strictly speaking, it can be any subtype of
+ * _dbus_bindings._Connection - but nobody else should be subtyping
+ * that, so let's keep this slightly inaccurate message */
+ PyErr_SetString(PyExc_TypeError, "connection_class must be "
+ "dbus.connection.Connection or a subtype");
+ return NULL;
+ }
+
+ dbus_error_init(&error);
+
+ Py_BEGIN_ALLOW_THREADS
+ server = dbus_server_listen(address, &error);
+ Py_END_ALLOW_THREADS
+
+ if (!server) {
+ DBusPyException_ConsumeError(&error);
+ return NULL;
+ }
+
+ self = DBusPyServer_NewConsumingDBusServer(cls, server, conn_class,
+ mainloop, auth_mechanisms);
+
+ if (!self) {
+ return NULL;
+ }
+
+ ((Server *)self)->weaklist = NULL;
+ TRACE(self);
+
+ return self;
+}
+
+/* Destructor */
+static void Server_tp_dealloc(Server *self)
+{
+ DBusServer *server = self->server;
+ PyObject *et, *ev, *etb;
+
+ /* avoid clobbering any pending exception */
+ PyErr_Fetch(&et, &ev, &etb);
+
+ if (self->weaklist) {
+ PyObject_ClearWeakRefs((PyObject *)self);
+ }
+
+ TRACE(self);
+ DBG("Deallocating Server at %p (DBusServer at %p)", self, server);
+ DBG_WHEREAMI;
+
+ if (server) {
+ DBG("Server at %p has a server, disconnecting it...", self);
+ Py_BEGIN_ALLOW_THREADS
+ dbus_server_disconnect(server);
+ Py_END_ALLOW_THREADS
+ }
+
+ Py_CLEAR(self->mainloop);
+
+ /* make sure to do this last to preserve the invariant that
+ * self->server is always non-NULL for any referenced Server.
+ */
+ DBG("Server at %p: nulling self->server", self);
+ self->server = NULL;
+
+ if (server) {
+ DBG("Server at %p: unreffing server", self);
+ dbus_server_unref(server);
+ }
+
+ DBG("Server at %p: freeing self", self);
+ PyErr_Restore(et, ev, etb);
+ (Py_TYPE(self)->tp_free)((PyObject *)self);
+}
+
+PyDoc_STRVAR(Server_disconnect__doc__,
+"disconnect()\n\n"
+"Releases the server's address and stops listening for new clients.\n\n"
+"If called more than once, only the first call has an effect.");
+static PyObject *
+Server_disconnect (Server *self, PyObject *args UNUSED)
+{
+ TRACE(self);
+ if (self->server) {
+ Py_BEGIN_ALLOW_THREADS
+ dbus_server_disconnect(self->server);
+ Py_END_ALLOW_THREADS
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Server_get_address__doc__,
+"get_address() -> str\n\n"
+"Returns the address of the server.");
+static PyObject *
+Server_get_address(Server *self, PyObject *args UNUSED)
+{
+ const char *address;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->server);
+ Py_BEGIN_ALLOW_THREADS
+ address = dbus_server_get_address(self->server);
+ Py_END_ALLOW_THREADS
+
+ return PyUnicode_FromString(address);
+}
+
+PyDoc_STRVAR(Server_get_id__doc__,
+"get_id() -> str\n\n"
+"Returns the unique ID of the server.");
+static PyObject *
+Server_get_id(Server *self, PyObject *args UNUSED)
+{
+ const char *id;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->server);
+ Py_BEGIN_ALLOW_THREADS
+ id = dbus_server_get_id(self->server);
+ Py_END_ALLOW_THREADS
+
+ return PyUnicode_FromString(id);
+}
+
+PyDoc_STRVAR(Server_get_is_connected__doc__,
+"get_is_connected() -> bool\n\n"
+"Return true if this Server is still listening for new connections.\n");
+static PyObject *
+Server_get_is_connected (Server *self, PyObject *args UNUSED)
+{
+ dbus_bool_t ret;
+
+ TRACE(self);
+ DBUS_PY_RAISE_VIA_NULL_IF_FAIL(self->server);
+ Py_BEGIN_ALLOW_THREADS
+ ret = dbus_server_get_is_connected(self->server);
+ Py_END_ALLOW_THREADS
+ return PyBool_FromLong(ret);
+}
+
+/* Server type object =============================================== */
+
+struct PyMethodDef DBusPyServer_tp_methods[] = {
+#define ENTRY(name, flags) {#name, (PyCFunction) (void (*)(void))Server_##name, flags, Server_##name##__doc__}
+ ENTRY(disconnect, METH_NOARGS),
+ ENTRY(get_address, METH_NOARGS),
+ ENTRY(get_id, METH_NOARGS),
+ ENTRY(get_is_connected, METH_NOARGS),
+ {NULL},
+#undef ENTRY
+};
+
+PyTypeObject DBusPyServer_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "_dbus_bindings._Server",/*tp_name*/
+ sizeof(Server), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ /* methods */
+ (destructor)Server_tp_dealloc,
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ 0, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash*/
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ Server_tp_doc, /*tp_doc*/
+ 0, /*tp_traverse*/
+ 0, /*tp_clear*/
+ 0, /*tp_richcompare*/
+ offsetof(Server, weaklist), /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ DBusPyServer_tp_methods,/*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ 0, /*tp_init*/
+ 0, /*tp_alloc*/
+ Server_tp_new, /*tp_new*/
+ 0, /*tp_free*/
+ 0, /*tp_is_gc*/
+};
+
+dbus_bool_t
+dbus_py_init_server_types(void)
+{
+ /* Get a slot to store our weakref on DBus Server */
+ _server_python_slot = -1;
+ if (!dbus_server_allocate_data_slot(&_server_python_slot))
+ return FALSE;
+
+ if (PyType_Ready(&DBusPyServer_Type) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+dbus_bool_t
+dbus_py_insert_server_types(PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF (&DBusPyServer_Type);
+ if (PyModule_AddObject(this_module, "_Server",
+ (PyObject *)&DBusPyServer_Type) < 0) return FALSE;
+
+ return TRUE;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/signature.c b/dbus_bindings/signature.c
new file mode 100644
index 0000000..cd2ba03
--- /dev/null
+++ b/dbus_bindings/signature.c
@@ -0,0 +1,251 @@
+/* Implementation of Signature type for D-Bus bindings.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "types-internal.h"
+
+PyDoc_STRVAR(Signature_tp_doc,
+"Signature(value: str or unicode[, variant_level: int])\n"
+"\n"
+"A string subclass whose values are restricted to valid D-Bus\n"
+"signatures. When iterated over, instead of individual characters it\n"
+"produces Signature instances representing single complete types.\n"
+"\n"
+"``value`` must be a valid D-Bus signature (zero or more single complete\n"
+"types).\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing a signature, this is represented in Python by a\n"
+" Signature with variant_level==2.\n"
+);
+
+typedef struct {
+ PyObject_HEAD
+ PyObject *bytes;
+ DBusSignatureIter iter;
+} SignatureIter;
+
+static void
+SignatureIter_tp_dealloc (SignatureIter *self)
+{
+ Py_CLEAR(self->bytes);
+ PyObject_Del(self);
+}
+
+static PyObject *
+SignatureIter_tp_iternext (SignatureIter *self)
+{
+ char *sig;
+ PyObject *obj;
+
+ /* Stop immediately if finished or not correctly initialized */
+ if (!self->bytes) return NULL;
+
+ sig = dbus_signature_iter_get_signature(&(self->iter));
+ if (!sig) return PyErr_NoMemory();
+ obj = PyObject_CallFunction((PyObject *)&DBusPySignature_Type, "s", sig);
+ dbus_free(sig);
+ if (!obj) return NULL;
+
+ if (!dbus_signature_iter_next(&(self->iter))) {
+ /* mark object as having been finished with */
+ Py_CLEAR(self->bytes);
+ }
+
+ return obj;
+}
+
+static PyObject *
+SignatureIter_tp_iter(PyObject *self)
+{
+ Py_INCREF(self);
+ return self;
+}
+
+static PyTypeObject SignatureIterType = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "_dbus_bindings._SignatureIter",
+ sizeof(SignatureIter),
+ 0,
+ (destructor)SignatureIter_tp_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
+ 0, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ SignatureIter_tp_iter, /* tp_iter */
+ (iternextfunc)SignatureIter_tp_iternext, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ /* deliberately not callable! Use iter(Signature) instead */
+ 0, /* tp_new */
+ 0, /* tp_free */
+};
+
+static PyObject *
+Signature_tp_iter(PyObject *self)
+{
+ SignatureIter *iter = PyObject_New(SignatureIter, &SignatureIterType);
+ PyObject *self_as_bytes;
+
+ if (!iter) return NULL;
+
+ self_as_bytes = PyUnicode_AsUTF8String(self);
+ if (!self_as_bytes) {
+ Py_CLEAR(iter);
+ return NULL;
+ }
+
+ if (PyBytes_GET_SIZE(self_as_bytes) > 0) {
+ iter->bytes = self_as_bytes;
+ dbus_signature_iter_init(&(iter->iter),
+ PyBytes_AS_STRING(self_as_bytes));
+ }
+ else {
+ /* this is a null string, make a null iterator */
+ iter->bytes = NULL;
+ Py_CLEAR(self_as_bytes);
+ }
+ return (PyObject *)iter;
+}
+
+static PyObject *
+Signature_tp_new (PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ const char *str = NULL;
+ PyObject *ignored;
+ static char *argnames[] = {"object_path", "variant_level", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|O:__new__", argnames,
+ &str, &ignored)) return NULL;
+ if (!dbus_signature_validate(str, NULL)) {
+ PyErr_SetString(PyExc_ValueError, "Corrupt type signature");
+ return NULL;
+ }
+ return (DBusPyStrBase_Type.tp_new)(cls, args, kwargs);
+}
+
+PyTypeObject DBusPySignature_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.Signature",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ Signature_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ Signature_tp_iter, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPyStrBase_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ Signature_tp_new, /* tp_new */
+ 0, /* tp_free */
+};
+
+dbus_bool_t
+dbus_py_init_signature(void)
+{
+ if (PyType_Ready(&SignatureIterType) < 0) return 0;
+
+ DBusPySignature_Type.tp_base = &DBusPyStrBase_Type;
+ if (PyType_Ready(&DBusPySignature_Type) < 0) return 0;
+
+ return 1;
+}
+
+dbus_bool_t
+dbus_py_insert_signature(PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF(&DBusPySignature_Type);
+ if (PyModule_AddObject(this_module, "Signature",
+ (PyObject *)&DBusPySignature_Type) < 0) return 0;
+ Py_INCREF(&SignatureIterType);
+ if (PyModule_AddObject(this_module, "_SignatureIter",
+ (PyObject *)&SignatureIterType) < 0) return 0;
+
+ return 1;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/string.c b/dbus_bindings/string.c
new file mode 100644
index 0000000..e57a483
--- /dev/null
+++ b/dbus_bindings/string.c
@@ -0,0 +1,267 @@
+/* Simple D-Bus types: ObjectPath and other string types.
+ *
+ * Copyright (C) 2006-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include "types-internal.h"
+#include <structmember.h>
+
+/* Object path ====================================================== */
+
+PyDoc_STRVAR(ObjectPath_tp_doc,
+"dbus.ObjectPath(path: str[, variant_level: int=0])\n"
+"A D-Bus object path, such as ``/com/example/MyApp/Documents/abc``.\n"
+"\n"
+"ObjectPath is a subtype of :py:class:`str`, and object-paths behave like strings.\n"
+"\n"
+"path must be an ASCII string following the syntax of object paths.\n"
+"variant_level must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing an object path, this is represented in Python by an\n"
+" ObjectPath with variant_level==2.\n"
+);
+
+static PyObject *
+ObjectPath_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ const char *str = NULL;
+ long variantness = 0;
+ static char *argnames[] = {"object_path", "variant_level", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|l:__new__", argnames,
+ &str, &variantness)) return NULL;
+ if (!dbus_py_validate_object_path(str)) {
+ return NULL;
+ }
+ return (DBusPyStrBase_Type.tp_new)(cls, args, kwargs);
+}
+
+PyTypeObject DBusPyObjectPath_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.ObjectPath",
+ 0,
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ ObjectPath_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&DBusPyStrBase_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ ObjectPath_tp_new, /* tp_new */
+};
+
+/* Unicode string representation ==================================== */
+
+PyDoc_STRVAR(String_tp_doc,
+"dbus.String(value: str or unicode[, variant_level: int])\n"
+"\n"
+"A string represented using Unicode - a subtype of ``unicode`` (Python 2)\n"
+"or ``str`` (Python 3).\n"
+"\n"
+"All strings on D-Bus are required to be valid Unicode; in the \"wire\n"
+"protocol\" they're transported as UTF-8.\n"
+"\n"
+"By default, when strings are converted from D-Bus to Python, they\n"
+"come out as this class. In Python 2, if you prefer to get UTF-8 strings\n"
+"(as instances\n"
+"of a subtype of `str`) or you want to avoid the conversion overhead of\n"
+"going from UTF-8 to Python's internal Unicode representation, see the\n"
+"documentation for `dbus.UTF8String`.\n"
+"\n"
+"variant_level must be non-negative; the default is 0.\n"
+);
+
+static PyMemberDef String_tp_members[] = {
+ {"variant_level", T_LONG, offsetof(DBusPyString, variant_level),
+ READONLY,
+ "Indicates how many nested Variant containers this object\n"
+ "is contained in: if a message's wire format has a variant containing a\n"
+ "variant containing an array, this is represented in Python by a\n"
+ "String or UTF8String with variant_level==2.\n"
+ },
+ {NULL},
+};
+
+static PyObject *
+String_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ PyObject *self;
+ long variantness = 0;
+ static char *argnames[] = {"variant_level", NULL};
+
+ if (PyTuple_Size(args) > 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "__new__ takes at most one positional parameter");
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(dbus_py_empty_tuple, kwargs,
+ "|l:__new__", argnames,
+ &variantness)) return NULL;
+ if (variantness < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "variant_level must be non-negative");
+ return NULL;
+ }
+ self = (PyUnicode_Type.tp_new)(cls, args, NULL);
+ if (self) {
+ ((DBusPyString *)self)->variant_level = variantness;
+ }
+ return self;
+}
+
+static PyObject *
+String_tp_repr(PyObject *self)
+{
+ PyObject *parent_repr = (PyUnicode_Type.tp_repr)(self);
+ PyObject *my_repr;
+
+ if (!parent_repr) {
+ return NULL;
+ }
+ if (((DBusPyString *)self)->variant_level > 0) {
+ my_repr = PyUnicode_FromFormat("%s(%V, variant_level=%ld)",
+ Py_TYPE(self)->tp_name,
+ REPRV(parent_repr),
+ ((DBusPyString *)self)->variant_level);
+ }
+ else {
+ my_repr = PyUnicode_FromFormat("%s(%V)", Py_TYPE(self)->tp_name,
+ REPRV(parent_repr));
+ }
+ /* whether my_repr is NULL or not: */
+ Py_CLEAR(parent_repr);
+ return my_repr;
+}
+
+PyTypeObject DBusPyString_Type = {
+ PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
+ "dbus.String",
+ sizeof(DBusPyString),
+ 0,
+ 0, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ String_tp_repr, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ PyObject_GenericGetAttr, /* tp_getattro */
+ dbus_py_immutable_setattro, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ String_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ String_tp_members, /* tp_members */
+ 0, /* tp_getset */
+ DEFERRED_ADDRESS(&PyUnicode_Type), /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ String_tp_new, /* tp_new */
+};
+
+dbus_bool_t
+dbus_py_init_string_types(void)
+{
+ /* don't need to do strange contortions for unicode, since it's not a
+ * "variable-size" object (it has a pointer to its data instead)
+ */
+ if (PyUnicode_Type.tp_itemsize != 0) {
+ fprintf(stderr, "dbus-python is not compatible with this version of "
+ "Python (unicode objects are assumed to be fixed-size)");
+ return 0;
+ }
+ DBusPyString_Type.tp_base = &PyUnicode_Type;
+ if (PyType_Ready(&DBusPyString_Type) < 0) return 0;
+
+ DBusPyObjectPath_Type.tp_base = &DBusPyStrBase_Type;
+ if (PyType_Ready(&DBusPyObjectPath_Type) < 0) return 0;
+
+ return 1;
+}
+
+dbus_bool_t
+dbus_py_insert_string_types(PyObject *this_module)
+{
+ /* PyModule_AddObject steals a ref */
+ Py_INCREF(&DBusPyObjectPath_Type);
+ Py_INCREF(&DBusPyString_Type);
+ if (PyModule_AddObject(this_module, "ObjectPath",
+ (PyObject *)&DBusPyObjectPath_Type) < 0) return 0;
+ if (PyModule_AddObject(this_module, "String",
+ (PyObject *)&DBusPyString_Type) < 0) return 0;
+
+
+ return 1;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/types-internal.h b/dbus_bindings/types-internal.h
new file mode 100644
index 0000000..02c931f
--- /dev/null
+++ b/dbus_bindings/types-internal.h
@@ -0,0 +1,97 @@
+/* D-Bus types: implementation internals
+ *
+ * Copyright (C) 2006-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include <Python.h>
+
+/* In Python2 >= 2.6 this aliases PyString to PyBytes. There is no PyString
+ * in Python 3, so this allows the C extension to be compilable in both Python
+ * versions.
+ */
+#include <bytesobject.h>
+
+#ifndef DBUS_BINDINGS_TYPES_INTERNAL_H
+#define DBUS_BINDINGS_TYPES_INTERNAL_H
+
+extern PyTypeObject DBusPyLongBase_Type;
+DEFINE_CHECK(DBusPyLongBase)
+
+extern PyTypeObject DBusPyFloatBase_Type;
+DEFINE_CHECK(DBusPyFloatBase)
+
+typedef struct {
+ PyFloatObject base;
+ long variant_level;
+} DBusPyFloatBase;
+
+typedef struct {
+ PyUnicodeObject unicode;
+ long variant_level;
+} DBusPyString;
+
+extern PyTypeObject DBusPyStrBase_Type;
+DEFINE_CHECK(DBusPyStrBase)
+
+extern PyTypeObject DBusPyBytesBase_Type;
+DEFINE_CHECK(DBusPyBytesBase)
+
+dbus_int16_t dbus_py_int16_range_check(PyObject *);
+dbus_uint16_t dbus_py_uint16_range_check(PyObject *);
+dbus_int32_t dbus_py_int32_range_check(PyObject *);
+dbus_uint32_t dbus_py_uint32_range_check(PyObject *);
+
+#if defined(DBUS_HAVE_INT64) && defined(HAVE_LONG_LONG)
+# define DBUS_PYTHON_64_BIT_WORKS 1
+dbus_int64_t dbus_py_int64_range_check(PyObject *);
+dbus_uint64_t dbus_py_uint64_range_check(PyObject *);
+#else
+# undef DBUS_PYTHON_64_BIT_WORKS
+#endif /* defined(DBUS_HAVE_INT64) && defined(HAVE_LONG_LONG) */
+
+extern PyObject *dbus_py_variant_level_const;
+extern PyObject *dbus_py_signature_const;
+extern PyObject *dbus_py__dbus_object_path__const;
+
+typedef struct {
+ PyListObject super;
+ PyObject *signature;
+ long variant_level;
+} DBusPyArray;
+
+typedef struct {
+ PyDictObject super;
+ PyObject *signature;
+ long variant_level;
+} DBusPyDict;
+
+PyObject *dbus_py_variant_level_getattro(PyObject *obj, PyObject *name);
+dbus_bool_t dbus_py_variant_level_set(PyObject *obj, long variant_level);
+void dbus_py_variant_level_clear(PyObject *obj);
+long dbus_py_variant_level_get(PyObject *obj);
+
+#endif
diff --git a/dbus_bindings/unixfd.c b/dbus_bindings/unixfd.c
new file mode 100644
index 0000000..63a2402
--- /dev/null
+++ b/dbus_bindings/unixfd.c
@@ -0,0 +1,281 @@
+/* Simple D-Bus types: Unix FD type.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2010 Signove <http://www.signove.com>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "types-internal.h"
+
+PyDoc_STRVAR(UnixFd_tp_doc,
+"dbus.UnixFd(value: int or file object[, variant_level: int])\n"
+"\n"
+"A Unix Fd.\n"
+"\n"
+"``value`` must be the integer value of a file descriptor, or an object that\n"
+"implements the fileno() method. Otherwise, `ValueError` will be\n"
+"raised.\n"
+"\n"
+"UnixFd keeps a dup() (duplicate) of the supplied file descriptor. The\n"
+"caller remains responsible for closing the original fd.\n"
+"\n"
+":py:attr:`variant_level` must be non-negative; the default is 0.\n"
+"\n"
+".. py:attribute:: variant_level\n"
+"\n"
+" Indicates how many nested Variant containers this object\n"
+" is contained in: if a message's wire format has a variant containing a\n"
+" variant containing an Unix Fd, this is represented in Python by an\n"
+" Unix Fd with variant_level==2.\n"
+);
+
+typedef struct {
+ PyObject_HEAD
+ int fd;
+ long variant_level;
+} UnixFdObject;
+
+/* Return values:
+ * -2 - the long value overflows an int
+ * -1 - Python failed producing a long (or in Python 2 an int)
+ * 0 - success
+ * 1 - arg is not a long (or in Python 2 an int)
+ *
+ * Or to summarize:
+ * status < 0 - an error occurred, and a Python exception is set.
+ * status == 0 - all is okay, output argument *fd is set.
+ * status > 0 - try something else
+ */
+static int
+make_fd(PyObject *arg, int *fd)
+{
+ long fd_arg;
+
+ if (PyLong_Check(arg))
+ {
+ /* on Python 2 this accepts either int or long */
+ fd_arg = PyLong_AsLong(arg);
+ if (fd_arg == -1 && PyErr_Occurred()) {
+ return -1;
+ }
+ }
+ else {
+ return 1;
+ }
+ /* Check for int overflow. */
+ if (fd_arg < INT_MIN || fd_arg > INT_MAX) {
+ PyErr_Format(PyExc_ValueError, "int is outside fd range");
+ return -2;
+ }
+ *fd = (int)fd_arg;
+ return 0;
+}
+
+static PyObject *
+UnixFd_tp_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
+{
+ UnixFdObject *self = NULL;
+ PyObject *arg;
+ int status, fd, fd_original = -1;
+
+ static char *argnames[] = {"fd", "variant_level", NULL};
+ long variant_level = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|l", argnames, &arg, &variant_level)) {
+ return NULL;
+ }
+
+ if (variant_level < 0) {
+ PyErr_Format(PyExc_ValueError, "variant_level cannot be less than 0");
+ return NULL;
+ }
+
+ status = make_fd(arg, &fd_original);
+ if (status < 0)
+ return NULL;
+
+ if (status > 0) {
+ if (PyObject_HasAttrString(arg, "fileno")) {
+ PyObject *fd_number = PyObject_CallMethod(arg, "fileno", NULL);
+ if (!fd_number)
+ return NULL;
+ status = make_fd(fd_number, &fd_original);
+ Py_CLEAR(fd_number);
+ if (status < 0)
+ return NULL;
+ if (status > 0) {
+ PyErr_Format(PyExc_ValueError, "Argument's fileno() method "
+ "returned a non-int value");
+ return NULL;
+ }
+ /* fd_original is all good. */
+ }
+ else {
+ PyErr_Format(PyExc_ValueError, "Argument is not int and does not "
+ "implement fileno() method");
+ return NULL;
+ }
+ }
+ assert(fd_original >= 0);
+ fd = dup(fd_original);
+ if (fd < 0) {
+ PyErr_Format(PyExc_ValueError, "Invalid file descriptor");
+ return NULL;
+ }
+
+ self = (UnixFdObject *) cls->tp_alloc(cls, 0);
+ if (!self) {
+ close(fd);
+ return NULL;
+ }
+
+ self->fd = fd;
+ self->variant_level = variant_level;
+
+ return (PyObject *)self;
+}
+
+static void
+UnixFd_dealloc(UnixFdObject *self)
+{
+ if (self->fd >= 0) {
+ close(self->fd);
+ self->fd = -1;
+ }
+}
+
+PyDoc_STRVAR(UnixFd_take__doc__,
+"take() -> int\n"
+"\n"
+"This method returns the file descriptor owned by UnixFd object.\n"
+"Note that, once this method is called, closing the file descriptor is\n"
+"the caller's responsibility.\n"
+"\n"
+"This method may be called at most once; UnixFd 'forgets' the file\n"
+"descriptor after it is taken.\n"
+"\n"
+":Raises ValueError: if this method has already been called\n"
+);
+static PyObject *
+UnixFd_take(UnixFdObject *self)
+{
+ PyObject *fdnumber;
+
+ if (self->fd < 0) {
+ PyErr_SetString(PyExc_ValueError, "File descriptor already taken");
+ return NULL;
+ }
+
+ fdnumber = Py_BuildValue("i", self->fd);
+ self->fd = -1;
+
+ return fdnumber;
+}
+
+int
+dbus_py_unix_fd_get_fd(PyObject *self)
+{
+ return ((UnixFdObject *) self)->fd;
+}
+
+static PyMethodDef UnixFd_methods[] = {
+ {"take", (PyCFunction) (void (*)(void)) UnixFd_take, METH_NOARGS, UnixFd_take__doc__ },
+ {NULL}
+};
+
+static struct PyMemberDef UnixFd_tp_members[] = {
+ {"variant_level", T_LONG, offsetof(UnixFdObject, variant_level),
+ READONLY,
+ "Indicates how many nested Variant containers this object\n"
+ "is contained in: if a message's wire format has a variant containing a\n"
+ "variant containing a file descriptor, this is represented in Python by\n"
+ "a UnixFd with variant_level==2.\n"
+ },
+ {NULL},
+};
+
+PyTypeObject DBusPyUnixFd_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dbus.UnixFd",
+ sizeof(UnixFdObject),
+ 0,
+ (destructor) UnixFd_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as_number */
+ 0, /* tp_as_sequence */
+ 0, /* tp_as_mapping */
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer */
+ Py_TPFLAGS_DEFAULT, /* tp_flags */
+ UnixFd_tp_doc, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ UnixFd_methods, /* tp_methods */
+ UnixFd_tp_members, /* tp_members */
+ 0, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ 0, /* tp_init */
+ 0, /* tp_alloc */
+ UnixFd_tp_new, /* tp_new */
+};
+
+dbus_bool_t
+dbus_py_init_unixfd_type(void)
+{
+ if (PyType_Ready(&DBusPyUnixFd_Type) < 0) return 0;
+
+ return 1;
+}
+
+dbus_bool_t
+dbus_py_insert_unixfd_type(PyObject *this_module)
+{
+ Py_INCREF(&DBusPyUnixFd_Type);
+ if (PyModule_AddObject(this_module, "UnixFd",
+ (PyObject *)&DBusPyUnixFd_Type) < 0) return 0;
+ return 1;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_bindings/validation.c b/dbus_bindings/validation.c
new file mode 100644
index 0000000..5a21dc2
--- /dev/null
+++ b/dbus_bindings/validation.c
@@ -0,0 +1,247 @@
+/* Implementation of various validation functions for use in dbus-python.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "dbus_bindings-internal.h"
+
+dbus_bool_t
+dbus_py_validate_bus_name(const char *name,
+ dbus_bool_t may_be_unique,
+ dbus_bool_t may_be_not_unique)
+{
+ dbus_bool_t dot = FALSE;
+ dbus_bool_t unique;
+ char last;
+ const char *ptr;
+
+ if (name[0] == '\0') {
+ PyErr_SetString(PyExc_ValueError, "Invalid bus name: "
+ "may not be empty");
+ return FALSE;
+ }
+ unique = (name[0] == ':');
+ if (unique && !may_be_unique) {
+ PyErr_Format(PyExc_ValueError, "Invalid well-known bus name '%s':"
+ "only unique names may start with ':'", name);
+ return FALSE;
+ }
+ if (!unique && !may_be_not_unique) {
+ PyErr_Format(PyExc_ValueError, "Invalid unique bus name '%s': "
+ "unique names must start with ':'", name);
+ return FALSE;
+ }
+ if (strlen(name) > 255) {
+ PyErr_Format(PyExc_ValueError, "Invalid bus name '%s': "
+ "too long (> 255 characters)", name);
+ return FALSE;
+ }
+ last = '\0';
+ for (ptr = name + (unique ? 1 : 0); *ptr; ptr++) {
+ if (*ptr == '.') {
+ dot = TRUE;
+ if (last == '.') {
+ PyErr_Format(PyExc_ValueError, "Invalid bus name '%s': "
+ "contains substring '..'", name);
+ return FALSE;
+ }
+ else if (last == '\0') {
+ PyErr_Format(PyExc_ValueError, "Invalid bus name '%s': "
+ "must not start with '.'", name);
+ return FALSE;
+ }
+ }
+ else if (*ptr >= '0' && *ptr <= '9') {
+ if (!unique) {
+ if (last == '.') {
+ PyErr_Format(PyExc_ValueError, "Invalid bus name '%s': "
+ "a digit may not follow '.' except in a "
+ "unique name starting with ':'", name);
+ return FALSE;
+ }
+ else if (last == '\0') {
+ PyErr_Format(PyExc_ValueError, "Invalid bus name '%s': "
+ "must not start with a digit", name);
+ return FALSE;
+ }
+ }
+ }
+ else if ((*ptr < 'a' || *ptr > 'z') &&
+ (*ptr < 'A' || *ptr > 'Z') && *ptr != '_' && *ptr != '-') {
+ PyErr_Format(PyExc_ValueError, "Invalid bus name '%s': "
+ "contains invalid character '%c'", name, *ptr);
+ return FALSE;
+ }
+ last = *ptr;
+ }
+ if (last == '.') {
+ PyErr_Format(PyExc_ValueError, "Invalid bus name '%s': must "
+ "not end with '.'", name);
+ return FALSE;
+ }
+ if (!dot) {
+ PyErr_Format(PyExc_ValueError, "Invalid bus name '%s': must "
+ "contain '.'", name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+dbus_bool_t
+dbus_py_validate_member_name(const char *name)
+{
+ const char *ptr;
+
+ if (name[0] == '\0') {
+ PyErr_SetString(PyExc_ValueError, "Invalid member name: may not "
+ "be empty");
+ return FALSE;
+ }
+ if (strlen(name) > 255) {
+ PyErr_Format(PyExc_ValueError, "Invalid member name '%s': "
+ "too long (> 255 characters)", name);
+ return FALSE;
+ }
+ for (ptr = name; *ptr; ptr++) {
+ if (*ptr >= '0' && *ptr <= '9') {
+ if (ptr == name) {
+ PyErr_Format(PyExc_ValueError, "Invalid member name '%s': "
+ "must not start with a digit", name);
+ return FALSE;
+ }
+ }
+ else if ((*ptr < 'a' || *ptr > 'z') &&
+ (*ptr < 'A' || *ptr > 'Z') && *ptr != '_') {
+ PyErr_Format(PyExc_ValueError, "Invalid member name '%s': "
+ "contains invalid character '%c'", name, *ptr);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+dbus_bool_t
+dbus_py_validate_interface_name(const char *name)
+{
+ dbus_bool_t dot = FALSE;
+ char last;
+ const char *ptr;
+
+ if (name[0] == '\0') {
+ PyErr_SetString(PyExc_ValueError, "Invalid interface or error name: "
+ "may not be empty");
+ return FALSE;
+ }
+ if (strlen(name) > 255) {
+ PyErr_Format(PyExc_ValueError, "Invalid interface or error name '%s': "
+ "too long (> 255 characters)", name);
+ return FALSE;
+ }
+ last = '\0';
+ for (ptr = name; *ptr; ptr++) {
+ if (*ptr == '.') {
+ dot = TRUE;
+ if (last == '.') {
+ PyErr_Format(PyExc_ValueError, "Invalid interface or "
+ "error name '%s': contains substring '..'", name);
+ return FALSE;
+ }
+ else if (last == '\0') {
+ PyErr_Format(PyExc_ValueError, "Invalid interface or error "
+ "name '%s': must not start with '.'", name);
+ return FALSE;
+ }
+ }
+ else if (*ptr >= '0' && *ptr <= '9') {
+ if (last == '.') {
+ PyErr_Format(PyExc_ValueError, "Invalid interface or error "
+ "name '%s': a digit may not follow '.'", name);
+ return FALSE;
+ }
+ else if (last == '\0') {
+ PyErr_Format(PyExc_ValueError, "Invalid interface or error "
+ "name '%s': must not start with a digit", name);
+ return FALSE;
+ }
+ }
+ else if ((*ptr < 'a' || *ptr > 'z') &&
+ (*ptr < 'A' || *ptr > 'Z') && *ptr != '_') {
+ PyErr_Format(PyExc_ValueError, "Invalid interface or error "
+ "name '%s': contains invalid character '%c'",
+ name, *ptr);
+ return FALSE;
+ }
+ last = *ptr;
+ }
+ if (last == '.') {
+ PyErr_Format(PyExc_ValueError, "Invalid interface or error name "
+ "'%s': must not end with '.'", name);
+ return FALSE;
+ }
+ if (!dot) {
+ PyErr_Format(PyExc_ValueError, "Invalid interface or error name "
+ "'%s': must contain '.'", name);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+dbus_bool_t
+dbus_py_validate_object_path(const char *path)
+{
+ const char *ptr;
+
+ if (path[0] != '/') {
+ PyErr_Format(PyExc_ValueError, "Invalid object path '%s': does not "
+ "start with '/'", path);
+ return FALSE;
+ }
+ if (path[1] == '\0') return TRUE;
+ for (ptr = path + 1; *ptr; ptr++) {
+ if (*ptr == '/') {
+ if (ptr[-1] == '/') {
+ PyErr_Format(PyExc_ValueError, "Invalid object path '%s': "
+ "contains substring '//'", path);
+ return FALSE;
+ }
+ }
+ else if ((*ptr < 'a' || *ptr > 'z') &&
+ (*ptr < 'A' || *ptr > 'Z') &&
+ (*ptr < '0' || *ptr > '9') && *ptr != '_') {
+ PyErr_Format(PyExc_ValueError, "Invalid object path '%s': "
+ "contains invalid character '%c'", path, *ptr);
+ return FALSE;
+ }
+ }
+ if (ptr[-1] == '/') {
+ PyErr_Format(PyExc_ValueError, "Invalid object path '%s': ends "
+ "with '/' and is not just '/'", path);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/dbus_glib_bindings/module.c b/dbus_glib_bindings/module.c
new file mode 100644
index 0000000..8b04eef
--- /dev/null
+++ b/dbus_glib_bindings/module.c
@@ -0,0 +1,200 @@
+/* Glue code to attach the GObject main loop to D-Bus from within Python.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <Python.h>
+#include <dbus/dbus-python.h>
+#include <dbus-gmain/dbus-gmain.h>
+
+PyMODINIT_FUNC PyInit__dbus_glib_bindings(void);
+
+#if defined(__GNUC__)
+# if __GNUC__ >= 3
+# define UNUSED __attribute__((__unused__))
+# else
+# define UNUSED /*nothing*/
+# endif
+#else
+# define UNUSED /*nothing*/
+#endif
+
+static dbus_bool_t
+dbus_py_glib_set_up_conn(DBusConnection *conn, void *data)
+{
+ GMainContext *ctx = (GMainContext *)data;
+ Py_BEGIN_ALLOW_THREADS
+ _dbus_py_glib_set_up_connection(conn, ctx);
+ Py_END_ALLOW_THREADS
+ return 1;
+}
+
+static dbus_bool_t
+dbus_py_glib_set_up_srv(DBusServer *srv, void *data)
+{
+ GMainContext *ctx = (GMainContext *)data;
+ Py_BEGIN_ALLOW_THREADS
+ _dbus_py_glib_set_up_server(srv, ctx);
+ Py_END_ALLOW_THREADS
+ return 1;
+}
+
+static void
+dbus_py_glib_unref_mainctx(void *data)
+{
+ if (data)
+ g_main_context_unref((GMainContext *)data);
+}
+
+/* Generate a dbus-python NativeMainLoop wrapper from a GLib main loop */
+static PyObject *
+dbus_glib_native_mainloop(GMainContext *ctx)
+{
+ PyObject *loop = DBusPyNativeMainLoop_New4(dbus_py_glib_set_up_conn,
+ dbus_py_glib_set_up_srv,
+ dbus_py_glib_unref_mainctx,
+ ctx ? g_main_context_ref(ctx)
+ : NULL);
+ if (!loop && ctx) {
+ g_main_context_unref(ctx);
+ }
+ return loop;
+}
+
+PyDoc_STRVAR(module_doc, "");
+
+PyDoc_STRVAR(DBusGMainLoop__doc__,
+"DBusGMainLoop([set_as_default=False]) -> NativeMainLoop\n"
+"\n"
+"Return a NativeMainLoop object which can be used to\n"
+"represent the default GLib main context in dbus-python.\n"
+"\n"
+"If the keyword argument set_as_default is given and is true, set the new\n"
+"main loop as the default for all new Connection or Bus instances.\n"
+"\n"
+"Non-default main contexts are not currently supported.\n");
+static PyObject *
+DBusGMainLoop (PyObject *always_null UNUSED, PyObject *args, PyObject *kwargs)
+{
+ PyObject *mainloop, *function, *result;
+ int set_as_default = 0;
+ static char *argnames[] = {"set_as_default", NULL};
+
+ if (PyTuple_Size(args) != 0) {
+ PyErr_SetString(PyExc_TypeError, "DBusGMainLoop() takes no "
+ "positional arguments");
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", argnames,
+ &set_as_default)) {
+ return NULL;
+ }
+
+ mainloop = dbus_glib_native_mainloop(NULL);
+ if (mainloop && set_as_default) {
+ if (!_dbus_bindings_module) {
+ PyErr_SetString(PyExc_ImportError, "_dbus_bindings not imported");
+ Py_CLEAR(mainloop);
+ return NULL;
+ }
+ function = PyObject_GetAttrString(_dbus_bindings_module,
+ "set_default_main_loop");
+ if (!function) {
+ Py_CLEAR(mainloop);
+ return NULL;
+ }
+ result = PyObject_CallFunctionObjArgs(function, mainloop, NULL);
+ Py_CLEAR(function);
+ if (!result) {
+ Py_CLEAR(mainloop);
+ return NULL;
+ }
+ Py_CLEAR(result);
+ }
+ return mainloop;
+}
+
+PyDoc_STRVAR(setup_with_g_main__doc__,
+"setup_with_g_main(conn: dbus.Connection)\n"
+"\n"
+"Deprecated.\n");
+static PyObject *
+setup_with_g_main (PyObject *always_null UNUSED, PyObject *args)
+{
+ DBusConnection *dbc;
+ PyObject *conn;
+ if (!PyArg_ParseTuple(args, "O:setup_with_g_main", &conn)) return NULL;
+
+ dbc = DBusPyConnection_BorrowDBusConnection (conn);
+ if (!dbc) return NULL;
+ dbus_py_glib_set_up_conn(dbc, NULL);
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(gthreads_init__doc__,
+"gthreads_init()");
+static PyObject *
+gthreads_init (PyObject *always_null UNUSED, PyObject *no_args UNUSED)
+{
+ dbus_threads_init_default();
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef module_functions[] = {
+ {"setup_with_g_main", setup_with_g_main, METH_VARARGS,
+ setup_with_g_main__doc__},
+ {"gthreads_init", gthreads_init, METH_NOARGS, gthreads_init__doc__},
+ {"DBusGMainLoop", (PyCFunction) (void (*)(void))DBusGMainLoop,
+ METH_VARARGS|METH_KEYWORDS, DBusGMainLoop__doc__},
+ {NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC
+PyInit__dbus_glib_bindings(void)
+{
+ PyObject *this_module;
+
+ static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "_dbus_glib_bindings", /* m_name */
+ module_doc, /* m_doc */
+ -1, /* m_size */
+ module_functions, /* m_methods */
+ NULL, /* m_reload */
+ NULL, /* m_traverse */
+ NULL, /* m_clear */
+ NULL /* m_free */
+ };
+
+ if (import_dbus_bindings("_dbus_glib_bindings") < 0)
+ return NULL;
+
+ if (!(this_module = PyModule_Create(&moduledef))) {
+ return NULL;
+ }
+ return this_module;
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/doc/API_CHANGES.txt b/doc/API_CHANGES.txt
new file mode 100644
index 0000000..ace4f5b
--- /dev/null
+++ b/doc/API_CHANGES.txt
@@ -0,0 +1,124 @@
+===============================
+API changes in dbus-python 0.80
+===============================
+
+:Author: Simon McVittie
+:Contact: simon.mcvittie@collabora.co.uk
+:Organization: `Collabora Ltd`_
+:Date: 2006-11-23
+
+.. _Collabora Ltd: http://www.collabora.co.uk/
+
+Type changes
+============
+
+* The Byte constructor accepts either single-byte strings, or integers in
+ the range 0 to 255.
+
+* There is no Variant type any more. Instead, the ``variant_level``
+ attribute on D-Bus types gives the number of variant wrappers in
+ which it is contained; this is to remove ambiguity. For instance, calling
+ this method::
+
+ @dbus.service.method('com.example', in_signature='v', out_signature='')
+ def Print(self, variant):
+ print repr(variant)
+
+ yields the following results::
+
+ # on the wire: Variant containing Int32
+ Int32(0, variant_level=1)
+ # on the wire: Variant containing Variant containing Int32
+ Int32(0, variant_level=2)
+
+ Once an object of a D-Bus type has been constructed, its
+ ``variant_level`` cannot be altered.
+
+* The D-Bus integer types (dbus.Int32, etc.) are properly range-checked.
+
+* The Array constructor takes arguments (iterable[, signature])
+ rather than (iterable[, type][, signature]); ditto for Dict.
+
+Calling conventions
+===================
+
+* In method parameters, method returns from proxy methods, etc.,
+ integers arrive as instances of dbus.Int32 etc., bytes arrive as
+ Byte, and so on, rather than everything being converted to an
+ appropriate built-in Python type. This means you can tell exactly
+ what arguments went over the bus, and their types.
+
+* Proxy methods with multiple return values return a tuple rather than
+ a list.
+
+* Calling a proxy method with reply ignored, or with async
+ handlers, returns None
+
+``dbus_bindings``
+=================
+
+* ConnectionError no longer exists (it was never raised)
+
+* ``dbus_bindings`` is now called ``_dbus_bindings``, and is considerably
+ different internally:
+
+ * connections are private at the libdbus level: shared connections
+ are only shared among Python code
+
+ * The MessageIter stuff is now done in C: there's a much simpler
+ Python API, ``Message.append(...)`` where positional arguments are
+ the things to be appended, and the keyword argument ``signature``
+ controls how objects are interpreted
+
+ * The signature-guessing algorithm used if there is no proper
+ signature is exposed as a static method,
+ ``Message.guess_signature(*args)``
+
+ * Bus is a subclass of Connection rather than being a wrapper object
+ which has-a Connection
+
+ * The timeouts in _send_with_reply and in _send_with_reply_and_block
+ are in (possibly fractional) seconds, as is conventional in Python
+
+ * The specialized Message subclasses have names ending with Message
+
+* There is a small amount of compatibility glue in a new
+ ``dbus_bindings`` module (also ``dbus.dbus_bindings``)
+ which should enable most current code to work - this is deprecated,
+ and will disappear in a future version of dbus-python
+
+Main loops
+==========
+
+Main loop handling is different - instead of the
+``use_default_mainloop`` keyword argument to Bus and subclasses, there's now
+``mainloop`` which takes an instance of dbus.mainloop.NativeMainLoop.
+
+Alternatively, you can set a default main loop by calling
+``dbus.set_default_main_loop()`` and passing it a NativeMainLoop, or
+by passing ``set_as_default=True`` to the factory function
+from which you obtained the native main loop.
+
+The plan is that in a future version of dbus-python there will be an
+abstract base class dbus.mainloop.MainLoop (or something); when it's added,
+instances of its subclasses will be accepted wherever a NativeMainLoop
+instance is now. This will let you wrap main loops using a Python API.
+This will be used to implement SimpleMainLoop (a pure-Python main loop
+which can only do D-Bus) and a Twisted main-loop wrapper.
+
+The only working mainloop implementation is (still) GLib; you can get
+a NativeMainLoop instance by::
+
+ from dbus.mainloop.glib import DBusGMainLoop
+ my_native_main_loop = DBusGMainLoop(set_as_default=True)
+
+The above is how the highly magical ``dbus.glib`` module is now implemented.
+At some point ``dbus.glib`` will be deprecated, since it's non-obvious,
+and pychecker will usually complain if you use it correctly!
+
+At the moment the GLib main loop always uses the default main context;
+python-gobject will probably need to add some extra API before we can
+allow other main-contexts to be used.
+
+..
+ vim:set sw=2 sts=2 et ft=rst tw=72:
diff --git a/doc/PY3PORT.txt b/doc/PY3PORT.txt
new file mode 100644
index 0000000..e159849
--- /dev/null
+++ b/doc/PY3PORT.txt
@@ -0,0 +1,222 @@
+===================
+Porting to Python 3
+===================
+
+This is an experimental port to Python 3.x where x >= 2. There are lots of
+great sources for porting C extensions to Python 3, including:
+
+ * http://python3porting.com/toc.html
+ * http://docs.python.org/howto/cporting.html
+ * http://docs.python.org/py3k/c-api/index.html
+
+I also consulted an early take on this port by John Palmieri and David Malcolm
+in the context of Fedora:
+
+ * https://bugs.freedesktop.org/show_bug.cgi?id=26420
+
+although I have made some different choices. The patches in that tracker
+issue also don't cover porting the Python bits (e.g. the test suite), nor the
+pygtk -> pygi porting, both which I've also attempted to do in this branch.
+
+This document outlines my notes and strategies for doing this port. Please
+feel free to contact me with any bugs, issues, disagreements, suggestions,
+kudos, and curses.
+
+Barry Warsaw
+barry@python.org
+2011-11-11
+
+
+User visible changes
+====================
+
+You've got some dbus-python code that works great in Python 2. This branch
+should generally allow your existing Python 2 code to continue to work
+unchanged. There are a few changes you'll notice in Python 2 though:
+
+ - The minimum supported Python 2 version is 2.7.
+ - All object reprs are unicodes. This change was made because it greatly
+ simplifies the implementation and cross-compatibility with Python 3.
+ - Some exception strings have changed.
+ - `MethodCallMessage` and `SignalMessage` objects have better reprs now.
+
+What do you need to do to port that to Python 3? Here are the user visible
+changes you should be aware of, relative to Python 2. Python 3.2 is the
+minimal required version:
+
+ - `ByteArray` objects must be initialized with bytes objects, not unicodes.
+ Use `b''` literals in the constructor. This also works in Python 2, where
+ bytes objects are aliases for 8-bit strings.
+ - `Byte` objects must be initialized with either a length-1 bytes object
+ (again, use `b''` literals to be compatible with either Python 2 or 3)
+ or an integer.
+ - byte signatures (i.e. `y` type codes) must be passed either a length-1
+ bytes object or an integer. unicodes (str in Python 3) are not allowed.
+ - `ByteArray` is now a subclass of `bytes`, where in Python 2 it is a
+ subclass of `str`.
+ - `dbus.UTF8String` is gone, use `dbus.String`. Also `utf8_string` arguments
+ are no longer allowed.
+ - All longs are now ints, since Python 3 has only a single int type. This
+ also means that the class hierarchy for the dbus numeric types has changed
+ (all derive from int in Python 3).
+
+
+Bytes vs. Strings
+=================
+
+All strings in dbus are defined as UTF-8:
+
+http://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-signatures
+
+However, the dbus C API accepts `char*` which must be UTF-8 strings NUL
+terminated and no other NUL bytes.
+
+This page describes the mapping between Python types and dbus types:
+
+ http://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html#basic-types
+
+Notice that it maps dbus `string` (`'s'`) to `dbus.String` (unicode) or
+`dbus.UTF8String` (str). Also notice that there is no direct dbus equivalent
+of Python's bytes type (although dbus does have byte arrays), so I am mapping
+dbus strings to unicodes in all cases, and getting rid of `dbus.UTF8String` in
+Python 3. I've also added a `dbus._BytesBase` type which is unused in Python
+2, but which forms the base class for `dbus.ByteArray` in Python 3. This is
+an implementation detail and not part of the public API.
+
+In Python 3, object paths (`'o'` or `dbus.ObjectPath`), signatures (`'g'` or
+`dbus.Signature`), bus names, interfaces, and methods are all strings. A
+previous aborted effort was made to use bytes for these, which at first blush
+may makes some sense, but on deeper consideration does not. This approach
+also tended to impose too many changes on user code, and caused lots of
+difficult to track down problems.
+
+In Python 3, all such objects are subclasses of `str` (i.e. `unicode`).
+
+(As an example, dbus-python's callback dispatching pretty much assumes all
+these things are strings. When they are bytes, the fact that `'foo' != b'foo'`
+causes dispatch matching to fail in difficult to debug ways. Even bus names
+are not immune, since they do things like `bus_name[:1] == ':'` which fails in
+multiple ways when `bus_name` is a bytes. For sanity purposes, these are all
+unicode strings now, and we just eat the complexity at the C level.)
+
+I am using `#include <bytesobject.h>`, which exposes the PyBytes API to Python
+2.6 and 2.7, and I have converted all internal PyString calls to PyBytes
+calls. Where this is inappropriate, we'll use PyUnicode calls explicitly.
+E.g. all repr() implementations now return unicodes. Most of these changes
+shouldn't be noticed, even in existing Python 2 code.
+
+Generally, I've left the descriptions and docstrings saying "str" instead of
+"unicode" since there's no distinction in Python 3.
+
+APIs which previously returned PyStrings will usually return PyUnicodes, not
+PyBytes.
+
+
+Ints vs. Longs
+==============
+
+Python 3 only has PyLong types; PyInts are gone. For that reason, I've
+switched all PyInt calls to use PyLong in both Python 2 and Python 3. Python
+3.0 had a nice `<intobject.h>` header that aliased PyInt to PyLong, but that's
+gone as of Python 3.1, and the minimal required Python 3 version is 3.2.
+
+In the above page mapping basic types, you'll notice that the Python int type
+is mapped to 32-bit signed integers ('i') and the Python long type is mapped
+to 64-bit signed integers ('x'). Python 3 doesn't have this distinction, so
+ints map to 'i' even though ints can be larger in Python 3. Use the
+dbus-specific integer types if you must have more exact mappings.
+
+APIs which accepted ints in Python 2 will still do so, but they'll also now
+accept longs. These APIs obviously only accept longs in Python 3.
+
+Long literals in Python code are an interesting thing to have to port. Don't
+use them if you want your code to work in both Python versions.
+
+`dbus._IntBase` is removed in Python 3, you only have `dbus._LongBase`, which
+inherits from a Python 3 int (i.e. a PyLong). Again, this is an
+implementation detail that users should never care about.
+
+
+Macros
+======
+
+In types-internal.h, I define `PY3K` when `PY_MAJOR_VERSION` >= 3, so you'll
+see ifdefs on the former symbol within the C code.
+
+Python 3 really could use a PY_REFCNT() wrapper for ob_refcnt access.
+
+
+PyCapsule vs. PyCObject
+=======================
+
+`_dbus_bindings._C_API` is an attribute exposed to Python in the module. In
+Python 2, this is a PyCObject, but these do not exist in Python >= 3.2, so it
+is replaced with a PyCapsules for Python 3. However, since PyCapsules were
+only introduced in Python 2.7, and I want to support Python 2.6, PyCObjects
+are still used when this module is compiled for Python 2.
+
+
+Python level compatibility
+==========================
+
+`from dbus import _is_py3` gives you a flag to check if you must do something
+different in Python 3. In general I use this flag to support both versions in
+one set of sources, which seems better than trying to use 2to3. It's not part
+of the dbus-python public API, so you must not use it in third-party projects.
+
+
+Miscellaneous
+=============
+
+The PyDoc_STRVAR() documentation is probably out of date. Once the API
+choices have been green-lighted upstream, I'll make a pass through the code to
+update them. It might be tricky based on any differences between Python 2 and
+Python 3.
+
+There were a few places where I noticed what might be considered bugs,
+unchecked exception conditions, or possible reference count leaks. In these
+cases, I've just fixed what I can and hopefully haven't made the situation
+worse.
+
+`dbus_py_variant_level_get()` did not check possible error conditions, nor did
+their callers. When `dbus_py_variant_level_get()` encounters an error, it now
+returns -1, and callers check this.
+
+As much as possible, I've refrained from general code cleanups (e.g. 80
+columns), unless it just bugged me too much or I touched the code for reasons
+related to the port. I've also tried to stick to existing C code style,
+e.g. through the use of pervasive `Py_CLEAR()` calls, comparison against NULL
+usually with `!foo`, and such. As Bart Simpson might write on his classroom
+blackboard::
+
+ This is not a rewrite
+ This is not a rewrite
+ This is not a rewrite
+ This is not a rewrite
+ ...
+
+and so on. Well, mostly ;).
+
+I think I fixed a reference leak in `DBusPyServer_set_auth_mechanisms()`.
+`PySequence_Fast()` returns a new reference, which wasn't getting decref'd in
+any return path.
+
+ - Instantiation of metaclasses uses different, incompatible syntax in Python
+ 2 and 3. You have to use direct calling of the metaclass to work across
+ versions, i.e. `Interface = InterfaceType('Interface', (object,), {})`
+ - `iteritems()` and friends are gone. I dropped the "iter" prefixes.
+ - `xrange() is gone. I changed them to use `range()`.
+ - `isSequenceType()` is gone in Python 3, so I use a different idiom there.
+ - `__next__()` vs. `next()`
+ - `PyUnicode_FromFormat()` `%V` flag is a clever hack!
+ - `PyArg_Parse()`: No 'y' code in Python 2; in Python 3, no equivalent of 'z'
+ for bytes objects.
+
+
+Open issues
+===========
+
+Here are a few things that still need to be done, or for which there may be
+open questions::
+
+ - Update all C extension docstrings for accuracy.
diff --git a/doc/_static/.gitignore b/doc/_static/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/doc/_static/.gitignore
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644
index 0000000..f094a26
--- /dev/null
+++ b/doc/conf.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file does only contain a selection of the most common options. For a
+# full list see the documentation:
+# http://www.sphinx-doc.org/en/stable/config
+
+# -- Path setup --------------------------------------------------------------
+
+import os
+import sys
+
+sys.path.insert(0,
+ os.path.join(
+ os.environ.get('abs_top_builddir', os.path.abspath('..')),
+ '.libs',
+ ),
+)
+sys.path.insert(0, os.environ.get('abs_top_srcdir', os.path.abspath('..')))
+
+import _dbus_bindings
+
+# -- Project information -----------------------------------------------------
+
+project = u'dbus-python'
+copyright = u'2003-2018, D-Bus contributors'
+author = u'D-Bus contributors'
+
+# The short X.Y version
+version = _dbus_bindings.__version__
+# The full version, including alpha/beta/rc tags
+release = version
+
+
+# -- General configuration ---------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.coverage',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = ['.rst', '.txt']
+
+# The master toctree document.
+master_doc = 'index'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path .
+exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store']
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself. Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+# html_sidebars = {}
+
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'dbus-python'
+
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'dbus-python.tex', u'dbus-python Documentation',
+ u'D-Bus contributors', 'manual'),
+]
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ (master_doc, 'dbus-python', u'dbus-python Documentation',
+ [author], 3)
+]
+
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'dbus-python', u'dbus-python Documentation',
+ author, 'dbus-python',
+ 'Python bindings for the reference implementation of D-Bus.',
+ 'Miscellaneous'),
+]
+
+
+# -- Extension configuration -------------------------------------------------
diff --git a/doc/dbus.bus.rst b/doc/dbus.bus.rst
new file mode 100644
index 0000000..957f66a
--- /dev/null
+++ b/doc/dbus.bus.rst
@@ -0,0 +1,8 @@
+dbus.bus module
+===============
+
+.. automodule:: dbus.bus
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.connection.rst b/doc/dbus.connection.rst
new file mode 100644
index 0000000..97798c3
--- /dev/null
+++ b/doc/dbus.connection.rst
@@ -0,0 +1,8 @@
+dbus.connection module
+======================
+
+.. automodule:: dbus.connection
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.decorators.rst b/doc/dbus.decorators.rst
new file mode 100644
index 0000000..6e10c64
--- /dev/null
+++ b/doc/dbus.decorators.rst
@@ -0,0 +1,8 @@
+dbus.decorators module
+----------------------
+
+.. automodule:: dbus.decorators
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.exceptions.rst b/doc/dbus.exceptions.rst
new file mode 100644
index 0000000..a609fdd
--- /dev/null
+++ b/doc/dbus.exceptions.rst
@@ -0,0 +1,8 @@
+dbus.exceptions module
+----------------------
+
+.. automodule:: dbus.exceptions
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.gi_service.rst b/doc/dbus.gi_service.rst
new file mode 100644
index 0000000..68eb11e
--- /dev/null
+++ b/doc/dbus.gi_service.rst
@@ -0,0 +1,8 @@
+dbus.gi\_service module
+-----------------------
+
+.. automodule:: dbus.gi_service
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.glib.rst b/doc/dbus.glib.rst
new file mode 100644
index 0000000..2fac689
--- /dev/null
+++ b/doc/dbus.glib.rst
@@ -0,0 +1,8 @@
+dbus.glib module
+----------------
+
+.. automodule:: dbus.glib
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.gobject_service.rst b/doc/dbus.gobject_service.rst
new file mode 100644
index 0000000..b1ff1be
--- /dev/null
+++ b/doc/dbus.gobject_service.rst
@@ -0,0 +1,36 @@
+.. This is not done via automodule because it cannot be imported in
+.. Python 3.
+
+dbus.gobject\_service module
+----------------------------
+
+.. py:module:: gobject_service
+
+This module is only available when using Python 2, and is deprecated.
+
+.. py:class:: ExportedGObjectType(cls, name, bases, dct)
+
+ A metaclass which inherits from both GObjectMeta and
+ `dbus.service.InterfaceType`. Used as the metaclass for
+ `ExportedGObject`.
+
+.. py:class:: ExportedGObject(self, conn=None, object_path=None, **kwargs)
+
+ A GObject which is exported on the D-Bus.
+
+ Because GObject and `dbus.service.Object` both have custom metaclasses,
+ the naive approach using simple multiple inheritance won't work. This
+ class has `ExportedGObjectType` as its metaclass, which is sufficient
+ to make it work correctly.
+
+ :param dbus.connection.Connection conn:
+ The D-Bus connection or bus
+ :param str object_path:
+ The object path at which to register this object.
+ :keyword dbus.service.BusName bus_name:
+ A bus name to be held on behalf of this object, or None.
+ :keyword dict gobject_properties:
+ GObject properties to be set on the constructed object.
+
+ Any unrecognised keyword arguments will also be interpreted
+ as GObject properties.
diff --git a/doc/dbus.lowlevel.rst b/doc/dbus.lowlevel.rst
new file mode 100644
index 0000000..f8060db
--- /dev/null
+++ b/doc/dbus.lowlevel.rst
@@ -0,0 +1,8 @@
+dbus.lowlevel module
+--------------------
+
+.. automodule:: dbus.lowlevel
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.mainloop.rst b/doc/dbus.mainloop.rst
new file mode 100644
index 0000000..6c3805c
--- /dev/null
+++ b/doc/dbus.mainloop.rst
@@ -0,0 +1,20 @@
+dbus.mainloop package
+=====================
+
+Module contents
+---------------
+
+.. automodule:: dbus.mainloop
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
+
+dbus.mainloop.glib module
+-------------------------
+
+.. automodule:: dbus.mainloop.glib
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.proxies.rst b/doc/dbus.proxies.rst
new file mode 100644
index 0000000..73df177
--- /dev/null
+++ b/doc/dbus.proxies.rst
@@ -0,0 +1,8 @@
+dbus.proxies module
+-------------------
+
+.. automodule:: dbus.proxies
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.rst b/doc/dbus.rst
new file mode 100644
index 0000000..2b2ab96
--- /dev/null
+++ b/doc/dbus.rst
@@ -0,0 +1,35 @@
+dbus package API reference
+==========================
+
+Submodules
+----------
+
+.. toctree::
+
+ dbus.bus
+ dbus.connection
+ dbus.decorators
+ dbus.exceptions
+ dbus.gi_service
+ dbus.lowlevel
+ dbus.mainloop
+ dbus.proxies
+ dbus.server
+ dbus.service
+ dbus.types
+
+Deprecated submodules
+---------------------
+
+.. toctree::
+
+ dbus.glib
+ dbus.gobject_service
+
+Module contents
+---------------
+
+.. automodule:: dbus
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.server.rst b/doc/dbus.server.rst
new file mode 100644
index 0000000..ddcaaf8
--- /dev/null
+++ b/doc/dbus.server.rst
@@ -0,0 +1,8 @@
+dbus.server module
+------------------
+
+.. automodule:: dbus.server
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.service.rst b/doc/dbus.service.rst
new file mode 100644
index 0000000..ac4c223
--- /dev/null
+++ b/doc/dbus.service.rst
@@ -0,0 +1,8 @@
+dbus.service module
+-------------------
+
+.. automodule:: dbus.service
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/dbus.types.rst b/doc/dbus.types.rst
new file mode 100644
index 0000000..58c1d5c
--- /dev/null
+++ b/doc/dbus.types.rst
@@ -0,0 +1,8 @@
+dbus.types module
+-----------------
+
+.. automodule:: dbus.types
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 0000000..19f6b66
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,59 @@
+=======================================
+dbus-python_: Python bindings for D-Bus
+=======================================
+
+.. _dbus-python: http://www.freedesktop.org/wiki/Software/DBusBindings#python
+
+dbus-python is a Python binding for ``dbus``, the reference implementation
+of the D-Bus protocol.
+
+Problems and alternatives
+=========================
+
+dbus-python might not be the best D-Bus binding for you to use. dbus-python
+does not follow the principle of "In the face of ambiguity, refuse the
+temptation to guess", and can't be changed to not do so without seriously
+breaking compatibility.
+
+In addition, it uses libdbus (which has known problems with multi-threaded
+use) and attempts to be main-loop-agnostic (which means you have to select
+a suitable main loop for your application).
+
+Alternative ways to get your Python code onto D-Bus include:
+
+* GDBus, part of the GIO module of `GLib`_, via GObject-Introspection and
+ `PyGI`_ (uses the GLib main loop and object model)
+
+* QtDBus, part of `Qt`_, via `PyQt`_ (uses the Qt main loop and object model)
+
+.. _GLib: http://developer.gnome.org/glib/
+.. _PyGI: https://live.gnome.org/PyGObject
+.. _Qt: https://qt.nokia.com/
+.. _PyQT: http://www.riverbankcomputing.co.uk/software/pyqt/intro
+
+Documentation
+=============
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+ tutorial
+ dbus
+ PY3PORT
+ news
+ API_CHANGES
+
+Contributing to dbus-python
+===========================
+
+Please see `the Gitlab project`_.
+
+.. _the Gitlab project: https://gitlab.freedesktop.org/dbus/dbus-python/blob/master/CONTRIBUTING.md
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/doc/news.rst b/doc/news.rst
new file mode 100644
index 0000000..249c431
--- /dev/null
+++ b/doc/news.rst
@@ -0,0 +1,5 @@
+===============
+Release history
+===============
+
+.. include:: ../NEWS
diff --git a/doc/redirects b/doc/redirects
new file mode 100644
index 0000000..d343ea9
--- /dev/null
+++ b/doc/redirects
@@ -0,0 +1,140 @@
+doc/API_CHANGES.html ../API_CHANGES.html
+doc/API_CHANGES.txt "See ../API_CHANGES.html"
+doc/HACKING.html https://gitlab.freedesktop.org/dbus/dbus-python/blob/master/CONTRIBUTING.md
+doc/HACKING.txt "See https://gitlab.freedesktop.org/dbus/dbus-python/blob/master/CONTRIBUTING.md"
+doc/PY3PORT.html ../PY3PORT.html
+doc/PY3PORT.txt "See ../PY3PORT.html"
+doc/tutorial.html ../tutorial.html
+doc/tutorial.txt "See ../tutorial.html"
+api/change-index.html ../news.html
+api/class-tree.html ../index.html
+api/dbus.Array-class.html ../dbus.html
+api/dbus.Boolean-class.html ../dbus.html
+api/dbus.bus.BusConnection-class.html ../dbus.bus.html
+api/dbus.bus-module.html ../dbus.bus.html
+api/dbus.bus.NameOwnerWatch-class.html ../dbus.bus.html
+api/dbus.bus-pysrc.html ../dbus.bus.html
+api/dbus.ByteArray-class.html ../dbus.html
+api/dbus.Byte-class.html ../dbus.html
+api/dbus._compat-module.html ../dbus.html
+api/dbus._compat-pysrc.html ../dbus.html
+api/dbus.connection.Connection-class.html ../dbus.connection.html
+api/dbus.connection-module.html ../dbus.connection.html
+api/dbus.connection-pysrc.html ../dbus.connection.html
+api/dbus.connection.SignalMatch-class.html ../dbus.connection.html
+api/dbus.dbus_bindings.ConnectionError-class.html ../dbus.html
+api/dbus.dbus_bindings-module.html ../dbus.html
+api/dbus.dbus_bindings-pysrc.html ../dbus.html
+api/dbus._dbus.Bus-class.html ../dbus.html
+api/dbus._dbus._DBusBindingsEmulation-class.html ../dbus.html
+api/dbus._dbus-module.html ../dbus.html
+api/dbus._dbus-pysrc.html ../dbus.html
+api/dbus._dbus.SessionBus-class.html ../dbus.html
+api/dbus._dbus.StarterBus-class.html ../dbus.html
+api/dbus._dbus.SystemBus-class.html ../dbus.html
+api/dbus.decorators-module.html ../dbus.decorators.html
+api/dbus.decorators-pysrc.html ../dbus.decorators.html
+api/dbus.Dictionary-class.html ../dbus.html
+api/dbus.Double-class.html ../dbus.html
+api/dbus.exceptions.DBusException-class.html ../dbus.html
+api/dbus.exceptions.IntrospectionParserException-class.html ../dbus.html
+api/dbus.exceptions.MissingErrorHandlerException-class.html ../dbus.html
+api/dbus.exceptions.MissingReplyHandlerException-class.html ../dbus.html
+api/dbus.exceptions-module.html ../dbus.html
+api/dbus.exceptions.NameExistsException-class.html ../dbus.html
+api/dbus.exceptions-pysrc.html ../dbus.html
+api/dbus.exceptions.UnknownMethodException-class.html ../dbus.html
+api/dbus.exceptions.ValidationException-class.html ../dbus.html
+api/dbus._expat_introspect_parser-module.html ../dbus.html
+api/dbus._expat_introspect_parser._Parser-class.html ../dbus.html
+api/dbus._expat_introspect_parser-pysrc.html ../dbus.html
+api/dbus.gi_service.ExportedGObject-class.html ../dbus.gi_service.html
+api/dbus.gi_service.ExportedGObjectType-class.html ../dbus.gi_service.html
+api/dbus.gi_service-module.html ../dbus.gi_service.html
+api/dbus.gi_service-pysrc.html ../dbus.gi_service.html
+api/dbus.glib-module.html ../dbus.glib.html
+api/dbus.glib-pysrc.html ../dbus.glib.html
+api/dbus.gobject_service.ExportedGObject-class.html ../dbus.gobject_service.html
+api/dbus.gobject_service.ExportedGObjectType-class.html ../dbus.gobject_service.html
+api/dbus.gobject_service-module.html ../dbus.gobject_service.html
+api/dbus.gobject_service-pysrc.html ../dbus.gobject_service.html
+api/dbus.Int16-class.html ../dbus.html
+api/dbus.Int32-class.html ../dbus.html
+api/dbus.Int64-class.html ../dbus.html
+api/dbus.lowlevel.ErrorMessage-class.html ../dbus.lowlevel.html
+api/dbus.lowlevel.Message-class.html ../dbus.lowlevel.html
+api/dbus.lowlevel.MethodCallMessage-class.html ../dbus.lowlevel.html
+api/dbus.lowlevel.MethodReturnMessage-class.html ../dbus.lowlevel.html
+api/dbus.lowlevel-module.html ../dbus.lowlevel.html
+api/dbus.lowlevel.PendingCall-class.html ../dbus.lowlevel.html
+api/dbus.lowlevel-pysrc.html ../dbus.lowlevel.html
+api/dbus.lowlevel.SignalMessage-class.html ../dbus.lowlevel.html
+api/dbus.mainloop.glib-module.html ../dbus.mainloop.html
+api/dbus.mainloop.glib-pysrc.html ../dbus.mainloop.html
+api/dbus.mainloop-module.html ../dbus.mainloop.html
+api/dbus.mainloop.NativeMainLoop-class.html ../dbus.mainloop.html
+api/dbus.mainloop-pysrc.html ../dbus.mainloop.html
+api/dbus-module.html ../dbus.html
+api/dbus.ObjectPath-class.html ../dbus.html
+api/dbus.proxies._DeferredMethod-class.html ../dbus.proxies.html
+api/dbus.proxies.Interface-class.html ../dbus.proxies.html
+api/dbus.proxies-module.html ../dbus.proxies.html
+api/dbus.proxies._ProxyMethod-class.html ../dbus.proxies.html
+api/dbus.proxies.ProxyObject-class.html ../dbus.proxies.html
+api/dbus.proxies-pysrc.html ../dbus.proxies.html
+api/dbus-pysrc.html ../dbus.html
+api/dbus.server-module.html ../dbus.server.html
+api/dbus.server-pysrc.html ../dbus.server.html
+api/dbus.server.Server-class.html ../dbus.server.html
+api/dbus.service.BusName-class.html ../dbus.service.html
+api/dbus.service.FallbackObject-class.html ../dbus.service.html
+api/dbus.service.Interface-class.html ../dbus.service.html
+api/dbus.service.InterfaceType-class.html ../dbus.service.html
+api/dbus.service-module.html ../dbus.service.html
+api/dbus.service.Object-class.html ../dbus.service.html
+api/dbus.service-pysrc.html ../dbus.service.html
+api/dbus.service._VariantSignature-class.html ../dbus.service.html
+api/dbus.Signature-class.html ../dbus.html
+api/dbus.String-class.html ../dbus.html
+api/dbus.Struct-class.html ../dbus.html
+api/dbus.types-module.html ../dbus.html
+api/dbus.types-pysrc.html ../dbus.html
+api/dbus.types.UnixFd-class.html ../dbus.html
+api/dbus.UInt16-class.html ../dbus.html
+api/dbus.UInt32-class.html ../dbus.html
+api/dbus.UInt64-class.html ../dbus.html
+api/dbus.UTF8String-class.html ../dbus.html
+api/dbus._version-module.html ../dbus.html
+api/dbus._version-pysrc.html ../dbus.html
+api/deprecated-index.html ../dbus.html
+api/frames.html ../dbus.html
+api/help.html ../dbus.html
+api/identifier-dbus.html ../dbus.html
+api/index.html ../dbus.html
+api/module-tree.html ../dbus.html
+api/redirect.html ../dbus.html
+api/since-index.html ../news.html
+api/toc-dbus.bus-module.html ../dbus.bus.html
+api/toc-dbus._compat-module.html ../dbus.html
+api/toc-dbus.connection-module.html ../dbus.connection.html
+api/toc-dbus.dbus_bindings-module.html ../dbus.html
+api/toc-dbus._dbus-module.html ../dbus.html
+api/toc-dbus.decorators-module.html ../dbus.decorators.html
+api/toc-dbus.exceptions-module.html ../dbus.exceptions.html
+api/toc-dbus._expat_introspect_parser-module.html ../dbus.html
+api/toc-dbus.gi_service-module.html ../dbus.gi_service.html
+api/toc-dbus.glib-module.html ../dbus.glib.html
+api/toc-dbus.gobject_service-module.html ../dbus.gobject_service.html
+api/toc-dbus.lowlevel-module.html ../dbus.lowlevel.html
+api/toc-dbus.mainloop.glib-module.html ../dbus.mainloop.html
+api/toc-dbus.mainloop-module.html ../dbus.mainloop.html
+api/toc-dbus-module.html ../dbus.html
+api/toc-dbus.proxies-module.html ../dbus.proxies.html
+api/toc-dbus.server-module.html ../dbus.server.html
+api/toc-dbus.service-module.html ../dbus.service.html
+api/toc-dbus.types-module.html ../dbus.types.html
+api/toc-dbus._version-module.html ../dbus.html
+api/toc-everything.html ../dbus.html
+api/toc.html ../dbus.html
+HACKING.html https://gitlab.freedesktop.org/dbus/dbus-python/blob/master/CONTRIBUTING.md
+HACKING.txt "See https://gitlab.freedesktop.org/dbus/dbus-python/blob/master/CONTRIBUTING.md"
diff --git a/doc/redirects.py b/doc/redirects.py
new file mode 100644
index 0000000..8d41a11
--- /dev/null
+++ b/doc/redirects.py
@@ -0,0 +1,46 @@
+#!/usr/bin/python
+
+# Copyright 2018-2019 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+
+import os
+
+SRCDIR = os.environ.get('DBUS_TOP_SRCDIR', '.')
+
+if __name__ == '__main__':
+ with open(os.path.join(SRCDIR, 'doc', 'redirects'), 'r') as reader:
+ for line in reader:
+ line = line.strip()
+
+ if not line:
+ continue
+
+ if line.startswith('#'):
+ continue
+
+ page, dest = line.split(None, 1)
+
+ try:
+ os.makedirs(os.path.join('doc', '_build', os.path.dirname(page)))
+ except OSError:
+ pass
+
+ assert not os.path.exists(os.path.join('doc', '_build', page))
+
+ if dest.startswith('"'):
+ assert page.endswith('.txt')
+ text = dest.strip('"')
+
+ with open(os.path.join('doc', '_build', page), 'w') as writer:
+ writer.write(text)
+ writer.write('\n')
+ else:
+ assert page.endswith('.html')
+
+ with open(os.path.join('doc', '_build', page), 'w') as writer:
+ writer.write(
+ '<meta http-equiv="refresh" content="0; url={}" />\n'.format(
+ dest))
+ writer.write(
+ 'See <a href="{}">{}</a>\n'.format(
+ dest, dest))
diff --git a/doc/tutorial.txt b/doc/tutorial.txt
new file mode 100644
index 0000000..cc7a99e
--- /dev/null
+++ b/doc/tutorial.txt
@@ -0,0 +1,716 @@
+====================
+dbus-python tutorial
+====================
+
+:Author: Simon McVittie, `Collabora Ltd.`_
+:Date: 2006-06-14
+
+.. _`Collabora Ltd.`: http://www.collabora.co.uk/
+
+This tutorial requires Python 2.4 or up, and ``dbus-python`` 0.80rc4 or up.
+
+.. contents::
+
+.. --------------------------------------------------------------------
+
+.. _Bus object:
+.. _Bus objects:
+
+Connecting to the Bus
+=====================
+
+Applications that use D-Bus typically connect to a *bus daemon*, which
+forwards messages between the applications. To use D-Bus, you need to create a
+``Bus`` object representing the connection to the bus daemon.
+
+There are generally two bus daemons you may be interested in. Each user
+login session should have a *session bus*, which is local to that
+session. It's used to communicate between desktop applications. Connect
+to the session bus by creating a ``SessionBus`` object::
+
+ import dbus
+
+ session_bus = dbus.SessionBus()
+
+The *system bus* is global and usually started during boot; it's used to
+communicate with system services like udev_, NetworkManager_, and the
+`Hardware Abstraction Layer daemon (hald)`_. To connect to the system
+bus, create a ``SystemBus`` object::
+
+ import dbus
+
+ system_bus = dbus.SystemBus()
+
+Of course, you can connect to both in the same application.
+
+For special purposes, you might use a non-default Bus, or a connection
+which isn't a Bus at all, using some new API added in dbus-python 0.81.0.
+This is not described here, and will at some stage be the subject of a separate
+tutorial.
+
+.. _udev:
+ http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html
+.. _NetworkManager:
+ http://www.gnome.org/projects/NetworkManager/
+.. _Hardware Abstraction Layer daemon (hald):
+ http://www.freedesktop.org/wiki/Software/hal
+
+.. --------------------------------------------------------------------
+
+Making method calls
+===================
+
+D-Bus applications can export objects for other applications' use. To
+start working with an object in another application, you need to know:
+
+* The *bus name*. This identifies which application you want to
+ communicate with. You'll usually identify applications by a
+ *well-known name*, which is a dot-separated string starting with a
+ reversed domain name, such as ``org.freedesktop.NetworkManager``
+ or ``com.example.WordProcessor``.
+
+* The *object path*. Applications can export many objects - for
+ instance, example.com's word processor might provide an object
+ representing the word processor application itself and an object for
+ each document window opened, or it might also provide an object for
+ each paragraph within a document.
+
+ To identify which one you want to interact with, you use an object path,
+ a slash-separated string resembling a filename. For instance, example.com's
+ word processor might provide an object at ``/`` representing the word
+ processor itself, and objects at ``/documents/123`` and
+ ``/documents/345`` representing opened document windows.
+
+As you'd expect, one of the main things you can do with remote objects
+is to call their methods. As in Python, methods may have parameters,
+and they may return one or more values.
+
+.. _proxy object:
+
+Proxy objects
+-------------
+
+To interact with a remote object, you use a *proxy object*. This is a
+Python object which acts as a proxy or "stand-in" for the remote object -
+when you call a method on a proxy object, this causes dbus-python to make
+a method call on the remote object, passing back any return values from
+the remote object's method as the return values of the proxy method call.
+
+To obtain a proxy object, call the ``get_object`` method on the ``Bus``.
+For example, NetworkManager_ has the well-known name
+``org.freedesktop.NetworkManager`` and exports an object whose object
+path is ``/org/freedesktop/NetworkManager``, plus an object per network
+interface at object paths like
+``/org/freedesktop/NetworkManager/Devices/eth0``. You can get a proxy
+for the object representing eth0 like this::
+
+ import dbus
+ bus = dbus.SystemBus()
+ proxy = bus.get_object('org.freedesktop.NetworkManager',
+ '/org/freedesktop/NetworkManager/Devices/eth0')
+ # proxy is a dbus.proxies.ProxyObject
+
+Interfaces and methods
+----------------------
+
+D-Bus uses *interfaces* to provide a namespacing mechanism for methods.
+An interface is a group of related methods and signals (more on signals
+later), identified by a name which is a series of dot-separated components
+starting with a reversed domain name. For instance, each NetworkManager_
+object representing a network interface implements the interface
+``org.freedesktop.NetworkManager.Devices``, which has methods like
+``getProperties``.
+
+To call a method, call the method of the same name on the proxy object,
+passing in the interface name via the ``dbus_interface`` keyword argument::
+
+ import dbus
+ bus = dbus.SystemBus()
+ eth0 = bus.get_object('org.freedesktop.NetworkManager',
+ '/org/freedesktop/NetworkManager/Devices/eth0')
+ props = eth0.getProperties(dbus_interface='org.freedesktop.NetworkManager.Devices')
+ # props is a tuple of properties, the first of which is the object path
+
+.. _dbus.Interface:
+
+As a short cut, if you're going to be calling many methods with the same
+interface, you can construct a ``dbus.Interface`` object and call
+methods on that, without needing to specify the interface again::
+
+ import dbus
+ bus = dbus.SystemBus()
+ eth0 = bus.get_object('org.freedesktop.NetworkManager',
+ '/org/freedesktop/NetworkManager/Devices/eth0')
+ eth0_dev_iface = dbus.Interface(eth0,
+ dbus_interface='org.freedesktop.NetworkManager.Devices')
+ props = eth0_dev_iface.getProperties()
+ # props is the same as before
+
+See also
+~~~~~~~~
+
+See the example in ``examples/example-client.py``. Before running it,
+you'll need to run ``examples/example-service.py`` in the background or
+in another shell.
+
+Data types
+----------
+
+Unlike Python, D-Bus is statically typed - each method has a certain
+*signature* representing the types of its arguments, and will not accept
+arguments of other types.
+
+D-Bus has an introspection mechanism, which ``dbus-python`` tries to use
+to discover the correct argument types. If this succeeds, Python types
+are converted into the right D-Bus data types automatically, if possible;
+``TypeError`` is raised if the type is inappropriate.
+
+If the introspection mechanism fails (or the argument's type is
+variant - see below), you have to provide arguments of
+the correct type. ``dbus-python`` provides Python types corresponding to
+the D-Bus data types, and a few native Python types are also converted to
+D-Bus data types automatically. If you use a type which isn't among these,
+a ``TypeError`` will be raised telling you that ``dbus-python`` was
+unable to guess the D-Bus signature.
+
+Basic types
+~~~~~~~~~~~
+
+The following basic data types are supported.
+
++-----------------------+---------------------+-----------------------+
+|Python type |converted to |notes |
+| |D-Bus type | |
++=======================+=====================+=======================+
+|D-Bus `proxy object`_ |object path |`(+)`_ |
++-----------------------+(signature 'o') | |
+|`dbus.Interface`_ | | |
++-----------------------+ | |
+|`dbus.service.Object`_ | | |
++-----------------------+---------------------+-----------------------+
+|``dbus.Boolean`` |Boolean |a subclass of ``int`` |
+| |(signature 'b') | |
++-----------------------+---------------------+ |
+|``dbus.Byte`` |byte (signature 'y') | |
++-----------------------+---------------------+ |
+|``dbus.Int16`` |16-bit signed | |
+| |integer ('n') | |
++-----------------------+---------------------+ |
+|``dbus.UInt16`` |16-bit unsigned | |
+| |integer ('q') | |
++-----------------------+---------------------+ |
+|``dbus.Int32`` |32-bit signed | |
+| |integer ('i') | |
++-----------------------+---------------------+-----------------------+
+|``dbus.UInt32`` |32-bit unsigned |a subclass of ``long`` |
+| |integer ('u') |(Python 2) |
++-----------------------+---------------------+ |
+|``dbus.Int64`` |64-bit signed |a subclass of ``int`` |
+| |integer ('x') |(Python 3) |
++-----------------------+---------------------+ |
+|``dbus.UInt64`` |64-bit unsigned | |
+| |integer ('t') | |
++-----------------------+---------------------+-----------------------+
+|``dbus.Double`` |double-precision |a subclass of ``float``|
+| |floating point ('d') | |
++-----------------------+---------------------+-----------------------+
+|``dbus.ObjectPath`` |object path ('o') |a subclass of ``str`` |
++-----------------------+---------------------+ |
+|``dbus.Signature`` |signature ('g') | |
++-----------------------+---------------------+-----------------------+
+|``dbus.String`` |string ('s') |a subclass of |
+| | |``unicode`` (Python 2) |
+| | | |
+| | |a subclass of ``str`` |
+| | |(Python 3) |
++-----------------------+---------------------+-----------------------+
+|``dbus.UTF8String`` |string ('s') |a subclass of ``str``, |
+| | |only in Python 2 |
++-----------------------+---------------------+-----------------------+
+|``bool`` |Boolean ('b') | |
++-----------------------+---------------------+-----------------------+
+|``int`` or subclass |32-bit signed | |
+| |integer ('i') | |
++-----------------------+---------------------+-----------------------+
+|``long`` or subclass |64-bit signed | Python 2 only |
+| |integer ('i') | |
++-----------------------+---------------------+-----------------------+
+|``float`` or subclass |double-precision | |
+| |floating point ('d') | |
++-----------------------+---------------------+-----------------------+
+|``bytes`` or subclass |string ('s') | must be valid UTF-8 |
++-----------------------+---------------------+-----------------------+
+|Python 2 ``unicode`` |string ('s') | |
++-----------------------+ | |
+|Python 3 ``str`` | | |
++-----------------------+---------------------+-----------------------+
+
+.. _(+):
+
+(+): D-Bus proxy objects, exported D-Bus service objects and anything
+else with the special attribute ``__dbus_object_path__``, which
+must be a string, are converted to their object-path. This might be
+useful if you're writing an object-oriented API using dbus-python.
+
+Basic type conversions
+~~~~~~~~~~~~~~~~~~~~~~
+
+If introspection succeeded, ``dbus-python`` will also accept:
+
+* for Boolean parameters, any object (converted as if via ``int(bool(...))``)
+* for byte parameters, a single-character string (converted as if via ``ord()``)
+* for byte and integer parameters, any integer (must be in the correct range)
+* for object-path and signature parameters, any ``str`` or ``unicode``
+ subclass (the value must follow the appropriate syntax)
+
+Container types
+~~~~~~~~~~~~~~~
+
+D-Bus supports four container types: array (a variable-length sequence of the
+same type), struct (a fixed-length sequence whose members may have
+different types), dictionary (a mapping from values of the same basic type to
+values of the same type), and variant (a container which may hold any
+D-Bus type, including another variant).
+
+Arrays are represented by Python lists, or by ``dbus.Array``, a subclass
+of ``list``. When sending an array, if an introspected signature is
+available, that will be used; otherwise, if the ``signature`` keyword
+parameter was passed to the ``Array`` constructor, that will be used to
+determine the contents' signature; otherwise, ``dbus-python`` will guess
+from the array's first item.
+
+The signature of an array is 'ax' where 'x' represents the signature of
+one item. For instance, you could also have 'as' (array of strings) or
+'a(ii)' (array of structs each containing two 32-bit integers).
+
+There's also a type ``dbus.ByteArray`` which is a subclass of ``bytes``,
+used as a more efficient representation of a D-Bus array of bytes
+(signature 'ay').
+
+Structs are represented by Python tuples, or by ``dbus.Struct``, a
+subclass of ``tuple``. When sending a struct, if an introspected signature is
+available, that will be used; otherwise, if the ``signature`` keyword
+parameter was passed to the ``Array`` constructor, that will be used to
+determine the contents' signature; otherwise, ``dbus-python`` will guess
+from the array's first item.
+
+The signature of a struct consists of the signatures of the contents,
+in parentheses - for instance '(is)' is the signature of a struct
+containing a 32-bit integer and a string.
+
+Dictionaries are represented by Python dictionaries, or by
+``dbus.Dictionary``, a subclass of ``dict``. When sending a dictionary,
+if an introspected signature is available, that will be used; otherwise,
+if the ``signature`` keyword parameter was passed to the ``Dictionary``
+constructor, that will be used to determine the contents' key and value
+signatures; otherwise, ``dbus-python`` will guess from an arbitrary item
+of the ``dict``.
+
+The signature of a dictionary is 'a{xy}' where 'x' represents the
+signature of the keys (which may not be a container type) and 'y'
+represents the signature of the values. For instance,
+'a{s(ii)}' is a dictionary where the keys are strings and the values are
+structs containing two 32-bit integers.
+
+Variants are represented by setting the ``variant_level`` keyword
+argument in the constructor of any D-Bus data type to a value greater
+than 0 (``variant_level`` 1 means a variant containing some other data type,
+``variant_level`` 2 means a variant containing a variant containing some
+other data type, and so on). If a non-variant is passed as an argument
+but introspection indicates that a variant is expected, it'll
+automatically be wrapped in a variant.
+
+The signature of a variant is 'v'.
+
+.. _byte_arrays and utf8_strings:
+
+Return values, and the ``byte_arrays`` and ``utf8_strings`` options
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If a D-Bus method returns no value, the Python proxy method will return
+``None``.
+
+If a D-Bus method returns one value, the Python proxy method will return
+that value as one of the ``dbus.`` types - by default, strings are
+returned as ``dbus.String`` (a subclass of Unicode) and byte arrays are
+returned as a ``dbus.Array`` of ``dbus.Byte``.
+
+If a D-Bus method returns multiple values, the Python proxy method
+will return a tuple containing those values.
+
+If you want strings returned as ``dbus.UTF8String`` (a subclass of
+``bytes``) pass the keyword parameter ``utf8_strings=True`` to the proxy
+method. This mode is only available in Python 2.
+
+If you want byte arrays returned as ``dbus.ByteArray`` (also a
+subclass of ``bytes`` - in practice, this is often what you want) pass
+the keyword parameter ``byte_arrays=True`` to the proxy method.
+
+.. --------------------------------------------------------------------
+
+Making asynchronous method calls
+================================
+
+Asynchronous (non-blocking) method calls allow multiple method calls to
+be in progress simultaneously, and allow your application to do other
+work while it's waiting for the results. To make asynchronous calls,
+you first need an event loop or "main loop".
+
+Setting up an event loop
+------------------------
+
+Currently, the only main loop supported by ``dbus-python`` is GLib.
+
+``dbus-python`` has a global default main loop, which is the easiest way
+to use this functionality. To arrange for the GLib main loop to be the
+default, use::
+
+ from dbus.mainloop.glib import DBusGMainLoop
+
+ DBusGMainLoop(set_as_default=True)
+
+You must do this before `connecting to the bus`_.
+
+Actually starting the main loop is as usual for ``pygi``::
+
+ from gi.repository import GLib
+
+ loop = GLib.MainLoop()
+ loop.run()
+
+While ``loop.run()`` is executing, GLib will run your callbacks when
+appropriate. To stop, call ``loop.quit()``.
+
+You can also set a main loop on a per-connection basis, by passing a
+main loop to the Bus constructor::
+
+ import dbus
+ from dbus.mainloop.glib import DBusGMainLoop
+
+ dbus_loop = DBusGMainLoop()
+
+ bus = dbus.SessionBus(mainloop=dbus_loop)
+
+This isn't very useful until we support more than one main loop, though.
+
+Backwards compatibility: ``dbus.glib``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In versions of ``dbus-python`` prior to 0.80, the way to set GLib as the
+default main loop was::
+
+ import dbus.glib
+
+Executing that import statement would automatically load the GLib main
+loop and make this the default. This is now deprecated, since it's
+highly non-obvious, but may be useful if you want to write or understand
+backwards-compatible code.
+
+The Qt main loop
+~~~~~~~~~~~~~~~~
+
+PyQt v4.2 and later includes support for integrating dbus-python with
+the Qt event loop. To connect D-Bus to this main loop, call
+``dbus.mainloop.qt.DBusQtMainLoop`` instead of
+``dbus.mainloop.glib.DBusGMainLoop``. Otherwise the Qt loop is used in
+exactly the same way as the GLib loop.
+
+Making asynchronous calls
+-------------------------
+
+To make a call asynchronous, pass two callables as keyword arguments
+``reply_handler`` and ``error_handler`` to the proxy method. The proxy
+method will immediately return `None`. At some later time, when the event
+loop is running, one of these will happen: either
+
+* the ``reply_handler`` will be called with the method's return values
+ as arguments; or
+
+* the ``error_handler`` will be called with one argument, an instance of
+ ``DBusException`` representing a remote exception.
+
+See also
+~~~~~~~~
+
+``examples/example-async-client.py`` makes asynchronous method calls to
+the service provided by ``examples/example-service.py`` which return
+either a value or an exception. As for ``examples/example-client.py``,
+you need to run ``examples/example-service.py`` in the background or
+in another shell first.
+
+.. --------------------------------------------------------------------
+
+Receiving signals
+=================
+
+To receive signals, the Bus needs to be connected to an event loop - see
+section `Setting up an event loop`_. Signals will only be received while
+the event loop is running.
+
+Signal matching
+---------------
+
+To respond to signals, you can use the ``add_signal_receiver`` method on
+`Bus objects`_. This arranges for a callback to be called when a
+matching signal is received, and has the following arguments:
+
+* a callable (the ``handler_function``) which will be called by the event loop
+ when the signal is received - its parameters will be the arguments of
+ the signal
+
+* the signal name, ``signal_name``: here None (the default) matches all names
+
+* the D-Bus interface, ``dbus_interface``: again None is the default,
+ and matches all interfaces
+
+* a sender bus name (well-known or unique), ``bus_name``: None is again
+ the default, and matches all senders. Well-known names match signals
+ from whatever application is currently the primary owner of that
+ well-known name.
+
+* a sender object path, ``path``: once again None is the default and
+ matches all object paths
+
+``add_signal_receiver`` also has keyword arguments ``utf8_strings`` and
+``byte_arrays`` which influence the types used when calling the
+handler function, in the same way as the `byte_arrays and utf8_strings`_
+options on proxy methods.
+
+``add_signal_receiver`` returns a ``SignalMatch`` object. Its only
+useful public API at the moment is a ``remove`` method with no
+arguments, which removes the signal match from the connection.
+
+Getting more information from a signal
+--------------------------------------
+
+You can also arrange for more information to be passed to the handler
+function. If you pass the keyword arguments ``sender_keyword``,
+``destination_keyword``, ``interface_keyword``, ``member_keyword`` or
+``path_keyword`` to the ``connect_to_signal`` method, the appropriate
+part of the signal message will be passed to the handler function as a
+keyword argument: for instance if you use ::
+
+ def handler(sender=None):
+ print "got signal from %r" % sender
+
+ iface.connect_to_signal("Hello", handler, sender_keyword='sender')
+
+and a signal ``Hello`` with no arguments is received from
+``com.example.Foo``, the ``handler`` function will be called with
+``sender='com.example.Foo'``.
+
+String argument matching
+------------------------
+
+If there are keyword parameters for the form ``arg``\ *n* where n is a
+small non-negative number, their values must be Unicode strings (Python
+2 ``unicode`` or Python 3 ``str``) or UTF-8 bytestrings. The handler
+will only be called if that argument of the signal (numbered from zero)
+is a D-Bus string (in particular, not an object-path or a signature)
+with that value.
+
+.. *this comment is to stop the above breaking vim syntax highlighting*
+
+Receiving signals from a proxy object
+-------------------------------------
+
+`Proxy objects`_ have a special method ``connect_to_signal`` which
+arranges for a callback to be called when a signal is received
+from the corresponding remote object. The parameters are:
+
+* the name of the signal
+
+* a callable (the handler function) which will be called by the event loop
+ when the signal is received - its parameters will be the arguments of
+ the signal
+
+* the handler function, a callable: the same as for ``add_signal_receiver``
+
+* the keyword argument ``dbus_interface`` qualifies the name with its
+ interface
+
+`dbus.Interface` objects have a similar ``connect_to_signal`` method,
+but in this case you don't need the ``dbus_interface`` keyword argument
+since the interface to use is already known.
+
+The same extra keyword arguments as for ``add_signal_receiver`` are also
+available, and just like ``add_signal_receiver``, it returns a
+SignalMatch.
+
+You shouldn't use proxy objects just to listen to signals, since they
+might activate the relevant service when created, but if you already have a
+proxy object in order to call methods, it's often convenient to use it to add
+signal matches too.
+
+See also
+--------
+
+``examples/signal-recipient.py`` receives signals - it demonstrates
+general signal matching as well as ``connect_to_signal``. Before running it,
+you'll need to run ``examples/signal-emitter.py`` in the background or
+in another shell.
+
+.. _BusName:
+
+.. --------------------------------------------------------------------
+
+Claiming a bus name
+===================
+
+FIXME describe `BusName`_ - perhaps fix its API first?
+
+The unique-instance idiom
+-------------------------
+
+FIXME provide exemplary code, put it in examples
+
+.. _exported object:
+.. _exported objects:
+
+.. --------------------------------------------------------------------
+
+Exporting objects
+=================
+
+Objects made available to other applications over D-Bus are said to be
+*exported*. All subclasses of ``dbus.service.Object`` are automatically
+exported.
+
+To export objects, the Bus needs to be connected to an event loop - see
+section `Setting up an event loop`_. Exported methods will only be called,
+and queued signals will only be sent, while the event loop is running.
+
+.. _dbus.service.Object:
+
+Inheriting from ``dbus.service.Object``
+---------------------------------------
+
+To export an object onto the Bus, just subclass
+``dbus.service.Object``. Object expects either a `BusName`_ or a `Bus
+object`_, and an object-path, to be passed to its constructor: arrange
+for this information to be available. For example::
+
+ class Example(dbus.service.Object):
+ def __init__(self, object_path):
+ dbus.service.Object.__init__(self, dbus.SessionBus(), path)
+
+This object will automatically support introspection, but won't do
+anything particularly interesting. To fix that, you'll need to export some
+methods and signals too.
+
+FIXME also mention dbus.gobject.ExportedGObject once I've written it
+
+Exporting methods with ``dbus.service.method``
+----------------------------------------------
+
+To export a method, use the decorator ``dbus.service.method``. For
+example::
+
+ class Example(dbus.service.Object):
+ def __init__(self, object_path):
+ dbus.service.Object.__init__(self, dbus.SessionBus(), path)
+
+ @dbus.service.method(dbus_interface='com.example.Sample',
+ in_signature='v', out_signature='s')
+ def StringifyVariant(self, variant):
+ return str(variant)
+
+The ``in_signature`` and ``out_signature`` are D-Bus signature strings
+as described in `Data Types`_.
+
+As well as the keywords shown, you can pass ``utf8_strings`` and
+``byte_arrays`` keyword arguments, which influence the types which will
+be passed to the decorated method when it's called via D-Bus, in the
+same way that the `byte_arrays and utf8_strings`_ options affect the
+return value of a proxy method.
+
+You can find a simple example in ``examples/example-service.py``, which
+we used earlier to demonstrate ``examples/example-client.py``.
+
+Finding out the caller's bus name
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``method`` decorator accepts a ``sender_keyword`` keyword argument.
+If you set that to a string, the unique bus name of the sender will be
+passed to the decorated method as a keyword argument of that name::
+
+ class Example(dbus.service.Object):
+ def __init__(self, object_path):
+ dbus.service.Object.__init__(self, dbus.SessionBus(), path)
+
+ @dbus.service.method(dbus_interface='com.example.Sample',
+ in_signature='', out_signature='s',
+ sender_keyword='sender')
+ def SayHello(self, sender=None):
+ return 'Hello, %s!' % sender
+ # -> something like 'Hello, :1.1!'
+
+Asynchronous method implementations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+FIXME and also add an example, perhaps examples/example-async-service.py
+
+Emitting signals with ``dbus.service.signal``
+---------------------------------------------
+
+To export a signal, use the decorator ``dbus.service.signal``; to emit
+that signal, call the decorated method. The decorated method can also
+contain code which will be run when called, as usual. For example::
+
+ class Example(dbus.service.Object):
+ def __init__(self, object_path):
+ dbus.service.Object.__init__(self, dbus.SessionBus(), path)
+
+ @dbus.service.signal(dbus_interface='com.example.Sample',
+ signature='us')
+ def NumberOfBottlesChanged(self, number, contents):
+ print "%d bottles of %s on the wall" % (number, contents)
+
+ e = Example('/bottle-counter')
+ e.NumberOfBottlesChanged(100, 'beer')
+ # -> emits com.example.Sample.NumberOfBottlesChanged(100, 'beer')
+ # and prints "100 bottles of beer on the wall"
+
+The signal will be queued for sending when the decorated method returns -
+you can prevent the signal from being sent by raising an exception
+from the decorated method (for instance, if the parameters are
+inappropriate). The signal will only actually be sent when the event loop
+next runs.
+
+Example
+~~~~~~~
+
+``examples/example-signal-emitter.py`` emits some signals on demand when
+one of its methods is called. (In reality, you'd emit a signal when some
+sort of internal state changed, which may or may not be triggered by a
+D-Bus method call.)
+
+.. --------------------------------------------------------------------
+
+License for this document
+=========================
+
+Copyright 2006-2007 `Collabora Ltd.`_
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+..
+ vim:set ft=rst sw=4 sts=4 et tw=72:
diff --git a/examples/example-async-client.py b/examples/example-async-client.py
new file mode 100755
index 0000000..7b97943
--- /dev/null
+++ b/examples/example-async-client.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+usage = """Usage:
+python3 example-service.py &
+python3 example-async-client.py
+python3 example-client.py --exit-service
+"""
+
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import sys
+import traceback
+
+from gi.repository import GLib
+
+import dbus
+import dbus.mainloop.glib
+
+# Callbacks for asynchronous calls
+
+def handle_hello_reply(r):
+ global hello_replied
+ hello_replied = True
+
+ print("async client:", str(r))
+
+ if hello_replied and raise_replied:
+ loop.quit()
+
+def handle_hello_error(e):
+ global failed
+ global hello_replied
+ hello_replied = True
+ failed = True
+
+ print("async client: HelloWorld raised an exception! That's not meant to happen...")
+ print("\t", str(e))
+
+ if hello_replied and raise_replied:
+ loop.quit()
+
+def handle_raise_reply():
+ global failed
+ global raise_replied
+ raise_replied = True
+ failed = True
+
+ print("async client: RaiseException returned normally! That's not meant to happen...")
+
+ if hello_replied and raise_replied:
+ loop.quit()
+
+def handle_raise_error(e):
+ global raise_replied
+ raise_replied = True
+
+ print("async client: RaiseException raised an exception as expected:")
+ print("\t", str(e))
+
+ if hello_replied and raise_replied:
+ loop.quit()
+
+def make_calls():
+ # To make an async call, use the reply_handler and error_handler kwargs
+ remote_object.HelloWorld("Hello from example-async-client.py!",
+ dbus_interface='com.example.SampleInterface',
+ reply_handler=handle_hello_reply,
+ error_handler=handle_hello_error)
+
+ # Interface objects also support async calls
+ iface = dbus.Interface(remote_object, 'com.example.SampleInterface')
+
+ iface.RaiseException(reply_handler=handle_raise_reply,
+ error_handler=handle_raise_error)
+
+ return False
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SessionBus()
+ try:
+ remote_object = bus.get_object("com.example.SampleService","/SomeObject")
+ except dbus.DBusException:
+ traceback.print_exc()
+ print(usage)
+ sys.exit(1)
+
+ # Make the method call after a short delay
+ GLib.timeout_add(1000, make_calls)
+
+ failed = False
+ hello_replied = False
+ raise_replied = False
+
+ loop = GLib.MainLoop()
+ loop.run()
+ if failed:
+ raise SystemExit("Example async client failed!")
diff --git a/examples/example-client.py b/examples/example-client.py
new file mode 100755
index 0000000..6f7ade2
--- /dev/null
+++ b/examples/example-client.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+usage = """Usage:
+python3 example-service.py &
+python3 example-client.py
+python3 example-client.py --exit-service
+"""
+
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import sys
+from traceback import print_exc
+
+import dbus
+
+def main():
+ bus = dbus.SessionBus()
+
+ try:
+ remote_object = bus.get_object("com.example.SampleService",
+ "/SomeObject")
+
+ # you can either specify the dbus_interface in each call...
+ hello_reply_list = remote_object.HelloWorld("Hello from example-client.py!",
+ dbus_interface = "com.example.SampleInterface")
+ except dbus.DBusException:
+ print_exc()
+ print(usage)
+ sys.exit(1)
+
+ print("client:", hello_reply_list)
+
+ # ... or create an Interface wrapper for the remote object
+ iface = dbus.Interface(remote_object, "com.example.SampleInterface")
+
+ hello_reply_tuple = iface.GetTuple()
+
+ print("client:", hello_reply_tuple)
+
+ hello_reply_dict = iface.GetDict()
+
+ print("client:", hello_reply_dict)
+
+ # D-Bus exceptions are mapped to Python exceptions
+ try:
+ iface.RaiseException()
+ except dbus.DBusException as e:
+ print("client:", str(e))
+
+ # introspection is automatically supported
+ print("client:", remote_object.Introspect(dbus_interface="org.freedesktop.DBus.Introspectable"))
+
+ if sys.argv[1:] == ['--exit-service']:
+ iface.Exit()
+
+if __name__ == '__main__':
+ main()
diff --git a/examples/example-service.py b/examples/example-service.py
new file mode 100755
index 0000000..e1ed325
--- /dev/null
+++ b/examples/example-service.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+usage = """Usage:
+python3 example-service.py &
+python3 example-client.py
+python3 example-async-client.py
+python3 example-client.py --exit-service
+"""
+
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from gi.repository import GLib
+
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+
+class DemoException(dbus.DBusException):
+ _dbus_error_name = 'com.example.DemoException'
+
+class SomeObject(dbus.service.Object):
+
+ @dbus.service.method("com.example.SampleInterface",
+ in_signature='s', out_signature='as')
+ def HelloWorld(self, hello_message):
+ print("service:", str(hello_message))
+ return ["Hello", " from example-service.py", "with unique name",
+ session_bus.get_unique_name()]
+
+ @dbus.service.method("com.example.SampleInterface",
+ in_signature='', out_signature='')
+ def RaiseException(self):
+ raise DemoException('The RaiseException method does what you might '
+ 'expect')
+
+ @dbus.service.method("com.example.SampleInterface",
+ in_signature='', out_signature='(ss)')
+ def GetTuple(self):
+ return ("Hello Tuple", " from example-service.py")
+
+ @dbus.service.method("com.example.SampleInterface",
+ in_signature='', out_signature='a{ss}')
+ def GetDict(self):
+ return {"first": "Hello Dict", "second": " from example-service.py"}
+
+ @dbus.service.method("com.example.SampleInterface",
+ in_signature='', out_signature='')
+ def Exit(self):
+ mainloop.quit()
+
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ session_bus = dbus.SessionBus()
+ name = dbus.service.BusName("com.example.SampleService", session_bus)
+ object = SomeObject(session_bus, '/SomeObject')
+
+ mainloop = GLib.MainLoop()
+ print("Running example service.")
+ print(usage)
+ mainloop.run()
diff --git a/examples/example-signal-emitter.py b/examples/example-signal-emitter.py
new file mode 100755
index 0000000..fc3f0db
--- /dev/null
+++ b/examples/example-signal-emitter.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+usage = """Usage:
+python3 example-signal-emitter.py &
+python3 example-signal-recipient.py
+python3 example-signal-recipient.py --exit-service
+"""
+
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from gi.repository import GLib
+
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+
+class TestObject(dbus.service.Object):
+ def __init__(self, conn, object_path='/com/example/TestService/object'):
+ dbus.service.Object.__init__(self, conn, object_path)
+
+ @dbus.service.signal('com.example.TestService')
+ def HelloSignal(self, message):
+ # The signal is emitted when this method exits
+ # You can have code here if you wish
+ pass
+
+ @dbus.service.method('com.example.TestService')
+ def emitHelloSignal(self):
+ #you emit signals by calling the signal's skeleton method
+ self.HelloSignal('Hello')
+ return 'Signal emitted'
+
+ @dbus.service.method("com.example.TestService",
+ in_signature='', out_signature='')
+ def Exit(self):
+ loop.quit()
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ session_bus = dbus.SessionBus()
+ name = dbus.service.BusName('com.example.TestService', session_bus)
+ object = TestObject(session_bus)
+
+ loop = GLib.MainLoop()
+ print("Running example signal emitter service.")
+ print(usage)
+ loop.run()
diff --git a/examples/example-signal-recipient.py b/examples/example-signal-recipient.py
new file mode 100755
index 0000000..6e89ace
--- /dev/null
+++ b/examples/example-signal-recipient.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+usage = """Usage:
+python3 example-signal-emitter.py &
+python3 example-signal-recipient.py
+python3 example-signal-recipient.py --exit-service
+"""
+
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import sys
+import traceback
+
+from gi.repository import GLib
+
+import dbus
+import dbus.mainloop.glib
+
+def handle_reply(msg):
+ print("recipient:", msg)
+
+def handle_error(e):
+ print("recipient:", str(e))
+
+def emit_signal():
+ #call the emitHelloSignal method
+ object.emitHelloSignal(dbus_interface="com.example.TestService")
+ #reply_handler = handle_reply, error_handler = handle_error)
+ # exit after waiting a short time for the signal
+ GLib.timeout_add(2000, loop.quit)
+
+ if sys.argv[1:] == ['--exit-service']:
+ object.Exit(dbus_interface='com.example.TestService')
+
+ return False
+
+def hello_signal_handler(hello_string):
+ print("recipient: Received signal (by connecting using remote object) and it says: "
+ + hello_string)
+
+def catchall_signal_handler(*args, **kwargs):
+ print("recipient: Caught signal (in catchall handler) "
+ + kwargs['dbus_interface'] + "." + kwargs['member'])
+ for arg in args:
+ print(" " + str(arg))
+
+def catchall_hello_signals_handler(hello_string):
+ print("recipient: Received a hello signal and it says " + hello_string)
+
+def catchall_testservice_interface_handler(hello_string, dbus_message):
+ print("recipient: com.example.TestService interface says " + hello_string + " when it sent signal " + dbus_message.get_member())
+
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SessionBus()
+ try:
+ object = bus.get_object("com.example.TestService","/com/example/TestService/object")
+
+ object.connect_to_signal("HelloSignal", hello_signal_handler, dbus_interface="com.example.TestService", arg0="Hello")
+ except dbus.DBusException:
+ traceback.print_exc()
+ print(usage)
+ sys.exit(1)
+
+ #lets make a catchall
+ bus.add_signal_receiver(catchall_signal_handler, interface_keyword='dbus_interface', member_keyword='member')
+
+ bus.add_signal_receiver(catchall_hello_signals_handler, dbus_interface = "com.example.TestService", signal_name = "HelloSignal")
+
+ bus.add_signal_receiver(catchall_testservice_interface_handler, dbus_interface = "com.example.TestService", message_keyword='dbus_message')
+
+ # Tell the remote object to emit the signal after a short delay
+ GLib.timeout_add(2000, emit_signal)
+
+ loop = GLib.MainLoop()
+ loop.run()
diff --git a/examples/gconf-proxy-client.py b/examples/gconf-proxy-client.py
new file mode 100755
index 0000000..c03e947
--- /dev/null
+++ b/examples/gconf-proxy-client.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+# Client for gconf-proxy-service2.py.
+
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import dbus
+
+gconf_key = "/desktop/gnome/file_views/icon_theme"
+
+bus = dbus.SessionBus()
+gconf_key_object = dbus.Interface(bus.get_object("com.example.GConfProxy", "/org/gnome/GConf" + gconf_key), "org.gnome.GConf")
+
+value = gconf_key_object.getString()
+
+print("client: Value of GConf key %s is %s" % (gconf_key, value))
diff --git a/examples/gconf-proxy-service2.py b/examples/gconf-proxy-service2.py
new file mode 100755
index 0000000..74cc814
--- /dev/null
+++ b/examples/gconf-proxy-service2.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+# Example of implementing an entire subtree of objects using
+# a FallbackObject.
+#
+# This is not a particularly realistic example of real-world code any more,
+# because GConf now uses D-Bus internally itself, and is deprecated;
+# but it's a valid example of a FallbackObject.
+
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import dbus
+import dbus.mainloop.glib
+import dbus.service
+
+from gi.repository import GLib
+
+try:
+ import gconf
+except ImportError:
+ print('service: gconf not available, using mock implementation')
+ gconf = None
+
+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+# there is a real service called "org.gnome.GConf"; don't collide with it.
+name = dbus.service.BusName("com.example.GConfProxy", dbus.SessionBus())
+
+class GConfObject(dbus.service.FallbackObject):
+ def __init__(self):
+ dbus.service.FallbackObject.__init__(self, dbus.SessionBus(), '/org/gnome/GConf')
+ if gconf is None:
+ self.client = None
+ else:
+ self.client = gconf.client_get_default()
+
+ @dbus.service.method("org.gnome.GConf", in_signature='', out_signature='s', rel_path_keyword='object_path')
+ def getString(self, object_path):
+ if self.client is None:
+ return '<gconf not available>'
+
+ return self.client.get_string(object_path)
+
+ @dbus.service.method("org.gnome.GConf", in_signature='s', out_signature='', rel_path_keyword='object_path')
+ def setString(self, value, object_path):
+ if self.client is None:
+ raise RuntimeError('gconf not available')
+
+ self.client.set_string(object_path, value)
+
+ @dbus.service.method("org.gnome.GConf", in_signature='', out_signature='i', rel_path_keyword='object_path')
+ def getInt(self, object_path):
+ if self.client is None:
+ return 42
+
+ return self.client.get_int(object_path)
+
+ @dbus.service.method("org.gnome.GConf", in_signature='i', out_signature='', rel_path_keyword='object_path')
+ def setInt(self, value, object_path):
+ if self.client is None:
+ raise RuntimeError('gconf not available')
+
+ self.client.set_int(object_path, value)
+
+gconf_service = GConfObject()
+
+print ("GConf Proxy service started.")
+print ("Run 'gconf-proxy-client.py' to fetch a GConf key through the proxy...")
+
+mainloop = GLib.MainLoop()
+mainloop.run()
diff --git a/examples/list-system-services.py b/examples/list-system-services.py
new file mode 100755
index 0000000..aad9e9a
--- /dev/null
+++ b/examples/list-system-services.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+"""Usage: python3 list-system-services.py [--session|--system]
+List services on the system bus (default) or the session bus."""
+
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import sys
+
+import dbus
+
+def main(argv):
+ factory = dbus.SystemBus
+
+ if len(argv) > 2:
+ sys.exit(__doc__)
+ elif len(argv) == 2:
+ if argv[1] == '--session':
+ factory = dbus.SessionBus
+ elif argv[1] != '--system':
+ sys.exit(__doc__)
+
+ # Get a connection to the system or session bus as appropriate
+ # We're only using blocking calls, so don't actually need a main loop here
+ bus = factory()
+
+ # This could be done by calling bus.list_names(), but here's
+ # more or less what that means:
+
+ # Get a reference to the desktop bus' standard object, denoted
+ # by the path /org/freedesktop/DBus.
+ dbus_object = bus.get_object('org.freedesktop.DBus',
+ '/org/freedesktop/DBus')
+
+ # The object /org/freedesktop/DBus
+ # implements the 'org.freedesktop.DBus' interface
+ dbus_iface = dbus.Interface(dbus_object, 'org.freedesktop.DBus')
+
+ # One of the member functions in the org.freedesktop.DBus interface
+ # is ListNames(), which provides a list of all the other services
+ # registered on this bus. Call it, and print the list.
+ services = dbus_iface.ListNames()
+ services.sort()
+ for service in services:
+ print(service)
+
+if __name__ == '__main__':
+ main(sys.argv)
diff --git a/examples/unix-fd-client.py b/examples/unix-fd-client.py
new file mode 100755
index 0000000..5725513
--- /dev/null
+++ b/examples/unix-fd-client.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+import time
+
+usage = """Usage:
+python3 unix-fd-service.py <file name> &
+python3 unix-fd-client.py
+"""
+
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+# Copyright (C) 2010 Signove <http://www.signove.com>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import sys
+from traceback import print_exc
+import os
+
+import dbus
+
+def main():
+ bus = dbus.SessionBus()
+
+ try:
+ remote_object = bus.get_object("com.example.SampleService",
+ "/SomeObject")
+
+ except dbus.DBusException:
+ print_exc()
+ print(usage)
+ sys.exit(1)
+
+ iface = dbus.Interface(remote_object, "com.example.SampleInterface")
+
+ # UnixFd is an opaque object that takes care of received fd
+ fd_object = iface.GetFd()
+ print("client: fd_object = %s" % fd_object)
+
+ # Once we take the fd number, we are in charge of closing it!
+ fd = fd_object.take()
+ print("client: fd = %s" % fd)
+
+ # We want to encapsulate the integer fd into a Python file or socket object
+ f = os.fdopen(fd, "r")
+
+ # If it were an UNIX socket we would do
+ # sk = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM)
+ # os.close(fd)
+ #
+ # fromfd() dup()s the descriptor so we need to close the original,
+ # otherwise it 'leaks' (stays open until program exits).
+
+ f.seek(0)
+ print("client: read from fd = %r" % f.read())
+
+if __name__ == '__main__':
+ main()
diff --git a/examples/unix-fd-service.py b/examples/unix-fd-service.py
new file mode 100755
index 0000000..e7a6b50
--- /dev/null
+++ b/examples/unix-fd-service.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+usage = """Usage:
+python3 unix-fd-service.py <file name> &
+python3 unix-fd-client.py
+"""
+
+# Copyright (C) 2004-2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+# Copyright (C) 2010 Signove <http://www.signove.com>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from gi.repository import GLib
+
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+import sys
+import random
+
+class SomeObject(dbus.service.Object):
+
+ counter = 0
+
+ @dbus.service.method("com.example.SampleInterface",
+ in_signature='', out_signature='h')
+ def GetFd(self):
+ self.counter = (self.counter + 1) % 3
+
+ if self.counter == 0:
+ print("service: sending UnixFd(filelike)")
+ return dbus.types.UnixFd(f)
+ elif self.counter == 1:
+ print("service: sending int")
+ return f.fileno()
+ else:
+ print("service: sending UnixFd(int)")
+ return dbus.types.UnixFd(f.fileno())
+
+if len(sys.argv) < 2:
+ print(usage)
+ sys.exit(1)
+
+f = open(sys.argv[1], "r")
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ session_bus = dbus.SessionBus()
+ name = dbus.service.BusName("com.example.SampleService", session_bus)
+ object = SomeObject(session_bus, '/SomeObject')
+
+ mainloop = GLib.MainLoop()
+ print("Running fd service.")
+ print(usage)
+ mainloop.run()
diff --git a/include/dbus/dbus-python.h b/include/dbus/dbus-python.h
new file mode 100644
index 0000000..72a9bf1
--- /dev/null
+++ b/include/dbus/dbus-python.h
@@ -0,0 +1,106 @@
+/* C API for _dbus_bindings, used by _dbus_glib_bindings and any third-party
+ * main loop integration which might happen in future.
+ *
+ * This file is currently Python-version-independent - please keep it that way.
+ *
+ * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef DBUS_PYTHON_H
+#define DBUS_PYTHON_H
+
+#include <Python.h>
+#include <dbus/dbus.h>
+
+#define PYDBUS_CAPSULE_NAME "_dbus_bindings._C_API"
+
+DBUS_BEGIN_DECLS
+
+typedef void (*_dbus_py_func_ptr)(void);
+
+typedef dbus_bool_t (*_dbus_py_conn_setup_func)(DBusConnection *, void *);
+typedef dbus_bool_t (*_dbus_py_srv_setup_func)(DBusServer *, void *);
+typedef void (*_dbus_py_free_func)(void *);
+
+#define DBUS_BINDINGS_API_COUNT 3
+
+#ifdef INSIDE_DBUS_PYTHON_BINDINGS
+
+extern DBusConnection *DBusPyConnection_BorrowDBusConnection(PyObject *);
+extern PyObject *DBusPyNativeMainLoop_New4(_dbus_py_conn_setup_func,
+ _dbus_py_srv_setup_func,
+ _dbus_py_free_func,
+ void *);
+
+#else
+
+static PyObject *_dbus_bindings_module = NULL;
+static _dbus_py_func_ptr *dbus_bindings_API;
+
+#define DBusPyConnection_BorrowDBusConnection \
+ (*(DBusConnection *(*)(PyObject *))dbus_bindings_API[1])
+#define DBusPyNativeMainLoop_New4 \
+ ((PyObject *(*)(_dbus_py_conn_setup_func, _dbus_py_srv_setup_func, \
+ _dbus_py_free_func, void *))dbus_bindings_API[2])
+
+static int
+import_dbus_bindings(const char *this_module_name)
+{
+ PyObject *c_api;
+ int count;
+
+ _dbus_bindings_module = PyImport_ImportModule("_dbus_bindings");
+ if (!_dbus_bindings_module) {
+ return -1;
+ }
+ c_api = PyObject_GetAttrString(_dbus_bindings_module, "_C_API");
+ if (c_api == NULL) return -1;
+ dbus_bindings_API = NULL;
+ if (PyCapsule_IsValid(c_api, PYDBUS_CAPSULE_NAME)) {
+ dbus_bindings_API = (_dbus_py_func_ptr *)PyCapsule_GetPointer(
+ c_api, PYDBUS_CAPSULE_NAME);
+ }
+ Py_CLEAR(c_api);
+ if (!dbus_bindings_API) {
+ PyErr_SetString(PyExc_RuntimeError, "C API is not a PyCapsule");
+ return -1;
+ }
+ count = *(int *)dbus_bindings_API[0];
+ if (count < DBUS_BINDINGS_API_COUNT) {
+ PyErr_Format(PyExc_RuntimeError,
+ "_dbus_bindings has API version %d but %s needs "
+ "_dbus_bindings API version at least %d",
+ count, this_module_name,
+ DBUS_BINDINGS_API_COUNT);
+ return -1;
+ }
+ return 0;
+}
+
+#endif
+
+DBUS_END_DECLS
+
+#endif
diff --git a/m4/ax_python_devel.m4 b/m4/ax_python_devel.m4
new file mode 100644
index 0000000..35f025f
--- /dev/null
+++ b/m4/ax_python_devel.m4
@@ -0,0 +1,416 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_python_devel.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PYTHON_DEVEL([version])
+#
+# DESCRIPTION
+#
+# Note: Defines as a precious variable "PYTHON_VERSION". Don't override it
+# in your configure.ac.
+#
+# This macro checks for Python and tries to get the include path to
+# 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LIBS) output
+# variables. It also exports $(PYTHON_EXTRA_LIBS) and
+# $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code.
+#
+# You can search for some particular version of Python by passing a
+# parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please
+# note that you *have* to pass also an operator along with the version to
+# match, and pay special attention to the single quotes surrounding the
+# version number. Don't use "PYTHON_VERSION" for this: that environment
+# variable is declared as precious and thus reserved for the end-user.
+#
+# This macro should work for all versions of Python >= 2.1.0. As an end
+# user, you can disable the check for the python version by setting the
+# PYTHON_NOVERSIONCHECK environment variable to something else than the
+# empty string.
+#
+# If you need to use this macro for an older Python version, please
+# contact the authors. We're always open for feedback.
+#
+# LICENSE
+#
+# Copyright (c) 2009 Sebastian Huber <sebastian-huber@web.de>
+# Copyright (c) 2009 Alan W. Irwin
+# Copyright (c) 2009 Rafael Laboissiere <rafael@laboissiere.net>
+# Copyright (c) 2009 Andrew Collier
+# Copyright (c) 2009 Matteo Settenvini <matteo@member.fsf.org>
+# Copyright (c) 2009 Horst Knorr <hk_classes@knoda.org>
+# Copyright (c) 2013 Daniel Mullner <muellner@math.stanford.edu>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 26
+
+AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL])
+AC_DEFUN([AX_PYTHON_DEVEL],[
+ #
+ # Allow the use of a (user set) custom python version
+ #
+ AC_ARG_VAR([PYTHON_VERSION],[The installed Python
+ version to use, for example '2.3'. This string
+ will be appended to the Python interpreter
+ canonical name.])
+
+ AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]])
+ if test -z "$PYTHON"; then
+ AC_MSG_ERROR([Cannot find python$PYTHON_VERSION in your system path])
+ PYTHON_VERSION=""
+ fi
+
+ #
+ # Check for a version of Python >= 2.1.0
+ #
+ AC_MSG_CHECKING([for a version of Python >= '2.1.0'])
+ ac_supports_python_ver=`$PYTHON -c "import sys; \
+ ver = sys.version.split ()[[0]]; \
+ print (ver >= '2.1.0')"`
+ if test "$ac_supports_python_ver" != "True"; then
+ if test -z "$PYTHON_NOVERSIONCHECK"; then
+ AC_MSG_RESULT([no])
+ AC_MSG_FAILURE([
+This version of the AC@&t@_PYTHON_DEVEL macro
+doesn't work properly with versions of Python before
+2.1.0. You may need to re-run configure, setting the
+variables PYTHON_CPPFLAGS, PYTHON_LIBS, PYTHON_SITE_PKG,
+PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand.
+Moreover, to disable this check, set PYTHON_NOVERSIONCHECK
+to something else than an empty string.
+])
+ else
+ AC_MSG_RESULT([skip at user request])
+ fi
+ else
+ AC_MSG_RESULT([yes])
+ fi
+
+ #
+ # If the macro parameter ``version'' is set, honour it.
+ # A Python shim class, VPy, is used to implement correct version comparisons via
+ # string expressions, since e.g. a naive textual ">= 2.7.3" won't work for
+ # Python 2.7.10 (the ".1" being evaluated as less than ".3").
+ #
+ if test -n "$1"; then
+ AC_MSG_CHECKING([for a version of Python $1])
+ cat << EOF > ax_python_devel_vpy.py
+class VPy:
+ def vtup(self, s):
+ return tuple(map(int, s.strip().replace("rc", ".").split(".")))
+ def __init__(self):
+ import sys
+ self.vpy = tuple(sys.version_info)
+ def __eq__(self, s):
+ return self.vpy == self.vtup(s)
+ def __ne__(self, s):
+ return self.vpy != self.vtup(s)
+ def __lt__(self, s):
+ return self.vpy < self.vtup(s)
+ def __gt__(self, s):
+ return self.vpy > self.vtup(s)
+ def __le__(self, s):
+ return self.vpy <= self.vtup(s)
+ def __ge__(self, s):
+ return self.vpy >= self.vtup(s)
+EOF
+ ac_supports_python_ver=`$PYTHON -c "import ax_python_devel_vpy; \
+ ver = ax_python_devel_vpy.VPy(); \
+ print (ver $1)"`
+ rm -rf ax_python_devel_vpy*.py* __pycache__/ax_python_devel_vpy*.py*
+ if test "$ac_supports_python_ver" = "True"; then
+ AC_MSG_RESULT([yes])
+ else
+ AC_MSG_RESULT([no])
+ AC_MSG_ERROR([this package requires Python $1.
+If you have it installed, but it isn't the default Python
+interpreter in your system path, please pass the PYTHON_VERSION
+variable to configure. See ``configure --help'' for reference.
+])
+ PYTHON_VERSION=""
+ fi
+ fi
+
+ #
+ # Check if you have distutils, else fail
+ #
+ AC_MSG_CHECKING([for the sysconfig Python package])
+ ac_sysconfig_result=`$PYTHON -c "import sysconfig" 2>&1`
+ if test $? -eq 0; then
+ AC_MSG_RESULT([yes])
+ IMPORT_SYSCONFIG="import sysconfig"
+ else
+ AC_MSG_RESULT([no])
+
+ AC_MSG_CHECKING([for the distutils Python package])
+ ac_sysconfig_result=`$PYTHON -c "from distutils import sysconfig" 2>&1`
+ if test $? -eq 0; then
+ AC_MSG_RESULT([yes])
+ IMPORT_SYSCONFIG="from distutils import sysconfig"
+ else
+ AC_MSG_ERROR([cannot import Python module "distutils".
+Please check your Python installation. The error was:
+$ac_sysconfig_result])
+ PYTHON_VERSION=""
+ fi
+ fi
+
+ #
+ # Check for Python include path
+ #
+ AC_MSG_CHECKING([for Python include path])
+ if test -z "$PYTHON_CPPFLAGS"; then
+ if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then
+ # sysconfig module has different functions
+ python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_path ('include'));"`
+ plat_python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_path ('platinclude'));"`
+ else
+ # old distutils way
+ python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_python_inc ());"`
+ plat_python_path=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_python_inc (plat_specific=1));"`
+ fi
+ if test -n "${python_path}"; then
+ if test "${plat_python_path}" != "${python_path}"; then
+ python_path="-I$python_path -I$plat_python_path"
+ else
+ python_path="-I$python_path"
+ fi
+ fi
+ PYTHON_CPPFLAGS=$python_path
+ fi
+ AC_MSG_RESULT([$PYTHON_CPPFLAGS])
+ AC_SUBST([PYTHON_CPPFLAGS])
+
+ #
+ # Check for Python library path
+ #
+ AC_MSG_CHECKING([for Python library path])
+ if test -z "$PYTHON_LIBS"; then
+ # (makes two attempts to ensure we've got a version number
+ # from the interpreter)
+ ac_python_version=`cat<<EOD | $PYTHON -
+
+# join all versioning strings, on some systems
+# major/minor numbers could be in different list elements
+from sysconfig import *
+e = get_config_var('VERSION')
+if e is not None:
+ print(e)
+EOD`
+
+ if test -z "$ac_python_version"; then
+ if test -n "$PYTHON_VERSION"; then
+ ac_python_version=$PYTHON_VERSION
+ else
+ ac_python_version=`$PYTHON -c "import sys; \
+ print ("%d.%d" % sys.version_info[[:2]])"`
+ fi
+ fi
+
+ # Make the versioning information available to the compiler
+ AC_DEFINE_UNQUOTED([HAVE_PYTHON], ["$ac_python_version"],
+ [If available, contains the Python version number currently in use.])
+
+ # First, the library directory:
+ ac_python_libdir=`cat<<EOD | $PYTHON -
+
+# There should be only one
+$IMPORT_SYSCONFIG
+e = sysconfig.get_config_var('LIBDIR')
+if e is not None:
+ print (e)
+EOD`
+
+ # Now, for the library:
+ ac_python_library=`cat<<EOD | $PYTHON -
+
+$IMPORT_SYSCONFIG
+c = sysconfig.get_config_vars()
+if 'LDVERSION' in c:
+ print ('python'+c[['LDVERSION']])
+else:
+ print ('python'+c[['VERSION']])
+EOD`
+
+ # This small piece shamelessly adapted from PostgreSQL python macro;
+ # credits goes to momjian, I think. I'd like to put the right name
+ # in the credits, if someone can point me in the right direction... ?
+ #
+ if test -n "$ac_python_libdir" -a -n "$ac_python_library"
+ then
+ # use the official shared library
+ ac_python_library=`echo "$ac_python_library" | sed "s/^lib//"`
+ PYTHON_LIBS="-L$ac_python_libdir -l$ac_python_library"
+ else
+ # old way: use libpython from python_configdir
+ ac_python_libdir=`$PYTHON -c \
+ "from sysconfig import get_python_lib as f; \
+ import os; \
+ print (os.path.join(f(plat_specific=1, standard_lib=1), 'config'));"`
+ PYTHON_LIBS="-L$ac_python_libdir -lpython$ac_python_version"
+ fi
+
+ if test -z "PYTHON_LIBS"; then
+ AC_MSG_ERROR([
+ Cannot determine location of your Python DSO. Please check it was installed with
+ dynamic libraries enabled, or try setting PYTHON_LIBS by hand.
+ ])
+ fi
+ fi
+ AC_MSG_RESULT([$PYTHON_LIBS])
+ AC_SUBST([PYTHON_LIBS])
+
+ #
+ # Check for site packages
+ #
+ AC_MSG_CHECKING([for Python site-packages path])
+ if test -z "$PYTHON_SITE_PKG"; then
+ if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then
+ PYTHON_SITE_PKG=`$PYTHON -c "
+$IMPORT_SYSCONFIG;
+if hasattr(sysconfig, 'get_default_scheme'):
+ scheme = sysconfig.get_default_scheme()
+else:
+ scheme = sysconfig._get_default_scheme()
+if scheme == 'posix_local':
+ # Debian's default scheme installs to /usr/local/ but we want to find headers in /usr/
+ scheme = 'posix_prefix'
+prefix = '$prefix'
+if prefix == 'NONE':
+ prefix = '$ac_default_prefix'
+sitedir = sysconfig.get_path('purelib', scheme, vars={'base': prefix})
+print(sitedir)"`
+ else
+ # distutils.sysconfig way
+ PYTHON_SITE_PKG=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_python_lib(0,0));"`
+ fi
+ fi
+ AC_MSG_RESULT([$PYTHON_SITE_PKG])
+ AC_SUBST([PYTHON_SITE_PKG])
+
+ #
+ # Check for platform-specific site packages
+ #
+ AC_MSG_CHECKING([for Python platform specific site-packages path])
+ if test -z "$PYTHON_PLATFORM_SITE_PKG"; then
+ if test "$IMPORT_SYSCONFIG" = "import sysconfig"; then
+ PYTHON_PLATFORM_SITE_PKG=`$PYTHON -c "
+$IMPORT_SYSCONFIG;
+if hasattr(sysconfig, 'get_default_scheme'):
+ scheme = sysconfig.get_default_scheme()
+else:
+ scheme = sysconfig._get_default_scheme()
+if scheme == 'posix_local':
+ # Debian's default scheme installs to /usr/local/ but we want to find headers in /usr/
+ scheme = 'posix_prefix'
+prefix = '$prefix'
+if prefix == 'NONE':
+ prefix = '$ac_default_prefix'
+sitedir = sysconfig.get_path('platlib', scheme, vars={'platbase': prefix})
+print(sitedir)"`
+ else
+ # distutils.sysconfig way
+ PYTHON_PLATFORM_SITE_PKG=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ print (sysconfig.get_python_lib(1,0));"`
+ fi
+ fi
+ AC_MSG_RESULT([$PYTHON_PLATFORM_SITE_PKG])
+ AC_SUBST([PYTHON_PLATFORM_SITE_PKG])
+
+ #
+ # libraries which must be linked in when embedding
+ #
+ AC_MSG_CHECKING(python extra libraries)
+ if test -z "$PYTHON_EXTRA_LIBS"; then
+ PYTHON_EXTRA_LIBS=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ conf = sysconfig.get_config_var; \
+ print (conf('LIBS') + ' ' + conf('SYSLIBS'))"`
+ fi
+ AC_MSG_RESULT([$PYTHON_EXTRA_LIBS])
+ AC_SUBST(PYTHON_EXTRA_LIBS)
+
+ #
+ # linking flags needed when embedding
+ #
+ AC_MSG_CHECKING(python extra linking flags)
+ if test -z "$PYTHON_EXTRA_LDFLAGS"; then
+ PYTHON_EXTRA_LDFLAGS=`$PYTHON -c "$IMPORT_SYSCONFIG; \
+ conf = sysconfig.get_config_var; \
+ print (conf('LINKFORSHARED'))"`
+ fi
+ AC_MSG_RESULT([$PYTHON_EXTRA_LDFLAGS])
+ AC_SUBST(PYTHON_EXTRA_LDFLAGS)
+
+ #
+ # final check to see if everything compiles alright
+ #
+ AC_MSG_CHECKING([consistency of all components of python development environment])
+ # save current global flags
+ ac_save_LIBS="$LIBS"
+ ac_save_LDFLAGS="$LDFLAGS"
+ ac_save_CPPFLAGS="$CPPFLAGS"
+ LIBS="$ac_save_LIBS $PYTHON_LIBS $PYTHON_EXTRA_LIBS"
+ LDFLAGS="$ac_save_LDFLAGS $PYTHON_EXTRA_LDFLAGS"
+ CPPFLAGS="$ac_save_CPPFLAGS $PYTHON_CPPFLAGS"
+ AC_LANG_PUSH([C])
+ AC_LINK_IFELSE([
+ AC_LANG_PROGRAM([[#include <Python.h>]],
+ [[Py_Initialize();]])
+ ],[pythonexists=yes],[pythonexists=no])
+ AC_LANG_POP([C])
+ # turn back to default flags
+ CPPFLAGS="$ac_save_CPPFLAGS"
+ LIBS="$ac_save_LIBS"
+ LDFLAGS="$ac_save_LDFLAGS"
+
+ AC_MSG_RESULT([$pythonexists])
+
+ if test ! "x$pythonexists" = "xyes"; then
+ AC_MSG_FAILURE([
+ Could not link test program to Python. Maybe the main Python library has been
+ installed in some non-standard library path. If so, pass it to configure,
+ via the LIBS environment variable.
+ Example: ./configure LIBS="-L/usr/non-standard-path/python/lib"
+ ============================================================================
+ ERROR!
+ You probably have to install the development version of the Python package
+ for your distribution. The exact name of this package varies among them.
+ ============================================================================
+ ])
+ PYTHON_VERSION=""
+ fi
+
+ #
+ # all done!
+ #
+])
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..151fd6f
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+# Copyright © 2016 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from distutils.dir_util import copy_tree, mkpath
+from distutils.file_util import copy_file
+from setuptools.dist import Distribution
+from setuptools import setup, Extension
+import os
+import subprocess
+import sys
+
+if os.path.exists('.version'):
+ version = open('.version').read().strip()
+else:
+ version = subprocess.check_output(['autoconf', '--trace', 'AC_INIT:$2',
+ 'configure.ac']).decode('utf-8').strip()
+
+class Build(Distribution().get_command_class('build')):
+ """Dummy version of distutils build which runs an Autotools build system
+ instead.
+ """
+
+ def run(self):
+ srcdir = os.getcwd()
+ builddir = os.path.join(srcdir, self.build_temp)
+ configure = os.path.join(srcdir, 'configure')
+ mkpath(builddir)
+
+ if not os.path.exists(configure):
+ configure = os.path.join(srcdir, 'autogen.sh')
+
+ subprocess.check_call([
+ configure,
+ '--disable-maintainer-mode',
+ 'PYTHON=' + sys.executable,
+ # Put the documentation, etc. out of the way: we only want
+ # the Python code and extensions
+ '--prefix=' + os.path.join(builddir, 'prefix'),
+ ],
+ cwd=builddir)
+ make_args = [
+ 'pythondir=' + os.path.join(srcdir, self.build_lib),
+ 'pyexecdir=' + os.path.join(srcdir, self.build_lib),
+ ]
+ subprocess.check_call(['make', '-C', builddir] + make_args)
+ subprocess.check_call(['make', '-C', builddir, 'install'] + make_args)
+
+class BuildExt(Distribution().get_command_class('build_ext')):
+ def run(self):
+ pass
+
+class BuildPy(Distribution().get_command_class('build_py')):
+ def run(self):
+ pass
+
+dbus_bindings = Extension('_dbus_bindings',
+ sources=['dbus_bindings/module.c'])
+dbus_glib_bindings = Extension('_dbus_glib_bindings',
+ sources=['dbus_glib_bindings/module.c'])
+
+setup(
+ name='dbus-python',
+ version=version,
+ description='Python bindings for libdbus',
+ long_description=open('README').read(),
+ maintainer='The D-Bus maintainers',
+ maintainer_email='dbus@lists.freedesktop.org',
+ download_url='http://dbus.freedesktop.org/releases/dbus-python/',
+ url='http://www.freedesktop.org/wiki/Software/DBusBindings/#python',
+ packages=['dbus'],
+ ext_modules=[dbus_bindings, dbus_glib_bindings],
+ license='Expat (MIT/X11)',
+ classifiers=[
+ 'Development Status :: 7 - Inactive',
+ 'License :: OSI Approved :: MIT License',
+ 'Programming Language :: C',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: Implementation :: CPython',
+ 'Topic :: Software Development :: Object Brokering',
+ ],
+ cmdclass={
+ 'build': Build,
+ 'build_py': BuildPy,
+ 'build_ext': BuildExt,
+ },
+ tests_require=['tap.py'],
+)
diff --git a/.editorconfig b/subprojects/dbus-gmain/.editorconfig
index 05bbe50..05bbe50 100644
--- a/.editorconfig
+++ b/subprojects/dbus-gmain/.editorconfig
diff --git a/subprojects/dbus-gmain/.gitignore b/subprojects/dbus-gmain/.gitignore
new file mode 100644
index 0000000..1d15bff
--- /dev/null
+++ b/subprojects/dbus-gmain/.gitignore
@@ -0,0 +1,22 @@
+# Copyright 2006-2022 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+
+*.a
+*.gcda
+*.gcno
+*.lineno
+*.lo
+*.o
+*~
+.deps/
+.libs/
+/Makefile
+/Makefile.in
+/libdbus-gmain.la
+/test-suite.log
+/tests/*.log
+/tests/*.trs
+/tests/libtest.la
+/tests/test-30574
+/tests/test-thread-client
+/tests/test-thread-server
diff --git a/subprojects/dbus-gmain/.gitlab-ci.yml b/subprojects/dbus-gmain/.gitlab-ci.yml
new file mode 100644
index 0000000..c063317
--- /dev/null
+++ b/subprojects/dbus-gmain/.gitlab-ci.yml
@@ -0,0 +1,34 @@
+# Copyright © 2015-2022 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+
+image: debian:bullseye-slim
+
+stages:
+ - build
+
+build:
+ stage: build
+ image: "debian:bullseye-slim"
+ script:
+ - |
+ apt-get -y update
+ apt-get -y install \
+ build-essential \
+ dbus-daemon \
+ libdbus-1-dev \
+ libglib2.0-dev \
+ meson \
+ pkg-config \
+ python3 \
+ ${NULL+}
+ meson _build
+ meson compile -C _build -v
+ meson test -C _build -v
+
+reuse:
+ stage: build
+ image:
+ name: fsfe/reuse:latest
+ entrypoint: [""]
+ script:
+ - reuse lint
diff --git a/subprojects/dbus-gmain/.reuse/dep5 b/subprojects/dbus-gmain/.reuse/dep5
new file mode 100644
index 0000000..5985ce4
--- /dev/null
+++ b/subprojects/dbus-gmain/.reuse/dep5
@@ -0,0 +1,20 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: dbus-gmain
+Upstream-Contact: https://gitlab.freedesktop.org/dbus/dbus-glib/-/issues
+Source: https://gitlab.freedesktop.org/dbus/dbus-glib
+
+Files: AUTHORS
+Copyright:
+ Copyright 2006 Red Hat, Inc.
+ Copyright 2007 Codethink Ltd.
+License: MIT
+
+Files: CONTRIBUTING.md
+Copyright:
+ 2008-2018 Wayland contributors
+ 2018 Collabora Ltd.
+License: MIT
+
+Files: README.md
+Copyright: 2018 Collabora Ltd.
+License: MIT
diff --git a/subprojects/dbus-gmain/AUTHORS b/subprojects/dbus-gmain/AUTHORS
new file mode 100644
index 0000000..83c7766
--- /dev/null
+++ b/subprojects/dbus-gmain/AUTHORS
@@ -0,0 +1,15 @@
+Alexander Larsson
+Anders Carlsson
+Carlos Garnacho Parro
+Christian Dywan
+Colin Walters
+Havoc Pennington
+James Willcox
+Kristian Hogsberg
+Marc-Andre Lureau
+Mikael Hallendal
+Mike Gorse
+Richard Hult
+Ross Burton
+Steve Frécinaux
+Tobias Mueller
diff --git a/subprojects/dbus-gmain/CONTRIBUTING.md b/subprojects/dbus-gmain/CONTRIBUTING.md
new file mode 100644
index 0000000..5bbcee1
--- /dev/null
+++ b/subprojects/dbus-gmain/CONTRIBUTING.md
@@ -0,0 +1,110 @@
+# Contributing to dbus-gmain
+
+dbus-gmain is hosted by freedesktop.org. The source code repository,
+issue tracking and merge requests are provided by freedesktop.org's
+Gitlab installation, as a branch in the dbus-glib project:
+<https://gitlab.freedesktop.org/dbus/dbus-glib/tree/dbus-gmain>
+
+## Making changes
+
+If you are making changes that you wish to be incorporated upstream,
+please do as small commits to your local git tree that are individually
+correct, so there is a good history of your changes.
+
+The first line of the commit message should be a single sentence that
+describes the change, optionally with a prefix that identifies the
+area of the code that is affected.
+
+The body of the commit message should describe what the patch changes
+and why, and also note any particular side effects. This shouldn't be
+empty on most of the cases. It shouldn't take a lot of effort to write a
+commit message for an obvious change, so an empty commit message body is
+only acceptable if the questions "What?" and "Why?" are already answered
+on the one-line summary.
+
+The lines of the commit message should have at most 76 characters,
+to cope with the way git log presents them.
+
+See [notes on commit messages](https://who-t.blogspot.com/2009/12/on-commit-messages.html),
+[A Note About Git Commit Messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
+or [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/)
+for recommended reading on writing high-quality commit messages.
+
+Your patches should also include a Signed-off-by line with your name and
+email address, indicating that your contribution follows the [Developer's
+Certificate of Origin](https://developercertificate.org/). If you're
+not the patch's original author, you should also gather S-o-b's by
+them (and/or whomever gave the patch to you.) The significance of this
+is that it certifies that you created the patch, that it was created
+under an appropriate open source license, or provided to you under those
+terms. This lets us indicate a chain of responsibility for the copyright
+status of the code.
+
+We won't reject patches that lack S-o-b, but it is strongly recommended.
+
+When you consider changes ready for merging to mainline:
+
+* create a personal fork of <https://gitlab.freedesktop.org/dbus/dbus-glib>
+ on freedesktop.org Gitlab
+* push your changes to your personal fork as a branch
+* create a merge request at
+ <https://gitlab.freedesktop.org/dbus/dbus-glib/merge_requests>,
+ and remember to specify `dbus-gmain` as the target branch
+
+## Automated tests
+
+For nontrivial changes please try to extend the test suite to cover it.
+dbus-gmain uses GLib's test framework; tests are in the `tests/`
+directory.
+
+Run `make check` to run the test suite.
+
+## Coding style
+
+Please match the existing code style (Emacs: "gnu").
+
+## Licensing
+
+Please match the existing licensing (a dual-license: AFL-2.1 or GPL-2+,
+recipient's choice). Entirely new modules can be placed under a more
+permissive license: to avoid license proliferation, our preferred
+permissive license is the variant of the MIT/X11 license used by the
+Expat XML library (for example see the top of tools/ci-build.sh).
+
+## Conduct
+
+As a freedesktop.org project, dbus follows the Contributor Covenant,
+found at: <https://www.freedesktop.org/wiki/CodeOfConduct>
+
+Please conduct yourself in a respectful and civilised manner when
+interacting with community members on mailing lists, IRC, or bug
+trackers. The community represents the project as a whole, and abusive
+or bullying behaviour is not tolerated by the project.
+
+## (Lack of) versioning and releases
+
+dbus-gmain is currently set up to be a git subtree or git submodule,
+so it does not have releases in its own right. It gets merged or
+otherwise included in larger projects like dbus-glib and dbus-python
+instead.
+
+## Information for maintainers
+
+This section is not directly relevant to infrequent contributors.
+
+### Updating the copies of dbus-gmain in dbus-glib and dbus-python
+
+dbus-gmain is maintained via `git subtree`. To update one of the dependent
+projects, assuming you have a checkout of the dbus-gmain branch of the
+dbus-glib repository in ../dbus-gmain:
+
+ git subtree pull -P dbus-gmain ../dbus-gmain HEAD
+
+### Committing other people's patches
+
+If applying a patch from someone else that created them via
+"git-format-patch", you can use "git-am -s" to apply. Otherwise
+apply the patch and then use "git commit --author ..."
+
+Nontrivial patches should always go through Gitlab for peer review,
+so you should have an issue number or a merge request ID to refer to.
diff --git a/subprojects/dbus-gmain/COPYING b/subprojects/dbus-gmain/COPYING
new file mode 100644
index 0000000..72084a8
--- /dev/null
+++ b/subprojects/dbus-gmain/COPYING
@@ -0,0 +1,7 @@
+The D-Bus GLib main loop bindings are licensed to you under your choice
+of the Academic Free License version 2.1, or the GNU General Public
+License version 2. Both licenses are available in the LICENSES directory.
+
+This project's licensing is REUSE-compliant <https://reuse.software/>.
+See individual files for full details of copyright and licensing,
+and see LICENSES/*.txt for the license text.
diff --git a/LICENSES/AFL-2.1.txt b/subprojects/dbus-gmain/LICENSES/AFL-2.1.txt
index 011d6d4..011d6d4 100644
--- a/LICENSES/AFL-2.1.txt
+++ b/subprojects/dbus-gmain/LICENSES/AFL-2.1.txt
diff --git a/LICENSES/GPL-2.0-or-later.txt b/subprojects/dbus-gmain/LICENSES/GPL-2.0-or-later.txt
index 17cb286..17cb286 100644
--- a/LICENSES/GPL-2.0-or-later.txt
+++ b/subprojects/dbus-gmain/LICENSES/GPL-2.0-or-later.txt
diff --git a/subprojects/dbus-gmain/LICENSES/MIT.txt b/subprojects/dbus-gmain/LICENSES/MIT.txt
new file mode 100644
index 0000000..2071b23
--- /dev/null
+++ b/subprojects/dbus-gmain/LICENSES/MIT.txt
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) <year> <copyright holders>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/subprojects/dbus-gmain/Makefile.am b/subprojects/dbus-gmain/Makefile.am
new file mode 100644
index 0000000..a65244a
--- /dev/null
+++ b/subprojects/dbus-gmain/Makefile.am
@@ -0,0 +1,101 @@
+# Copyright © 2002-2003 CodeFactory AB
+# Copyright © 2002-2010 Red Hat, Inc.
+# Copyright © 2003 James Willcox
+# Copyright © 2006 Marc-Andre Lureau
+# Copyright © 2006-2018 Collabora Ltd.
+# Copyright © 2010-2012 Mike Gorse
+#
+# SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later
+
+EXTRA_DIST = \
+ AUTHORS \
+ CONTRIBUTING.md \
+ COPYING \
+ LICENSES/AFL-2.1.txt \
+ LICENSES/GPL-2.0-or-later.txt \
+ LICENSES/MIT.txt \
+ README.md \
+ meson.build \
+ meson_options.txt \
+ tests/meson.build \
+ tests/use-as-subproject.py \
+ tests/use-as-subproject/README \
+ tests/use-as-subproject/meson.build \
+ tests/use-as-subproject/use-dbus-gmain.c \
+ $(NULL)
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/subprojects \
+ -I$(top_builddir) \
+ -I$(top_builddir)/subprojects \
+ $(DBUS_CFLAGS) \
+ $(GLIB_CFLAGS) \
+ $(NULL)
+
+noinst_LTLIBRARIES = \
+ libdbus-gmain.la \
+ tests/libtest.la \
+ $(NULL)
+
+libdbus_gmain_la_SOURCES = \
+ dbus-gmain.c \
+ dbus-gmain/dbus-gmain.h \
+ $(NULL)
+
+libdbus_gmain_la_LIBADD = $(DBUS_LIBS) $(GLIB_LIBS)
+libdbus_gmain_la_LDFLAGS = -no-undefined
+
+tests_libtest_la_SOURCES = \
+ tests/util.c \
+ tests/util.h \
+ $(NULL)
+
+tests_libtest_la_LIBADD = $(DBUS_LIBS) $(GLIB_LIBS)
+tests_libtest_la_LDFLAGS = -no-undefined
+
+TESTS = \
+ tests/test-30574 \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ tests/test-30574 \
+ tests/test-thread-server \
+ tests/test-thread-client \
+ $(NULL)
+
+tests_test_thread_server_SOURCES = \
+ tests/test-thread-server.c \
+ tests/test-thread.h \
+ $(NULL)
+tests_test_thread_server_LDADD = \
+ libdbus-gmain.la \
+ tests/libtest.la \
+ $(GLIB_THREADS_LIBS) \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS) \
+ $(NULL)
+
+tests_test_thread_client_SOURCES = \
+ tests/test-thread-client.c \
+ tests/test-thread.h \
+ $(NULL)
+tests_test_thread_client_LDADD = \
+ libdbus-gmain.la \
+ tests/libtest.la \
+ $(GLIB_THREADS_LIBS) \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS) \
+ $(NULL)
+
+tests_test_30574_SOURCES = \
+ tests/30574.c \
+ $(NULL)
+tests_test_30574_LDADD = \
+ libdbus-gmain.la \
+ tests/libtest.la \
+ $(GLIB_LIBS) \
+ $(DBUS_LIBS) \
+ $(NULL)
+
+LOG_COMPILER = $(DBUS_RUN_SESSION) --
diff --git a/README.md b/subprojects/dbus-gmain/README.md
index 8f40161..8f40161 100644
--- a/README.md
+++ b/subprojects/dbus-gmain/README.md
diff --git a/dbus-gmain.c b/subprojects/dbus-gmain/dbus-gmain.c
index 3b7e596..3b7e596 100644
--- a/dbus-gmain.c
+++ b/subprojects/dbus-gmain/dbus-gmain.c
diff --git a/dbus-gmain/dbus-gmain.h b/subprojects/dbus-gmain/dbus-gmain/dbus-gmain.h
index b8df034..b8df034 100644
--- a/dbus-gmain/dbus-gmain.h
+++ b/subprojects/dbus-gmain/dbus-gmain/dbus-gmain.h
diff --git a/meson.build b/subprojects/dbus-gmain/meson.build
index 09f60b5..09f60b5 100644
--- a/meson.build
+++ b/subprojects/dbus-gmain/meson.build
diff --git a/meson_options.txt b/subprojects/dbus-gmain/meson_options.txt
index ed06753..ed06753 100644
--- a/meson_options.txt
+++ b/subprojects/dbus-gmain/meson_options.txt
diff --git a/tests/30574.c b/subprojects/dbus-gmain/tests/30574.c
index 6fe2b90..6fe2b90 100644
--- a/tests/30574.c
+++ b/subprojects/dbus-gmain/tests/30574.c
diff --git a/tests/meson.build b/subprojects/dbus-gmain/tests/meson.build
index 8344ee2..8344ee2 100644
--- a/tests/meson.build
+++ b/subprojects/dbus-gmain/tests/meson.build
diff --git a/tests/test-thread-client.c b/subprojects/dbus-gmain/tests/test-thread-client.c
index a115d41..a115d41 100644
--- a/tests/test-thread-client.c
+++ b/subprojects/dbus-gmain/tests/test-thread-client.c
diff --git a/tests/test-thread-server.c b/subprojects/dbus-gmain/tests/test-thread-server.c
index df821fb..df821fb 100644
--- a/tests/test-thread-server.c
+++ b/subprojects/dbus-gmain/tests/test-thread-server.c
diff --git a/tests/test-thread.h b/subprojects/dbus-gmain/tests/test-thread.h
index 74371f4..74371f4 100644
--- a/tests/test-thread.h
+++ b/subprojects/dbus-gmain/tests/test-thread.h
diff --git a/tests/use-as-subproject.py b/subprojects/dbus-gmain/tests/use-as-subproject.py
index 3f45c5b..3f45c5b 100644
--- a/tests/use-as-subproject.py
+++ b/subprojects/dbus-gmain/tests/use-as-subproject.py
diff --git a/tests/use-as-subproject/.gitignore b/subprojects/dbus-gmain/tests/use-as-subproject/.gitignore
index 348f6b5..348f6b5 100644
--- a/tests/use-as-subproject/.gitignore
+++ b/subprojects/dbus-gmain/tests/use-as-subproject/.gitignore
diff --git a/tests/use-as-subproject/README b/subprojects/dbus-gmain/tests/use-as-subproject/README
index d0f10fd..d0f10fd 100644
--- a/tests/use-as-subproject/README
+++ b/subprojects/dbus-gmain/tests/use-as-subproject/README
diff --git a/tests/use-as-subproject/meson.build b/subprojects/dbus-gmain/tests/use-as-subproject/meson.build
index 65f651a..65f651a 100644
--- a/tests/use-as-subproject/meson.build
+++ b/subprojects/dbus-gmain/tests/use-as-subproject/meson.build
diff --git a/tests/use-as-subproject/use-dbus-gmain.c b/subprojects/dbus-gmain/tests/use-as-subproject/use-dbus-gmain.c
index 21bf8e9..21bf8e9 100644
--- a/tests/use-as-subproject/use-dbus-gmain.c
+++ b/subprojects/dbus-gmain/tests/use-as-subproject/use-dbus-gmain.c
diff --git a/tests/util.c b/subprojects/dbus-gmain/tests/util.c
index 07da5d9..07da5d9 100644
--- a/tests/util.c
+++ b/subprojects/dbus-gmain/tests/util.c
diff --git a/tests/util.h b/subprojects/dbus-gmain/tests/util.h
index 0d8bfa3..0d8bfa3 100644
--- a/tests/util.h
+++ b/subprojects/dbus-gmain/tests/util.h
diff --git a/test/TestSuitePythonService.service.in b/test/TestSuitePythonService.service.in
new file mode 100644
index 0000000..b9a96df
--- /dev/null
+++ b/test/TestSuitePythonService.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.DBus.TestSuitePythonService
+Exec=/bin/bash -c "@PYTHON@ @G_TEST_SRCDIR@/test/test-service.py"
diff --git a/test/cross-test-client.py b/test/cross-test-client.py
new file mode 100755
index 0000000..4c25499
--- /dev/null
+++ b/test/cross-test-client.py
@@ -0,0 +1,409 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function, unicode_literals
+import logging
+
+try:
+ from gi.repository import GLib
+except ImportError:
+ raise SystemExit(77)
+
+from dbus import (
+ Array, Boolean, Byte, ByteArray, Double, Int16, Int32, Int64,
+ Interface, SessionBus, String, UInt16, UInt32, UInt64)
+import dbus.glib
+
+from crosstest import (
+ CROSS_TEST_BUS_NAME, CROSS_TEST_PATH, INTERFACE_CALLBACK_TESTS,
+ INTERFACE_SIGNAL_TESTS, INTERFACE_SINGLE_TESTS, INTERFACE_TESTS,
+ SignalTestsImpl)
+
+
+logging.basicConfig()
+logging.getLogger().setLevel(1)
+logger = logging.getLogger('cross-test-client')
+
+
+class Client(SignalTestsImpl):
+ fail_id = 0
+ expected = set()
+
+ def quit(self):
+ for x in self.expected:
+ self.fail_id += 1
+ print("%s fail %d" % (x, self.fail_id))
+ s = "report %d: reply to %s didn't arrive" % (self.fail_id, x)
+ print(s)
+ logger.error(s)
+ logger.info("asking server to Exit")
+ Interface(self.obj, INTERFACE_TESTS).Exit(reply_handler=self.quit_reply_handler, error_handler=self.quit_error_handler)
+
+ def quit_reply_handler(self):
+ logger.info("server says it will exit")
+ loop.quit()
+
+ def quit_error_handler(self, e):
+ logger.error("error telling server to quit: %s %s",
+ e.__class__, e)
+ loop.quit()
+
+ @dbus.service.method(INTERFACE_CALLBACK_TESTS, 'qd')
+ def Response(self, input1, input2):
+ logger.info("signal/callback: Response received (%r,%r)",
+ input1, input2)
+ self.expected.discard('%s.Trigger' % INTERFACE_SIGNAL_TESTS)
+ if (input1, input2) != (42, 23):
+ self.fail_id += 1
+ print("%s.Trigger fail %d" %
+ (INTERFACE_SIGNAL_TESTS, self.fail_id))
+ s = ("report %d: expected (42,23), got %r"
+ % (self.fail_id, (input1, input2)))
+ logger.error(s)
+ print(s)
+ else:
+ print("%s.Trigger pass" % INTERFACE_SIGNAL_TESTS)
+ self.quit()
+
+ def assert_method_matches(self, interface, check_fn, check_arg, member,
+ *args):
+ if_obj = Interface(self.obj, interface)
+ method = getattr(if_obj, member)
+ try:
+ real_ret = method(*args)
+ except Exception as e:
+ self.fail_id += 1
+ print("%s.%s fail %d" % (interface, member, self.fail_id))
+ s = ("report %d: %s.%s%r: raised %r \"%s\""
+ % (self.fail_id, interface, member, args, e, e))
+ print(s)
+ logger.error(s)
+ __import__('traceback').print_exc()
+ return
+ try:
+ check_fn(real_ret, check_arg)
+ except Exception as e:
+ self.fail_id += 1
+ print("%s.%s fail %d" % (interface, member, self.fail_id))
+ s = ("report %d: %s.%s%r: %s"
+ % (self.fail_id, interface, member, args, e))
+ print(s)
+ logger.error(s)
+ return
+ print("%s.%s pass" % (interface, member))
+
+ def assert_method_eq(self, interface, ret, member, *args):
+ def equals(real_ret, exp):
+ if real_ret != exp:
+ raise AssertionError('expected %r of class %s, got %r of class %s' % (exp, exp.__class__, real_ret, real_ret.__class__))
+ if real_ret != exp:
+ raise AssertionError('expected %r, got %r' % (exp, real_ret))
+ if not isinstance(exp, (tuple, type(None))):
+ if real_ret.variant_level != getattr(exp, 'variant_level', 0):
+ raise AssertionError('expected variant_level=%d, got %r with level %d'
+ % (getattr(exp, 'variant_level', 0), real_ret,
+ real_ret.variant_level))
+ if isinstance(exp, list) or isinstance(exp, tuple):
+ for i in range(len(exp)):
+ try:
+ equals(real_ret[i], exp[i])
+ except AssertionError as e:
+ if not isinstance(e.args, tuple):
+ e.args = (e.args,)
+ e.args = e.args + ('(at position %d in sequence)' % i,)
+ raise e
+ elif isinstance(exp, dict):
+ for k in exp:
+ try:
+ equals(real_ret[k], exp[k])
+ except AssertionError as e:
+ if not isinstance(e.args, tuple):
+ e.args = (e.args,)
+ e.args = e.args + ('(at key %r in dict)' % k,)
+ raise e
+ self.assert_method_matches(interface, equals, ret, member, *args)
+
+ def assert_InvertMapping_eq(self, interface, expected, member, mapping):
+ def check(real_ret, exp):
+ for key in exp:
+ if key not in real_ret:
+ raise AssertionError('missing key %r' % key)
+ for key in real_ret:
+ if key not in exp:
+ raise AssertionError('unexpected key %r' % key)
+ got = list(real_ret[key])
+ wanted = list(exp[key])
+ got.sort()
+ wanted.sort()
+ if got != wanted:
+ raise AssertionError('expected %r => %r, got %r'
+ % (key, wanted, got))
+ self.assert_method_matches(interface, check, expected, member, mapping)
+
+ def triggered_cb(self, param, sender_path):
+ logger.info("method/signal: Triggered(%r) by %r",
+ param, sender_path)
+ self.expected.discard('%s.Trigger' % INTERFACE_TESTS)
+ if sender_path != '/Where/Ever':
+ self.fail_id += 1
+ print("%s.Trigger fail %d" % (INTERFACE_TESTS, self.fail_id))
+ s = ("report %d: expected signal from /Where/Ever, got %r"
+ % (self.fail_id, sender_path))
+ print(s)
+ logger.error(s)
+ elif param != 42:
+ self.fail_id += 1
+ print("%s.Trigger fail %d" % (INTERFACE_TESTS, self.fail_id))
+ s = ("report %d: expected signal param 42, got %r"
+ % (self.fail_id, param))
+ print(s)
+ logger.error(s)
+ else:
+ print("%s.Trigger pass" % INTERFACE_TESTS)
+
+ def trigger_returned_cb(self):
+ logger.info('method/signal: Trigger() returned')
+ # Callback tests
+ logger.info("signal/callback: Emitting signal to trigger callback")
+ self.expected.add('%s.Trigger' % INTERFACE_SIGNAL_TESTS)
+ self.Trigger(UInt16(42), 23.0)
+ logger.info("signal/callback: Emitting signal returned")
+
+ def run_client(self):
+ bus = SessionBus()
+ obj = bus.get_object(CROSS_TEST_BUS_NAME, CROSS_TEST_PATH)
+ self.obj = obj
+
+ self.run_synchronous_tests(obj)
+
+ # Signal tests
+ logger.info("Binding signal handler for Triggered")
+ # FIXME: doesn't seem to work when going via the Interface method
+ # FIXME: should be possible to ask the proxy object for its
+ # bus name
+ bus.add_signal_receiver(self.triggered_cb, 'Triggered',
+ INTERFACE_SIGNAL_TESTS,
+ CROSS_TEST_BUS_NAME,
+ path_keyword='sender_path')
+ logger.info("method/signal: Triggering signal")
+ self.expected.add('%s.Trigger' % INTERFACE_TESTS)
+ Interface(obj, INTERFACE_TESTS).Trigger(
+ '/Where/Ever', dbus.UInt64(42),
+ reply_handler=self.trigger_returned_cb,
+ error_handler=self.trigger_error_handler)
+
+ def trigger_error_handler(self, e):
+ logger.error("method/signal: %s %s", e.__class__, e)
+ Interface(self.obj, INTERFACE_TESTS).Exit()
+ self.quit()
+
+ def run_synchronous_tests(self, obj):
+ # We can't test that coercion works correctly unless the server has
+ # sent us introspection data. Java doesn't :-/
+ have_signatures = True
+
+ # "Single tests"
+ if have_signatures:
+ self.assert_method_eq(INTERFACE_SINGLE_TESTS, 6, 'Sum', [1, 2, 3])
+ self.assert_method_eq(INTERFACE_SINGLE_TESTS, 6, 'Sum',
+ [b'\x01', b'\x02', b'\x03'])
+ self.assert_method_eq(INTERFACE_SINGLE_TESTS, 6, 'Sum', [Byte(1), Byte(2), Byte(3)])
+ self.assert_method_eq(INTERFACE_SINGLE_TESTS, 6, 'Sum', ByteArray(b'\x01\x02\x03'))
+
+ # Main tests
+ self.assert_method_eq(INTERFACE_TESTS, String('foo', variant_level=1), 'Identity', String('foo'))
+ self.assert_method_eq(INTERFACE_TESTS, Byte(42, variant_level=1), 'Identity', Byte(42))
+ self.assert_method_eq(INTERFACE_TESTS, Byte(42, variant_level=23), 'Identity', Byte(42, variant_level=23))
+ self.assert_method_eq(INTERFACE_TESTS, Double(42.5, variant_level=1), 'Identity', 42.5)
+ self.assert_method_eq(INTERFACE_TESTS, Double(-42.5, variant_level=1), 'Identity', -42.5)
+
+ if have_signatures:
+ self.assert_method_eq(INTERFACE_TESTS, String('foo', variant_level=1), 'Identity', 'foo')
+ self.assert_method_eq(INTERFACE_TESTS, Byte(42, variant_level=1), 'Identity', Byte(42))
+ self.assert_method_eq(INTERFACE_TESTS, Double(42.5, variant_level=1), 'Identity', Double(42.5))
+ self.assert_method_eq(INTERFACE_TESTS, Double(-42.5, variant_level=1), 'Identity', -42.5)
+
+ for i in (0, 42, 255):
+ self.assert_method_eq(INTERFACE_TESTS, Byte(i), 'IdentityByte', Byte(i))
+ for i in (True, False):
+ self.assert_method_eq(INTERFACE_TESTS, i, 'IdentityBool', i)
+
+ for i in (-0x8000, 0, 42, 0x7fff):
+ self.assert_method_eq(INTERFACE_TESTS, i, 'IdentityInt16', Int16(i))
+ for i in (0, 42, 0xffff):
+ self.assert_method_eq(INTERFACE_TESTS, i, 'IdentityUInt16', UInt16(i))
+ for i in (-0x7fffffff-1, 0, 42, 0x7fffffff):
+ self.assert_method_eq(INTERFACE_TESTS, i, 'IdentityInt32', Int32(i))
+ for i in (0, 42, 0xffffffff):
+ self.assert_method_eq(INTERFACE_TESTS, i, 'IdentityUInt32', UInt32(i))
+ MANY = 1
+ for n in (0x8000, 0x10000, 0x10000, 0x10000):
+ MANY *= n
+ for i in (-MANY, 0, 42, MANY-1):
+ self.assert_method_eq(INTERFACE_TESTS, i, 'IdentityInt64', Int64(i))
+ for i in (0, 42, 2*MANY - 1):
+ self.assert_method_eq(INTERFACE_TESTS, i, 'IdentityUInt64', UInt64(i))
+
+ self.assert_method_eq(INTERFACE_TESTS, 42.3, 'IdentityDouble', 42.3)
+ for i in ('', 'foo'):
+ self.assert_method_eq(INTERFACE_TESTS, i, 'IdentityString', i)
+ for i in ('\xa9', b'\xc2\xa9'):
+ self.assert_method_eq(INTERFACE_TESTS, '\xa9', 'IdentityString', i)
+
+ if have_signatures:
+ self.assert_method_eq(INTERFACE_TESTS, Byte(0x42),
+ 'IdentityByte', b'\x42')
+ self.assert_method_eq(INTERFACE_TESTS, True, 'IdentityBool', 42)
+ self.assert_method_eq(INTERFACE_TESTS, 42, 'IdentityInt16', 42)
+ self.assert_method_eq(INTERFACE_TESTS, 42, 'IdentityUInt16', 42)
+ self.assert_method_eq(INTERFACE_TESTS, 42, 'IdentityInt32', 42)
+ self.assert_method_eq(INTERFACE_TESTS, 42, 'IdentityUInt32', 42)
+ self.assert_method_eq(INTERFACE_TESTS, 42, 'IdentityInt64', 42)
+ self.assert_method_eq(INTERFACE_TESTS, 42, 'IdentityUInt64', 42)
+ self.assert_method_eq(INTERFACE_TESTS, 42.0, 'IdentityDouble', 42)
+
+ self.assert_method_eq(INTERFACE_TESTS, [Byte(b'\x01', variant_level=1),
+ Byte(b'\x02', variant_level=1),
+ Byte(b'\x03', variant_level=1)],
+ 'IdentityArray',
+ Array([Byte(b'\x01'),
+ Byte(b'\x02'),
+ Byte(b'\x03')],
+ signature='v'))
+
+ self.assert_method_eq(INTERFACE_TESTS, [Int32(1, variant_level=1),
+ Int32(2, variant_level=1),
+ Int32(3, variant_level=1)],
+ 'IdentityArray',
+ Array([Int32(1),
+ Int32(2),
+ Int32(3)],
+ signature='v'))
+ self.assert_method_eq(INTERFACE_TESTS, [String('a', variant_level=1),
+ String('b', variant_level=1),
+ String('c', variant_level=1)],
+ 'IdentityArray',
+ Array([String('a'),
+ String('b'),
+ String('c')],
+ signature='v'))
+
+ if have_signatures:
+ self.assert_method_eq(INTERFACE_TESTS, [Byte(b'\x01', variant_level=1),
+ Byte(b'\x02', variant_level=1),
+ Byte(b'\x03', variant_level=1)],
+ 'IdentityArray',
+ ByteArray(b'\x01\x02\x03'))
+ self.assert_method_eq(INTERFACE_TESTS, [Int32(1, variant_level=1),
+ Int32(2, variant_level=1),
+ Int32(3, variant_level=1)],
+ 'IdentityArray',
+ [Int32(1),
+ Int32(2),
+ Int32(3)])
+ self.assert_method_eq(INTERFACE_TESTS, [String('a', variant_level=1),
+ String('b', variant_level=1),
+ String('c', variant_level=1)],
+ 'IdentityArray',
+ ['a','b','c'])
+
+ self.assert_method_eq(INTERFACE_TESTS,
+ [Byte(1), Byte(2), Byte(3)],
+ 'IdentityByteArray',
+ ByteArray(b'\x01\x02\x03'))
+ if have_signatures:
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3],
+ 'IdentityByteArray',
+ [b'\x01', b'\x02', b'\x03'])
+ self.assert_method_eq(INTERFACE_TESTS, [False,True], 'IdentityBoolArray', [False,True])
+ if have_signatures:
+ self.assert_method_eq(INTERFACE_TESTS, [False,True,True], 'IdentityBoolArray', [0,1,2])
+
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityInt16Array', [Int16(1),Int16(2),Int16(3)])
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityUInt16Array', [UInt16(1),UInt16(2),UInt16(3)])
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityInt32Array', [Int32(1),Int32(2),Int32(3)])
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityUInt32Array', [UInt32(1),UInt32(2),UInt32(3)])
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityInt64Array', [Int64(1),Int64(2),Int64(3)])
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityUInt64Array', [UInt64(1),UInt64(2),UInt64(3)])
+
+ if have_signatures:
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityInt16Array', [1,2,3])
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityUInt16Array', [1,2,3])
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityInt32Array', [1,2,3])
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityUInt32Array', [1,2,3])
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityInt64Array', [1,2,3])
+ self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityUInt64Array', [1,2,3])
+
+ self.assert_method_eq(INTERFACE_TESTS, [1.0,2.5,3.1], 'IdentityDoubleArray', [1.0,2.5,3.1])
+ if have_signatures:
+ self.assert_method_eq(INTERFACE_TESTS, [1.0,2.5,3.1], 'IdentityDoubleArray', [1,2.5,3.1])
+ self.assert_method_eq(INTERFACE_TESTS, ['a','b','c'], 'IdentityStringArray', ['a','b','c'])
+ self.assert_method_eq(INTERFACE_TESTS, 6, 'Sum', [Int32(1),Int32(2),Int32(3)])
+ if have_signatures:
+ self.assert_method_eq(INTERFACE_TESTS, 6, 'Sum', [1,2,3])
+
+ self.assert_InvertMapping_eq(INTERFACE_TESTS, {'fps': ['unreal', 'quake'], 'rts': ['warcraft']}, 'InvertMapping', {'unreal': 'fps', 'quake': 'fps', 'warcraft': 'rts'})
+
+ self.assert_method_eq(INTERFACE_TESTS, ('a', 1, 2), 'DeStruct', ('a', UInt32(1), Int16(2)))
+ self.assert_method_eq(INTERFACE_TESTS, Array([String('x', variant_level=1)]),
+ 'Primitize', [String('x', variant_level=1)])
+ self.assert_method_eq(INTERFACE_TESTS, Array([String('x', variant_level=1)]),
+ 'Primitize', [String('x', variant_level=23)])
+ self.assert_method_eq(INTERFACE_TESTS,
+ Array([String('x', variant_level=1),
+ Byte(1, variant_level=1),
+ Byte(2, variant_level=1)]),
+ 'Primitize',
+ Array([String('x'), Byte(1), Byte(2)],
+ signature='v'))
+ self.assert_method_eq(INTERFACE_TESTS,
+ Array([String('x', variant_level=1),
+ Byte(1, variant_level=1),
+ Byte(2, variant_level=1)]),
+ 'Primitize',
+ Array([String('x'), Array([Byte(1), Byte(2)])],
+ signature='v'))
+ self.assert_method_eq(INTERFACE_TESTS, Boolean(False), 'Invert', True)
+ self.assert_method_eq(INTERFACE_TESTS, Boolean(True), 'Invert', False)
+ if have_signatures:
+ self.assert_method_eq(INTERFACE_TESTS, Boolean(False), 'Invert', 42)
+ self.assert_method_eq(INTERFACE_TESTS, Boolean(True), 'Invert', 0)
+
+
+if __name__ == '__main__':
+ # FIXME: should be possible to export objects without a bus name
+ if 0:
+ client = Client(dbus.SessionBus(), '/Client')
+ else:
+ # the Java cross test's interpretation is that the client should be
+ # at /Test too
+ client = Client(dbus.SessionBus(), '/Test')
+ GLib.idle_add(client.run_client)
+
+ loop = GLib.MainLoop()
+ logger.info("running...")
+ loop.run()
+ logger.info("main loop exited.")
diff --git a/test/cross-test-server.py b/test/cross-test-server.py
new file mode 100755
index 0000000..96a315a
--- /dev/null
+++ b/test/cross-test-server.py
@@ -0,0 +1,334 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+import logging
+
+try:
+ from gi.repository import GLib
+except ImportError:
+ raise SystemExit(77)
+
+import dbus.glib
+from dbus import SessionBus
+from dbus.service import BusName
+
+from crosstest import (
+ CROSS_TEST_BUS_NAME, CROSS_TEST_PATH, INTERFACE_CALLBACK_TESTS,
+ INTERFACE_SIGNAL_TESTS, INTERFACE_SINGLE_TESTS, INTERFACE_TESTS,
+ SignalTestsImpl)
+
+
+logging.basicConfig()
+logging.getLogger().setLevel(1)
+logger = logging.getLogger('cross-test-server')
+
+
+class VerboseSet(set):
+ def add(self, thing):
+ print('%s ok' % thing)
+ set.add(self, thing)
+
+
+objects = {}
+
+
+tested_things = VerboseSet()
+testable_things = [
+ INTERFACE_SINGLE_TESTS + '.Sum',
+ INTERFACE_TESTS + '.Identity',
+ INTERFACE_TESTS + '.IdentityByte',
+ INTERFACE_TESTS + '.IdentityBool',
+ INTERFACE_TESTS + '.IdentityInt16',
+ INTERFACE_TESTS + '.IdentityUInt16',
+ INTERFACE_TESTS + '.IdentityInt32',
+ INTERFACE_TESTS + '.IdentityUInt32',
+ INTERFACE_TESTS + '.IdentityInt64',
+ INTERFACE_TESTS + '.IdentityUInt64',
+ INTERFACE_TESTS + '.IdentityDouble',
+ INTERFACE_TESTS + '.IdentityString',
+ INTERFACE_TESTS + '.IdentityArray',
+ INTERFACE_TESTS + '.IdentityByteArray',
+ INTERFACE_TESTS + '.IdentityBoolArray',
+ INTERFACE_TESTS + '.IdentityInt16Array',
+ INTERFACE_TESTS + '.IdentityUInt16Array',
+ INTERFACE_TESTS + '.IdentityInt32Array',
+ INTERFACE_TESTS + '.IdentityUInt32Array',
+ INTERFACE_TESTS + '.IdentityInt64Array',
+ INTERFACE_TESTS + '.IdentityUInt64Array',
+ INTERFACE_TESTS + '.IdentityDoubleArray',
+ INTERFACE_TESTS + '.IdentityStringArray',
+ INTERFACE_TESTS + '.Sum',
+ INTERFACE_TESTS + '.InvertMapping',
+ INTERFACE_TESTS + '.DeStruct',
+ INTERFACE_TESTS + '.Primitize',
+ INTERFACE_TESTS + '.Trigger',
+ INTERFACE_TESTS + '.Exit',
+ INTERFACE_TESTS + '.Invert',
+ INTERFACE_SIGNAL_TESTS + '.Trigger',
+]
+
+
+class SingleTestsImpl(dbus.service.Object):
+
+ @dbus.service.method(INTERFACE_SINGLE_TESTS, 'ay', 'u')
+ def Sum(self, input):
+ tested_things.add(INTERFACE_SINGLE_TESTS + '.Sum')
+ u = sum(input)
+ logger.info('Sum of %r is %r', input, u)
+ return u
+
+
+class TestsImpl(dbus.service.Object):
+
+ def __init__(self, bus_name, service_path, exit_fn):
+ self._exit_fn = exit_fn
+ dbus.service.Object.__init__(self, bus_name, service_path)
+
+ @dbus.service.method(INTERFACE_TESTS, 'v', 'v')
+ def Identity(self, input):
+ tested_things.add(INTERFACE_TESTS + '.Identity')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'y', 'y')
+ def IdentityByte(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityByte')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'b', 'b')
+ def IdentityBool(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityBool')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'n', 'n')
+ def IdentityInt16(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityInt16')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'q', 'q')
+ def IdentityUInt16(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityUInt16')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'i', 'i')
+ def IdentityInt32(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityInt32')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'u', 'u')
+ def IdentityUInt32(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityUInt32')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'x', 'x')
+ def IdentityInt64(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityInt64')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 't', 't')
+ def IdentityUInt64(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityUInt64')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'd', 'd')
+ def IdentityDouble(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityDouble')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 's', 's')
+ def IdentityString(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityString')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'av', 'av')
+ def IdentityArray(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityArray')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'ay', 'ay')
+ def IdentityByteArray(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityByteArray')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'ab', 'ab')
+ def IdentityBoolArray(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityBoolArray')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'an', 'an')
+ def IdentityInt16Array(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityInt16Array')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'aq', 'aq')
+ def IdentityUInt16Array(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityUInt16Array')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'ai', 'ai')
+ def IdentityInt32Array(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityInt32Array')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'au', 'au')
+ def IdentityUInt32Array(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityUInt32Array')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'ax', 'ax')
+ def IdentityInt64Array(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityInt64Array')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'at', 'at')
+ def IdentityUInt64Array(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityUInt64Array')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'ad', 'ad')
+ def IdentityDoubleArray(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityDoubleArray')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'as', 'as')
+ def IdentityStringArray(self, input):
+ tested_things.add(INTERFACE_TESTS + '.IdentityStringArray')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'ai', 'x')
+ def Sum(self, input):
+ tested_things.add(INTERFACE_TESTS + '.Sum')
+ x = sum(input)
+ logger.info('Sum of %r is %r', input, x)
+ return x
+
+ @dbus.service.method(INTERFACE_TESTS, 'a{ss}', 'a{sas}')
+ def InvertMapping(self, input):
+ tested_things.add(INTERFACE_TESTS + '.InvertMapping')
+ output = dbus.Dictionary({})
+ for k, v in input.items():
+ output.setdefault(v, []).append(k)
+ return output
+
+ @dbus.service.method(INTERFACE_TESTS, '(sun)', 'sun')
+ def DeStruct(self, input):
+ tested_things.add(INTERFACE_TESTS + '.DeStruct')
+ return input
+
+ @dbus.service.method(INTERFACE_TESTS, 'v', 'av')
+ def Primitize(self, input):
+ tested_things.add(INTERFACE_TESTS + '.Primitize')
+ return list(self.primitivize_helper(input))
+
+ def primitivize_helper(self, input):
+ if (isinstance(input, tuple) or isinstance(input, dbus.Struct)
+ or isinstance(input, list) or isinstance(input, dbus.Array)):
+ for x in input:
+ for y in self.primitivize_helper(x):
+ yield y
+ elif isinstance(input, dbus.ByteArray):
+ for x in input:
+ yield dbus.Byte(ord(x))
+ elif isinstance(input, dict) or isinstance(input, dbus.Dictionary):
+ for x in input:
+ for y in self.primitivize_helper(x):
+ yield y
+ for y in self.primitivize_helper(input[x]):
+ yield y
+ elif input.variant_level > 0:
+ yield input.__class__(input)
+ else:
+ yield input
+
+ @dbus.service.method(INTERFACE_TESTS, 'b', 'b')
+ def Invert(self, input):
+ tested_things.add(INTERFACE_TESTS + '.Invert')
+ return not input
+
+ @dbus.service.method(INTERFACE_TESTS, 'st', '',
+ connection_keyword='conn',
+ async_callbacks=('reply_cb', 'error_cb'))
+ def Trigger(self, object, parameter, conn=None, reply_cb=None,
+ error_cb=None):
+ assert isinstance(object, str)
+ logger.info('method/signal: client wants me to emit Triggered(%r) from %r', parameter, object)
+ tested_things.add(INTERFACE_TESTS + '.Trigger')
+ GLib.idle_add(lambda: self.emit_Triggered_from(conn, object,
+ parameter,
+ reply_cb))
+
+ def emit_Triggered_from(self, conn, object, parameter, reply_cb):
+ assert isinstance(object, str)
+ logger.info('method/signal: Emitting Triggered(%r) from %r', parameter, object)
+ obj = objects.get(object, None)
+ if obj is None:
+ obj = SignalTestsImpl(conn, object)
+ objects[object] = obj
+ obj.Triggered(parameter)
+ logger.info('method/signal: Emitted Triggered')
+ reply_cb()
+ logger.info('method/signal: Sent reply for Tests.Trigger()')
+
+ @dbus.service.method(INTERFACE_TESTS, '', '')
+ def Exit(self):
+ logger.info('client wants me to Exit')
+ tested_things.add(INTERFACE_TESTS + '.Exit')
+ for x in testable_things:
+ if x not in tested_things:
+ print('%s untested' % x)
+ logger.info('will quit when idle')
+ GLib.idle_add(self._exit_fn)
+
+
+class Server(SingleTestsImpl, TestsImpl, SignalTestsImpl):
+
+ def triggered_by_client(self, parameter1, parameter2, sender, sender_path):
+ # Called when the client emits TestSignals.Trigger from any object.
+ logger.info('signal/callback: Triggered by client (%s:%s): (%r,%r)', sender, sender_path, parameter1, parameter2)
+ tested_things.add(INTERFACE_SIGNAL_TESTS + '.Trigger')
+ dbus.Interface(dbus.SessionBus().get_object(sender, sender_path),
+ INTERFACE_CALLBACK_TESTS).Response(parameter1, parameter2)
+ logger.info('signal/callback: Sent Response')
+
+
+
+if __name__ == '__main__':
+ bus = SessionBus()
+ bus_name = BusName(CROSS_TEST_BUS_NAME, bus=bus)
+ loop = GLib.MainLoop()
+ obj = Server(bus_name, CROSS_TEST_PATH, loop.quit)
+ objects[CROSS_TEST_PATH] = obj
+ bus.add_signal_receiver(obj.triggered_by_client,
+ signal_name='Trigger',
+ dbus_interface=INTERFACE_SIGNAL_TESTS,
+ named_service=None,
+ path=None,
+ sender_keyword='sender',
+ path_keyword='sender_path')
+
+ logger.info("running...")
+ loop.run()
+ logger.info("main loop exited.")
diff --git a/test/crosstest.py b/test/crosstest.py
new file mode 100644
index 0000000..434273c
--- /dev/null
+++ b/test/crosstest.py
@@ -0,0 +1,46 @@
+# Shared code for the cross-test.
+
+# Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import dbus.service
+
+INTERFACE_SINGLE_TESTS = 'org.freedesktop.DBus.Binding.SingleTests'
+INTERFACE_TESTS = 'org.freedesktop.DBus.Binding.Tests'
+INTERFACE_SIGNAL_TESTS = 'org.freedesktop.DBus.Binding.TestSignals'
+INTERFACE_CALLBACK_TESTS = 'org.freedesktop.DBus.Binding.TestCallbacks'
+
+CROSS_TEST_PATH = '/Test'
+CROSS_TEST_BUS_NAME = 'org.freedesktop.DBus.Binding.TestServer'
+
+
+# Exported by both the client and the server
+class SignalTestsImpl(dbus.service.Object):
+ @dbus.service.signal(INTERFACE_SIGNAL_TESTS, 't')
+ def Triggered(self, parameter):
+ pass
+
+ @dbus.service.signal(INTERFACE_SIGNAL_TESTS, 'qd')
+ def Trigger(self, parameter1, parameter2):
+ pass
diff --git a/test/dbus_py_test.c b/test/dbus_py_test.c
new file mode 100644
index 0000000..6ff8547
--- /dev/null
+++ b/test/dbus_py_test.c
@@ -0,0 +1,138 @@
+/* Test fixtures for dbus-python, based on _dbus_glib_bindings.
+ *
+ * Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <Python.h>
+#include <dbus/dbus-python.h>
+
+PyMODINIT_FUNC PyInit_dbus_py_test(void);
+
+#if defined(__GNUC__)
+# if __GNUC__ >= 3
+# define UNUSED __attribute__((__unused__))
+# else
+# define UNUSED /*nothing*/
+# endif
+#else
+# define UNUSED /*nothing*/
+#endif
+
+static dbus_bool_t
+dbus_py_test_set_up_conn(DBusConnection *conn UNUSED, void *data UNUSED)
+{
+ PyErr_SetString(PyExc_ValueError, "Dummy error from UnusableMainLoop");
+ return 0;
+}
+
+static dbus_bool_t
+dbus_py_test_set_up_srv(DBusServer *srv UNUSED, void *data UNUSED)
+{
+ PyErr_SetString(PyExc_ValueError, "Dummy error from UnusableMainLoop");
+ return 0;
+}
+
+static void
+dbus_py_test_free(void *data UNUSED)
+{
+}
+
+static PyObject *
+dbus_test_native_mainloop(void)
+{
+ PyObject *loop = DBusPyNativeMainLoop_New4(dbus_py_test_set_up_conn,
+ dbus_py_test_set_up_srv,
+ dbus_py_test_free,
+ NULL);
+ return loop;
+}
+
+static PyObject *
+UnusableMainLoop (PyObject *always_null UNUSED, PyObject *args, PyObject *kwargs)
+{
+ PyObject *mainloop, *function, *result;
+ int set_as_default = 0;
+ static char *argnames[] = {"set_as_default", NULL};
+
+ if (PyTuple_Size(args) != 0) {
+ PyErr_SetString(PyExc_TypeError, "UnusableMainLoop() takes no "
+ "positional arguments");
+ return NULL;
+ }
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", argnames,
+ &set_as_default)) {
+ return NULL;
+ }
+
+ mainloop = dbus_test_native_mainloop();
+ if (mainloop && set_as_default) {
+ if (!_dbus_bindings_module) {
+ PyErr_SetString(PyExc_ImportError, "_dbus_bindings not imported");
+ Py_CLEAR(mainloop);
+ return NULL;
+ }
+ function = PyObject_GetAttrString(_dbus_bindings_module,
+ "set_default_main_loop");
+ if (!function) {
+ Py_CLEAR(mainloop);
+ return NULL;
+ }
+ result = PyObject_CallFunctionObjArgs(function, mainloop, NULL);
+ Py_CLEAR(function);
+ if (!result) {
+ Py_CLEAR(mainloop);
+ return NULL;
+ }
+ }
+ return mainloop;
+}
+
+static PyMethodDef module_functions[] = {
+ {"UnusableMainLoop", (PyCFunction) (void (*)(void))UnusableMainLoop,
+ METH_VARARGS|METH_KEYWORDS, "Return a main loop that fails to attach"},
+ {NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC
+PyInit_dbus_py_test(void)
+{
+ static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ "dbus_py_test", /* m_name */
+ NULL, /* m_doc */
+ -1, /* m_size */
+ module_functions, /* m_methods */
+ NULL, /* m_reload */
+ NULL, /* m_traverse */
+ NULL, /* m_clear */
+ NULL /* m_free */
+ };
+ if (import_dbus_bindings("dbus_py_test") < 0)
+ return NULL;
+
+ return PyModule_Create(&moduledef);
+}
+
+/* vim:set ft=c cino< sw=4 sts=4 et: */
diff --git a/test/dbus_test_utils.py b/test/dbus_test_utils.py
new file mode 100644
index 0000000..a7a6619
--- /dev/null
+++ b/test/dbus_test_utils.py
@@ -0,0 +1,53 @@
+# Copyright 2020 Simon McVittie
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+
+import unittest
+
+try:
+ from tap.runner import TAPTestRunner
+except ImportError:
+ TAPTestRunner = None # type: ignore
+
+def main():
+ # type: (...) -> None
+ if TAPTestRunner is not None:
+ runner = TAPTestRunner()
+ runner.set_stream(True)
+ unittest.main(testRunner=runner)
+ else:
+ # You thought pycotap was a minimal TAP implementation?
+ print('1..1')
+ program = unittest.main(exit=False)
+ if program.result.wasSuccessful():
+ print(
+ 'ok 1 - %r (tap module not available)'
+ % program.result
+ )
+ else:
+ print(
+ 'not ok 1 - %r (tap module not available)'
+ % program.result
+ )
diff --git a/test/import-repeatedly.c b/test/import-repeatedly.c
new file mode 100644
index 0000000..457104d
--- /dev/null
+++ b/test/import-repeatedly.c
@@ -0,0 +1,29 @@
+/* Regression test for https://bugs.freedesktop.org/show_bug.cgi?id=23831 */
+/*
+ * Copyright 2010-2016 Collabora Ltd.
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <stdio.h>
+
+#include <Python.h>
+
+int main(void)
+{
+ int i;
+
+ puts("1..1");
+
+ for (i = 0; i < 100; ++i) {
+ Py_Initialize();
+ if (PyRun_SimpleString("import dbus\n") != 0) {
+ puts("not ok 1 - there was an exception");
+ return 1;
+ }
+ Py_Finalize();
+ }
+
+ puts("ok 1 - was able to import dbus 100 times");
+
+ return 0;
+}
diff --git a/test/run-test.sh b/test/run-test.sh
new file mode 100755
index 0000000..97c1195
--- /dev/null
+++ b/test/run-test.sh
@@ -0,0 +1,145 @@
+#! /bin/bash
+
+# Copyright (C) 2006 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2006-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+set -e
+
+failed=
+test_num=0
+
+echo "# DBUS_TOP_SRCDIR=$DBUS_TOP_SRCDIR"
+echo "# DBUS_TOP_BUILDDIR=$DBUS_TOP_BUILDDIR"
+echo "# PYTHONPATH=$PYTHONPATH"
+echo "# PYTHON=${PYTHON:=python}"
+
+if ! [ -d "$DBUS_TEST_TMPDIR" ]; then
+ DBUS_TEST_TMPDIR="$(mktemp -d)"
+ if ! [ -d "$DBUS_TEST_TMPDIR" ]; then
+ echo "Bail out! Failed to create temporary directory (install mktemp?)"
+ exit 1
+ fi
+fi
+
+if ! "$PYTHON" -c 'from gi.repository import GLib'; then
+ echo "1..0 # SKIP could not import python-gi"
+ exit 0
+fi
+
+ok () {
+ test_num=$(( $test_num + 1 ))
+ echo "ok $test_num - $*"
+}
+
+not_ok () {
+ test_num=$(( $test_num + 1 ))
+ echo "not ok $test_num - $*"
+}
+
+skip () {
+ test_num=$(( $test_num + 1 ))
+ echo "ok $test_num # SKIP - $*"
+}
+
+dbus-monitor > "$DBUS_TEST_TMPDIR"/monitor.log &
+
+#echo "running the examples"
+
+#$PYTHON "$DBUS_TOP_SRCDIR"/examples/example-service.py &
+#$PYTHON "$DBUS_TOP_SRCDIR"/examples/example-signal-emitter.py &
+#$PYTHON "$DBUS_TOP_SRCDIR"/examples/list-system-services.py --session ||
+# die "list-system-services.py --session failed!"
+#$PYTHON "$DBUS_TOP_SRCDIR"/examples/example-async-client.py ||
+# die "example-async-client failed!"
+#$PYTHON "$DBUS_TOP_SRCDIR"/examples/example-client.py --exit-service ||
+# die "example-client failed!"
+#$PYTHON "$DBUS_TOP_SRCDIR"/examples/example-signal-recipient.py --exit-service ||
+# die "example-signal-recipient failed!"
+
+echo "# running cross-test (for better diagnostics use mjj29's dbus-test)"
+
+$PYTHON "$DBUS_TOP_SRCDIR"/test/cross-test-server.py > "$DBUS_TEST_TMPDIR"/cross-server.log &
+cross_test_server_pid="$!"
+
+$PYTHON "$DBUS_TOP_SRCDIR"/test/wait-for-name.py org.freedesktop.DBus.Binding.TestServer >&2
+
+e=0
+$PYTHON "$DBUS_TOP_SRCDIR"/test/cross-test-client.py > "$DBUS_TEST_TMPDIR"/cross-client.log || e=$?
+echo "# test-client exit status: $e"
+
+if test "$e" = 77; then
+ skip "cross-test-client exited $e, marking as skipped"
+elif grep . "$DBUS_TEST_TMPDIR"/cross-client.log >/dev/null; then
+ ok "cross-test-client produced some output"
+else
+ not_ok "cross-test-client produced no output"
+fi
+
+if test "$e" = 77; then
+ skip "test-client exited $e, marking as skipped"
+elif grep . "$DBUS_TEST_TMPDIR"/cross-server.log >/dev/null; then
+ ok "cross-test-server produced some output"
+else
+ not_ok "cross-test-server produced no output"
+fi
+
+if grep fail "$DBUS_TEST_TMPDIR"/cross-client.log >&2; then
+ not_ok "cross-client reported failures"
+else
+ ok "cross-test client reported no failures"
+fi
+
+if grep untested "$DBUS_TEST_TMPDIR"/cross-server.log; then
+ not_ok "cross-server reported untested functions"
+else
+ ok "cross-test server reported no untested functions"
+fi
+
+echo "# waiting for cross-test server to exit"
+if wait "$cross_test_server_pid"; then
+ ok "cross-test server: exit status 0"
+else
+ not_ok "cross-test server: exit status $?"
+fi
+
+echo "# ==== client log ===="
+cat "$DBUS_TEST_TMPDIR"/cross-client.log | sed -e 's/^/# /'
+echo "# ==== end ===="
+
+echo "# ==== server log ===="
+cat "$DBUS_TEST_TMPDIR"/cross-server.log | sed -e 's/^/# /'
+echo "# ==== end ===="
+
+rm -f "$DBUS_TEST_TMPDIR"/test-service.log
+rm -f "$DBUS_TEST_TMPDIR"/cross-client.log
+rm -f "$DBUS_TEST_TMPDIR"/cross-server.log
+rm -f "$DBUS_TEST_TMPDIR"/monitor.log
+
+echo "1..$test_num"
+
+if test -n "$failed"; then
+ exit 1
+fi
+exit 0
diff --git a/test/test-client.py b/test/test-client.py
new file mode 100755
index 0000000..3b1114d
--- /dev/null
+++ b/test/test-client.py
@@ -0,0 +1,659 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2004 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+import os
+import unittest
+import time
+import logging
+import weakref
+
+import dbus
+import _dbus_bindings
+import dbus.glib
+import dbus.lowlevel
+import dbus.service
+import dbus_test_utils
+
+try:
+ from gi.repository import GLib
+except ImportError:
+ print('1..0 # SKIP cannot import GLib')
+ raise SystemExit(0)
+
+logging.basicConfig()
+
+if 'DBUS_TEST_UNINSTALLED' in os.environ:
+ builddir = os.path.normpath(os.environ["DBUS_TOP_BUILDDIR"])
+ pydir = os.path.normpath(os.environ["DBUS_TOP_SRCDIR"])
+ pkg = dbus.__file__
+
+ if not pkg.startswith(pydir):
+ raise Exception("DBus modules (%s) are not being picked up from the "
+ "package" % pkg)
+
+ if not _dbus_bindings.__file__.startswith(builddir):
+ raise Exception("DBus modules (%s) are not being picked up from the "
+ "package" % _dbus_bindings.__file__)
+
+test_types_vals = [1, 12323231, 3.14159265, 99999999.99,
+ "dude", "123", "What is all the fuss about?", "gob@gob.com",
+ '\\u310c\\u310e\\u3114', '\\u0413\\u0414\\u0415',
+ '\\u2200software \\u2203crack', '\\xf4\\xe5\\xe8',
+ [1,2,3], ["how", "are", "you"], [1.23,2.3], [1], ["Hello"],
+ (1,2,3), (1,), (1,"2",3), ("2", "what"), ("you", 1.2),
+ {1:"a", 2:"b"}, {"a":1, "b":2}, #{"a":(1,"B")},
+ {1:1.1, 2:2.2}, [[1,2,3],[2,3,4]], [["a","b"],["c","d"]],
+ True, False,
+ dbus.Int16(-10), dbus.UInt16(10), 'SENTINEL',
+ #([1,2,3],"c", 1.2, ["a","b","c"], {"a": (1,"v"), "b": (2,"d")})
+ ]
+
+NAME = "org.freedesktop.DBus.TestSuitePythonService"
+IFACE = "org.freedesktop.DBus.TestSuiteInterface"
+OBJECT = "/org/freedesktop/DBus/TestSuitePythonObject"
+
+# A random string that we should not transmit on the bus as a result of
+# the NO_REPLY flag
+SHOULD_NOT_HAPPEN = u'a1c04a41-cf98-4923-8487-ddaeeb3f02d1'
+
+
+class TestDBusBindings(unittest.TestCase):
+ def setUp(self):
+ self.bus = dbus.SessionBus()
+ self.remote_object = self.bus.get_object(NAME, OBJECT)
+ self.remote_object_follow = self.bus.get_object(NAME, OBJECT,
+ follow_name_owner_changes=True)
+ self.iface = dbus.Interface(self.remote_object, IFACE)
+
+ def testGObject(self):
+ print("Testing ExportedGObject... ", end='')
+ remote_gobject = self.bus.get_object(NAME, OBJECT + '/GObject')
+ iface = dbus.Interface(remote_gobject, IFACE)
+ print("introspection, ", end='')
+ remote_gobject.Introspect(dbus_interface=dbus.INTROSPECTABLE_IFACE)
+ print("method call, ", end='')
+ self.assertEqual(iface.Echo('123'), '123')
+ print("... OK")
+
+ def testWeakRefs(self):
+ # regression test for Sugar crash caused by smcv getting weak refs
+ # wrong - pre-bugfix, this would segfault
+ bus = dbus.SessionBus(private=True)
+ ref = weakref.ref(bus)
+ self.assertTrue(ref() is bus)
+ del bus
+ self.assertTrue(ref() is None)
+
+ def testInterfaceKeyword(self):
+ #test dbus_interface parameter
+ print(self.remote_object.Echo("dbus_interface on Proxy test Passed",
+ dbus_interface = IFACE))
+ print(self.iface.Echo("dbus_interface on Interface test Passed",
+ dbus_interface = IFACE))
+ self.assertTrue(True)
+
+ def testGetDBusMethod(self):
+ self.assertEqual(self.iface.get_dbus_method('AcceptListOfByte')(b'\1\2\3'), [1,2,3])
+ self.assertEqual(self.remote_object.get_dbus_method('AcceptListOfByte', dbus_interface=IFACE)(b'\1\2\3'), [1,2,3])
+
+ def testCallingConventionOptions(self):
+ self.assertEqual(self.iface.AcceptListOfByte(b'\1\2\3'), [1,2,3])
+ self.assertEqual(self.iface.AcceptListOfByte(b'\1\2\3', byte_arrays=True), b'\1\2\3')
+ self.assertEqual(self.iface.AcceptByteArray(b'\1\2\3'), [1,2,3])
+ self.assertEqual(self.iface.AcceptByteArray(b'\1\2\3', byte_arrays=True), b'\1\2\3')
+ self.assertTrue(isinstance(self.iface.AcceptUnicodeString('abc'),
+ str))
+ self.assertTrue(isinstance(self.iface.AcceptUnicodeString('abc'), str))
+
+ def testIntrospection(self):
+ #test introspection
+ print("\n********* Introspection Test ************")
+ print(self.remote_object.Introspect(
+ dbus_interface="org.freedesktop.DBus.Introspectable"))
+ print("Introspection test passed")
+ self.assertTrue(True)
+
+ def testMultiPathIntrospection(self):
+ # test introspection on an object exported in multiple places
+ # https://bugs.freedesktop.org/show_bug.cgi?id=11794
+ remote_object = self.bus.get_object(NAME, OBJECT + '/Multi1')
+ remote_object.Introspect(dbus_interface="org.freedesktop.DBus.Introspectable")
+ remote_object = self.bus.get_object(NAME, OBJECT + '/Multi2')
+ remote_object.Introspect(dbus_interface="org.freedesktop.DBus.Introspectable")
+ remote_object = self.bus.get_object(NAME, OBJECT + '/Multi2/3')
+ remote_object.Introspect(dbus_interface="org.freedesktop.DBus.Introspectable")
+ self.assertTrue(True)
+
+ def testPythonTypes(self):
+ #test sending python types and getting them back
+ print("\n********* Testing Python Types ***********")
+
+ for send_val in test_types_vals:
+ print("Testing %s"% str(send_val))
+ recv_val = self.iface.Echo(send_val)
+ self.assertEqual(send_val, recv_val)
+ self.assertEqual(recv_val.variant_level, 1)
+
+ def testMethodExtraInfoKeywords(self):
+ print("Testing MethodExtraInfoKeywords...")
+ sender, path, destination, message_cls = self.iface.MethodExtraInfoKeywords()
+ self.assertTrue(sender.startswith(':'))
+ self.assertEqual(path, '/org/freedesktop/DBus/TestSuitePythonObject')
+ # we're using the "early binding" form of get_object (without
+ # follow_name_owner_changes), so the destination we actually sent it
+ # to will be the unique name
+ self.assertTrue(destination.startswith(':'))
+ self.assertEqual(message_cls, 'dbus.lowlevel.MethodCallMessage')
+
+ def testBenchmarkIntrospect(self):
+ print("\n********* Benchmark Introspect ************")
+ a = time.time()
+ print(a)
+ print(self.iface.GetComplexArray())
+ b = time.time()
+ print(b)
+ print("Delta: %f" % (b - a))
+ self.assertTrue(True)
+
+ def testAllowInteractiveAuthorization(self):
+ message = dbus.lowlevel.MethodCallMessage(NAME, OBJECT, IFACE, 'TestAllowInteractiveAuthorization')
+ assert not message.get_allow_interactive_authorization()
+ message.set_allow_interactive_authorization(True)
+ assert message.get_allow_interactive_authorization()
+
+ def testNoReply(self):
+ failures = []
+ report = []
+ main_loop = GLib.MainLoop()
+
+ def message_filter(conn, m):
+ print('Message filter received message: %r, %r' % (m, m.get_args_list()))
+
+ if conn is not self.bus:
+ failures.append('Message filter called on unexpected bus')
+
+ for a in m.get_args_list():
+ if isinstance(a, str):
+ if SHOULD_NOT_HAPPEN in a:
+ failures.append('Had an unexpected reply')
+ elif a == 'TestNoReply report':
+ report.extend(m.get_args_list())
+ main_loop.quit()
+
+ return dbus.lowlevel.HANDLER_RESULT_NOT_YET_HANDLED
+
+ self.bus.add_message_filter(message_filter)
+
+ message = dbus.lowlevel.MethodCallMessage(NAME, OBJECT, IFACE, 'TestNoReply')
+ message.append(True)
+ message.append(False)
+ message.set_no_reply(True)
+ self.bus.send_message(message)
+
+ message = dbus.lowlevel.MethodCallMessage(NAME, OBJECT, IFACE, 'TestNoReply')
+ message.append(False)
+ message.append(False)
+ message.set_no_reply(True)
+ self.bus.send_message(message)
+
+ message = dbus.lowlevel.MethodCallMessage(NAME, OBJECT, IFACE, 'TestNoReply')
+ message.append(True)
+ message.append(True)
+ self.bus.send_message(message)
+
+ main_loop.run()
+ self.bus.remove_message_filter(message_filter)
+
+ if failures:
+ self.assertTrue(False, failures)
+
+ self.assertEqual(report[0], 'TestNoReply report')
+ self.assertEqual(report[1], 1)
+ self.assertEqual(report[2], 1)
+
+ def testAsyncCalls(self):
+ #test sending python types and getting them back async
+ print("\n********* Testing Async Calls ***********")
+
+ failures = []
+ main_loop = GLib.MainLoop()
+
+ class async_check:
+ def __init__(self, test_controler, expected_result, do_exit, **kwargs):
+ self.expected_result = expected_result
+ self.do_exit = do_exit
+ self.test_controler = test_controler
+ if 'utf8' in kwargs:
+ raise TypeError("unexpected keyword argument 'utf8'")
+
+ def callback(self, val):
+ try:
+ if self.do_exit:
+ main_loop.quit()
+
+ self.test_controler.assertEqual(val, self.expected_result)
+ self.test_controler.assertEqual(val.variant_level, 1)
+ except Exception as e:
+ failures.append("%s:\n%s" % (e.__class__, e))
+
+ def error_handler(self, error):
+ print(error)
+ if self.do_exit:
+ main_loop.quit()
+
+ failures.append('%s: %s' % (error.__class__, error))
+
+ last_type = test_types_vals[-1]
+ for send_val in test_types_vals:
+ print("Testing %s" % str(send_val))
+ check = async_check(self, send_val, last_type == send_val)
+ recv_val = self.iface.Echo(send_val,
+ reply_handler=check.callback,
+ error_handler=check.error_handler)
+ main_loop.run()
+ if failures:
+ self.assertTrue(False, failures)
+
+ def testStrictMarshalling(self):
+ print("\n********* Testing strict return & signal marshalling ***********")
+
+ # these values are the same as in the server, and the
+ # methods should only succeed when they are called with
+ # the right value number, because they have out_signature
+ # decorations, and return an unmatching type when called
+ # with a different number
+ values = ["", ("",""), ("","",""), [], {}, ["",""], ["","",""]]
+ methods = [
+ (self.iface.ReturnOneString, 'SignalOneString', set([0]), set([0])),
+ (self.iface.ReturnTwoStrings, 'SignalTwoStrings', set([1, 5]), set([1])),
+ (self.iface.ReturnStruct, 'SignalStruct', set([1, 5]), set([1])),
+ # all of our test values are sequences so will marshall correctly
+ # into an array :P
+ (self.iface.ReturnArray, 'SignalArray', set(range(len(values))), set([3, 5, 6])),
+ (self.iface.ReturnDict, 'SignalDict', set([0, 3, 4]), set([4]))
+ ]
+
+ for (method, signal, success_values, return_values) in methods:
+ print("\nTrying correct behaviour of", method._method_name)
+ for value in range(len(values)):
+ try:
+ ret = method(value)
+ except Exception as e:
+ print("%s(%r) raised %s: %s" %
+ (method._method_name, values[value], e.__class__, e))
+
+ # should fail if it tried to marshal the wrong type
+ self.assertTrue(value not in success_values,
+ "%s should succeed when we ask it to "
+ "return %r\n%s\n%s" % (
+ method._method_name, values[value],
+ e.__class__, e))
+ else:
+ print("%s(%r) returned %r" % (
+ method._method_name, values[value], ret))
+
+ # should only succeed if it's the right return type
+ self.assertTrue(value in success_values,
+ "%s should fail when we ask it to "
+ "return %r" % (
+ method._method_name, values[value]))
+
+ # check the value is right too :D
+ returns = map(lambda n: values[n], return_values)
+ self.assertTrue(ret in returns,
+ "%s should return one of %r but it "
+ "returned %r instead" % (
+ method._method_name, returns, ret))
+
+ print("\nTrying correct emission of", signal)
+ for value in range(len(values)):
+ try:
+ self.iface.EmitSignal(signal, value)
+ except Exception as e:
+ print("EmitSignal(%s, %r) raised %s" % (signal, values[value], e.__class__))
+
+ # should fail if it tried to marshal the wrong type
+ self.assertTrue(value not in success_values, "EmitSignal(%s) should succeed when we ask it to return %r\n%s\n%s" % (signal, values[value], e.__class__, e))
+ else:
+ print("EmitSignal(%s, %r) appeared to succeed" % (signal, values[value]))
+
+ # should only succeed if it's the right return type
+ self.assertTrue(value in success_values, "EmitSignal(%s) should fail when we ask it to return %r" % (signal, values[value]))
+
+ # FIXME: wait for the signal here
+
+ print()
+
+ def testInheritance(self):
+ print("\n********* Testing inheritance from dbus.method.Interface ***********")
+ ret = self.iface.CheckInheritance()
+ print("CheckInheritance returned %s" % ret)
+ self.assertTrue(ret, "overriding CheckInheritance from TestInterface failed")
+
+ def testAsyncMethods(self):
+ print("\n********* Testing asynchronous method implementation *******")
+ for async_ in (True, False):
+ for fail in (True, False):
+ try:
+ val = ('a', 1, False, [1,2], {1:2})
+ print("calling AsynchronousMethod with %s %s %s" % (async_, fail, val))
+ ret = self.iface.AsynchronousMethod(async_, fail, val)
+ except Exception as e:
+ self.assertTrue(fail, '%s: %s' % (e.__class__, e))
+ print("Expected failure: %s: %s" % (e.__class__, e))
+ else:
+ self.assertTrue(not fail, 'Expected failure but succeeded?!')
+ self.assertEqual(val, ret)
+ self.assertEqual(1, ret.variant_level)
+
+ def testBusInstanceCaching(self):
+ print("\n********* Testing dbus.Bus instance sharing *********")
+
+ # unfortunately we can't test the system bus here
+ # but the codepaths are the same
+ for (cls, type, func) in ((dbus.SessionBus, dbus.Bus.TYPE_SESSION, dbus.Bus.get_session), (dbus.StarterBus, dbus.Bus.TYPE_STARTER, dbus.Bus.get_starter)):
+ print("\nTesting %s:" % cls.__name__)
+
+ share_cls = cls()
+ share_type = dbus.Bus(bus_type=type)
+ share_func = func()
+
+ private_cls = cls(private=True)
+ private_type = dbus.Bus(bus_type=type, private=True)
+ private_func = func(private=True)
+
+ print(" - checking shared instances are the same...")
+ self.assertTrue(share_cls == share_type, '%s should equal %s' % (share_cls, share_type))
+ self.assertTrue(share_type == share_func, '%s should equal %s' % (share_type, share_func))
+
+ print(" - checking private instances are distinct from the shared instance...")
+ self.assertTrue(share_cls != private_cls, '%s should not equal %s' % (share_cls, private_cls))
+ self.assertTrue(share_type != private_type, '%s should not equal %s' % (share_type, private_type))
+ self.assertTrue(share_func != private_func, '%s should not equal %s' % (share_func, private_func))
+
+ print(" - checking private instances are distinct from each other...")
+ self.assertTrue(private_cls != private_type, '%s should not equal %s' % (private_cls, private_type))
+ self.assertTrue(private_type != private_func, '%s should not equal %s' % (private_type, private_func))
+ self.assertTrue(private_func != private_cls, '%s should not equal %s' % (private_func, private_cls))
+
+ def testSenderName(self):
+ print('\n******** Testing sender name keyword ********')
+ myself = self.iface.WhoAmI()
+ print("I am", myself)
+
+ def testBusGetNameOwner(self):
+ ret = self.bus.get_name_owner(NAME)
+ self.assertTrue(ret.startswith(':'), ret)
+
+ def testBusListNames(self):
+ ret = self.bus.list_names()
+ self.assertTrue(NAME in ret, ret)
+
+ def testBusListActivatableNames(self):
+ ret = self.bus.list_activatable_names()
+ self.assertTrue(NAME in ret, ret)
+
+ def testBusNameHasOwner(self):
+ self.assertTrue(self.bus.name_has_owner(NAME))
+ self.assertTrue(not self.bus.name_has_owner('badger.mushroom.snake'))
+
+ def testBusNameCreation(self):
+ print('\n******** Testing BusName creation ********')
+ test = [('org.freedesktop.DBus.Python.TestName', True),
+ ('org.freedesktop.DBus.Python.TestName', True),
+ ('org.freedesktop.DBus.Python.InvalidName&^*%$', False)]
+ # Do some more intelligent handling/testing of queueing vs success?
+ # ('org.freedesktop.DBus.TestSuitePythonService', False)]
+ # For some reason this actually succeeds
+ # ('org.freedesktop.DBus', False)]
+
+ # make a method call to ensure the test service is active
+ self.iface.Echo("foo")
+
+ names = {}
+ for (name, succeed) in test:
+ try:
+ print("requesting %s" % name)
+ busname = dbus.service.BusName(name, dbus.SessionBus())
+ except Exception as e:
+ print("%s:\n%s" % (e.__class__, e))
+ self.assertTrue(not succeed, 'did not expect registering bus name %s to fail' % name)
+ else:
+ print(busname)
+ self.assertTrue(succeed, 'expected registering bus name %s to fail'% name)
+ if name in names:
+ self.assertTrue(names[name] == busname, 'got a new instance for same name %s' % name)
+ print("instance of %s re-used, good!" % name)
+ else:
+ names[name] = busname
+
+ del busname
+
+ print()
+
+ del names
+
+ bus = dbus.Bus()
+ ret = bus.name_has_owner('org.freedesktop.DBus.Python.TestName')
+ self.assertTrue(not ret, 'deleting reference failed to release BusName org.freedesktop.DBus.Python.TestName')
+
+ def testMultipleReturnWithoutSignature(self):
+ # https://bugs.freedesktop.org/show_bug.cgi?id=10174
+ ret = self.iface.MultipleReturnWithoutSignature()
+ self.assertTrue(not isinstance(ret, dbus.Struct), repr(ret))
+ self.assertEqual(ret, ('abc', 123))
+
+ def testListExportedChildObjects(self):
+ self.assertTrue(self.iface.TestListExportedChildObjects())
+
+ def testRemoveFromConnection(self):
+ # https://bugs.freedesktop.org/show_bug.cgi?id=10457
+ self.assertTrue(not self.iface.HasRemovableObject())
+ self.assertTrue(self.iface.AddRemovableObject())
+ self.assertTrue(self.iface.HasRemovableObject())
+
+ removable = self.bus.get_object(NAME, OBJECT + '/RemovableObject')
+ iface = dbus.Interface(removable, IFACE)
+ self.assertTrue(iface.IsThere())
+ self.assertTrue(iface.RemoveSelf())
+
+ self.assertTrue(not self.iface.HasRemovableObject())
+
+ # and again...
+ self.assertTrue(self.iface.AddRemovableObject())
+ self.assertTrue(self.iface.HasRemovableObject())
+ self.assertTrue(iface.IsThere())
+ self.assertTrue(iface.RemoveSelf())
+ self.assertTrue(not self.iface.HasRemovableObject())
+
+ def testFallbackObjectTrivial(self):
+ obj = self.bus.get_object(NAME, OBJECT + '/Fallback')
+ iface = dbus.Interface(obj, IFACE)
+ path, rel, unique_name = iface.TestPathAndConnKeywords()
+ self.assertEqual(path, OBJECT + '/Fallback')
+ self.assertEqual(rel, '/')
+ self.assertEqual(unique_name, obj.bus_name)
+
+ def testFallbackObjectNested(self):
+ obj = self.bus.get_object(NAME, OBJECT + '/Fallback/Nested')
+ iface = dbus.Interface(obj, IFACE)
+ path, rel, unique_name = iface.TestPathAndConnKeywords()
+ self.assertEqual(path, OBJECT + '/Fallback/Nested')
+ self.assertEqual(rel, '/')
+ self.assertEqual(unique_name, obj.bus_name)
+
+ obj = self.bus.get_object(NAME, OBJECT + '/Fallback/Nested/Badger/Mushroom')
+ iface = dbus.Interface(obj, IFACE)
+ path, rel, unique_name = iface.TestPathAndConnKeywords()
+ self.assertEqual(path, OBJECT + '/Fallback/Nested/Badger/Mushroom')
+ self.assertEqual(rel, '/Badger/Mushroom')
+ self.assertEqual(unique_name, obj.bus_name)
+
+ def testFallbackObject(self):
+ obj = self.bus.get_object(NAME, OBJECT + '/Fallback/Badger/Mushroom')
+ iface = dbus.Interface(obj, IFACE)
+ path, rel, unique_name = iface.TestPathAndConnKeywords()
+ self.assertEqual(path, OBJECT + '/Fallback/Badger/Mushroom')
+ self.assertEqual(rel, '/Badger/Mushroom')
+ self.assertEqual(unique_name, obj.bus_name)
+
+ def testTimeoutSync(self):
+ self.assertTrue(self.iface.BlockFor500ms(timeout=1.0) is None)
+ self.assertRaises(dbus.DBusException,
+ lambda: self.iface.BlockFor500ms(timeout=0.25))
+
+ def testAsyncRaise(self):
+ self.assertRaises(dbus.DBusException, self.iface.AsyncRaise)
+ try:
+ self.iface.AsyncRaise()
+ except dbus.DBusException as e:
+ self.assertTrue(e.get_dbus_name() ==
+ 'org.freedesktop.bugzilla.bug12403',
+ e.get_dbus_name())
+ else:
+ self.assertTrue(False)
+
+ def testClosePrivateBus(self):
+ # fd.o #12096
+ dbus.Bus(private=True).close()
+
+ def testTimeoutAsyncClient(self):
+ loop = GLib.MainLoop()
+ passes = []
+ fails = []
+ def correctly_returned():
+ passes.append('1000')
+ if len(passes) + len(fails) >= 2:
+ loop.quit()
+ def correctly_failed(exc):
+ passes.append('250')
+ if len(passes) + len(fails) >= 2:
+ loop.quit()
+ def incorrectly_returned():
+ fails.append('250')
+ if len(passes) + len(fails) >= 2:
+ loop.quit()
+ def incorrectly_failed(exc):
+ fails.append('1000')
+ if len(passes) + len(fails) >= 2:
+ loop.quit()
+ self.iface.BlockFor500ms(timeout=1.0,
+ reply_handler=correctly_returned,
+ error_handler=incorrectly_failed)
+ self.iface.BlockFor500ms(timeout=0.25,
+ reply_handler=incorrectly_returned,
+ error_handler=correctly_failed)
+ loop.run()
+ self.assertEqual(passes, ['250', '1000'])
+ self.assertEqual(fails, [])
+
+ def testTimeoutAsyncService(self):
+ self.assertTrue(self.iface.AsyncWait500ms(timeout=1.0) is None)
+ self.assertRaises(dbus.DBusException,
+ lambda: self.iface.AsyncWait500ms(timeout=0.25))
+
+ def testExceptions(self):
+ #self.assertRaises(dbus.DBusException,
+ # lambda: self.iface.RaiseValueError)
+ #self.assertRaises(dbus.DBusException,
+ # lambda: self.iface.RaiseDBusExceptionNoTraceback)
+ #self.assertRaises(dbus.DBusException,
+ # lambda: self.iface.RaiseDBusExceptionWithTraceback)
+
+ try:
+ self.iface.RaiseValueError()
+ except Exception as e:
+ self.assertTrue(isinstance(e, dbus.DBusException), e.__class__)
+ self.assertTrue('.ValueError: Traceback ' in str(e),
+ 'Wanted a traceback but got:\n"""%s"""' % str(e))
+ else:
+ raise AssertionError('Wanted an exception')
+
+ try:
+ self.iface.RaiseDBusExceptionNoTraceback()
+ except Exception as e:
+ self.assertTrue(isinstance(e, dbus.DBusException), e.__class__)
+ self.assertEqual(e.get_dbus_name(),
+ 'com.example.Networking.ServerError')
+ self.assertEqual(str(e),
+ 'com.example.Networking.ServerError: '
+ 'Server not responding')
+ else:
+ raise AssertionError('Wanted an exception')
+
+ try:
+ self.iface.RaiseDBusExceptionWithTraceback()
+ except Exception as e:
+ self.assertTrue(isinstance(e, dbus.DBusException), e.__class__)
+ self.assertEqual(e.get_dbus_name(),
+ 'com.example.Misc.RealityFailure')
+ self.assertTrue(str(e).startswith('com.example.Misc.RealityFailure: '
+ 'Traceback '),
+ 'Wanted a traceback but got:\n%s' % str(e))
+ else:
+ raise AssertionError('Wanted an exception')
+
+""" Remove this for now
+class TestDBusPythonToGLibBindings(unittest.TestCase):
+ def setUp(self):
+ self.bus = dbus.SessionBus()
+ self.remote_object = self.bus.get_object("org.freedesktop.DBus.TestSuiteGLibService", "/org/freedesktop/DBus/Tests/MyTestObject")
+ self.iface = dbus.Interface(self.remote_object, "org.freedesktop.DBus.Tests.MyObject")
+
+ def testIntrospection(self):
+ #test introspection
+ print "\n********* Introspection Test ************"
+ print self.remote_object.Introspect(dbus_interface="org.freedesktop.DBus.Introspectable")
+ print "Introspection test passed"
+ self.assertTrue(True)
+
+ def testCalls(self):
+ print "\n********* Call Test ************"
+ result = self.iface.ManyArgs(1000, 'Hello GLib', 2)
+ print result
+ self.assertTrue(result == [2002.0, 'HELLO GLIB'])
+
+ arg0 = {"Dude": 1, "john": "palmieri", "python": 2.4}
+ result = self.iface.ManyStringify(arg0)
+ print result
+
+ print "Call test passed"
+ self.assertTrue(True)
+
+ def testPythonTypes(self):
+ print "\n********* Testing Python Types ***********"
+
+ for send_val in test_types_vals:
+ print "Testing %s"% str(send_val)
+ recv_val = self.iface.EchoVariant(send_val)
+ self.assertEqual(send_val, recv_val.object)
+"""
+if __name__ == '__main__':
+ dbus.glib.init_threads()
+
+ dbus_test_utils.main()
diff --git a/test/test-exception-py3.py b/test/test-exception-py3.py
new file mode 100755
index 0000000..4ee0fff
--- /dev/null
+++ b/test/test-exception-py3.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright 2012 Michael Vogt
+# Copyright 2016-2020 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+
+import unittest
+
+import dbus
+import dbus_test_utils
+
+# from test-service.py
+class ServiceError(dbus.DBusException):
+ """Exception representing a normal "environmental" error"""
+ include_traceback = False
+ _dbus_error_name = 'com.example.Networking.ServerError'
+
+
+class DBusExceptionTestCase(unittest.TestCase):
+
+ def test_dbus_exception(self):
+ e = dbus.exceptions.DBusException("bäää")
+ msg = e.get_dbus_message()
+ self.assertEqual(msg, "bäää")
+ self.assertEqual(str(e), "bäää")
+
+ def test_subclass_exception(self):
+ e = ServiceError("bäää")
+ msg = e.get_dbus_message()
+ self.assertEqual(msg, "bäää")
+ self.assertEqual(str(e), "com.example.Networking.ServerError: bäää")
+
+
+if __name__ == "__main__":
+ dbus_test_utils.main()
diff --git a/test/test-p2p.py b/test/test-p2p.py
new file mode 100755
index 0000000..68f7f71
--- /dev/null
+++ b/test/test-p2p.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2004 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+
+import os
+import unittest
+import logging
+import sys
+
+import dbus
+import dbus.glib
+import dbus.service
+import dbus.types
+import dbus_test_utils
+
+try:
+ from gi.repository import GLib
+except ImportError:
+ print('1..0 # SKIP cannot import GLib')
+ raise SystemExit(0)
+
+logging.basicConfig()
+logging.getLogger().setLevel(1)
+
+
+NAME = "org.freedesktop.DBus.TestSuitePythonService"
+IFACE = "org.freedesktop.DBus.TestSuiteInterface"
+OBJECT = "/org/freedesktop/DBus/TestSuitePythonObject"
+
+class TestDBusBindings(unittest.TestCase):
+ # This test case relies on the test service already having been activated.
+
+ def get_conn_and_unique(self):
+ # since I haven't implemented servers yet, I'll use the bus daemon
+ # as our peer - note that there's no name tracking because we're not
+ # using dbus.bus.BusConnection!
+ conn = dbus.connection.Connection(
+ os.environ['DBUS_SESSION_BUS_ADDRESS'])
+ unique = conn.call_blocking('org.freedesktop.DBus',
+ '/org/freedesktop/DBus',
+ 'org.freedesktop.DBus', 'Hello',
+ '', ())
+ self.assertTrue(unique.startswith(':'), unique)
+ conn.set_unique_name(unique)
+ return conn, unique
+
+ def testCall(self):
+ conn, unique = self.get_conn_and_unique()
+ ret = conn.call_blocking(NAME, OBJECT, IFACE, 'Echo', 'v', ('V',))
+ self.assertEqual(ret, 'V')
+ self.assertEqual(ret.variant_level, 1)
+
+ @unittest.skipIf(sys.platform.startswith("win"), "requires Unix")
+ def testUnixFd(self):
+ with open('/dev/null', 'r') as file_like:
+ for method in 'Echo', 'EchoVariant', 'EchoFd':
+ if method == 'EchoFd':
+ sig = 'h'
+ else:
+ sig = 'v'
+
+ conn, unique = self.get_conn_and_unique()
+ ret = conn.call_blocking(NAME, OBJECT, IFACE, method, sig, (dbus.types.UnixFd(file_like),))
+ self.assertEqual(type(ret), dbus.types.UnixFd)
+
+ if method == 'EchoFd':
+ self.assertEqual(ret.variant_level, 0)
+ elif method == 'EchoVariant':
+ self.assertEqual(ret.variant_level, 1)
+
+ plain_fd = ret.take()
+ self.assertTrue(os.path.sameopenfile(file_like.fileno(), plain_fd))
+ os.close(plain_fd)
+
+ def testCallThroughProxy(self):
+ conn, unique = self.get_conn_and_unique()
+ proxy = conn.get_object(NAME, OBJECT)
+ iface = dbus.Interface(proxy, IFACE)
+ ret = iface.Echo('V')
+ self.assertEqual(ret, 'V')
+
+ def testSetUniqueName(self):
+ conn, unique = self.get_conn_and_unique()
+ ret = conn.call_blocking(NAME, OBJECT, IFACE,
+ 'MethodExtraInfoKeywords', '', ())
+ self.assertEqual(ret, (unique, OBJECT, NAME,
+ 'dbus.lowlevel.MethodCallMessage'))
+
+
+if __name__ == '__main__':
+ dbus.glib.init_threads()
+
+ dbus_test_utils.main()
diff --git a/test/test-server.py b/test/test-server.py
new file mode 100755
index 0000000..8bb5550
--- /dev/null
+++ b/test/test-server.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+
+# Copyright 2008 Mathias Hasselmann and/or Openismus
+# Copyright 2008-2016 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+
+from dbus.mainloop.glib import DBusGMainLoop
+
+import dbus
+import dbus.connection
+import dbus.service
+import dbus.server
+
+from gi.repository import GLib
+import os, sys
+
+class TestService(dbus.service.Object):
+ NAME = 'org.freedesktop.dbus.TestService'
+ PATH = '/org/freedesktop/dbus/TestService'
+
+ def __init__(self, conn, path=PATH):
+ super(TestService, self).__init__(conn, path)
+
+ @dbus.service.method(dbus_interface=NAME,
+ out_signature='s',
+ in_signature='s')
+ def reverse(self, text):
+ text = list(text)
+ text.reverse()
+
+ return ''.join(text)
+
+pin, pout = os.pipe()
+child = os.fork()
+
+if 0 == child:
+ DBusGMainLoop(set_as_default=True)
+ server = dbus.server.Server('unix:tmpdir=/tmp')
+
+ def new_connection(conn):
+ print "new connection, %r" % conn
+ TestService(conn)
+
+ def connection_gone(conn):
+ print "goodbye, %r" % conn
+
+ # Instantiate a TestService every time a connection is created
+ server.on_connection_added.append(new_connection)
+ server.on_connection_removed.append(connection_gone)
+
+ os.write(pout, server.address)
+ os.close(pout)
+ os.close(pin)
+
+ print 'server running: %s' % server.address
+ GLib.MainLoop().run()
+
+ print 'server quit'
+
+ print 'done?'
+
+else:
+ os.waitpid(child, os.WNOHANG)
+ os.close(pout)
+
+ address = os.read(pin, 128)
+ os.close(pin)
+
+ client = dbus.connection.Connection(address)
+ proxy = client.get_object(TestService.NAME, TestService.PATH)
+ object = dbus.Interface(proxy, TestService.NAME)
+
+ while True:
+ line = sys.stdin.readline()
+ if not line: break
+
+ text = line.strip()
+ print 'reverse(%s): %s' % (text, object.reverse(text))
+
+ client.close()
diff --git a/test/test-service.py b/test/test-service.py
new file mode 100755
index 0000000..f0f2819
--- /dev/null
+++ b/test/test-service.py
@@ -0,0 +1,416 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2004 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import os
+import logging
+from time import sleep
+
+import dbus
+
+if 'DBUS_TEST_UNINSTALLED' in os.environ:
+ import _dbus_bindings
+
+ builddir = os.path.normpath(os.environ["DBUS_TOP_BUILDDIR"])
+ pydir = os.path.normpath(os.environ["DBUS_TOP_SRCDIR"])
+ pkg = dbus.__file__
+
+ if not pkg.startswith(pydir):
+ raise Exception("DBus modules (%s) are not being picked up from the "
+ "package" % pkg)
+
+ if not _dbus_bindings.__file__.startswith(builddir):
+ raise Exception("DBus modules (%s) are not being picked up from the "
+ "package" % _dbus_bindings.__file__)
+
+import dbus.service
+import dbus.glib
+import random
+
+from dbus.gi_service import ExportedGObject
+from gi.repository import GLib
+
+
+if 'DBUS_TEST_TMPDIR' in os.environ:
+ logging.basicConfig(
+ filename=os.environ['DBUS_TEST_TMPDIR'] + '/test-service.log',
+ filemode='a')
+else:
+ logging.basicConfig()
+
+logging.getLogger().setLevel(1)
+logger = logging.getLogger('test-service')
+
+NAME = "org.freedesktop.DBus.TestSuitePythonService"
+IFACE = "org.freedesktop.DBus.TestSuiteInterface"
+OBJECT = "/org/freedesktop/DBus/TestSuitePythonObject"
+
+# A random string that we should not transmit on the bus as a result of
+# the NO_REPLY flag
+SHOULD_NOT_HAPPEN = 'a1c04a41-cf98-4923-8487-ddaeeb3f02d1'
+
+
+class RemovableObject(dbus.service.Object):
+ # Part of test for https://bugs.freedesktop.org/show_bug.cgi?id=10457
+ @dbus.service.method(IFACE, in_signature='', out_signature='b')
+ def IsThere(self):
+ return True
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='b')
+ def RemoveSelf(self):
+ self.remove_from_connection()
+ return True
+
+class TestGObject(ExportedGObject):
+ def __init__(self, bus_name, object_path=OBJECT + '/GObject'):
+ super(TestGObject, self).__init__(bus_name, object_path)
+
+ @dbus.service.method(IFACE)
+ def Echo(self, arg):
+ return arg
+
+class TestInterface(dbus.service.Interface):
+ @dbus.service.method(IFACE, in_signature='', out_signature='b')
+ def CheckInheritance(self):
+ return False
+
+class Fallback(dbus.service.FallbackObject):
+ def __init__(self, conn, object_path=OBJECT + '/Fallback'):
+ super(Fallback, self).__init__(conn, object_path)
+ self.add_to_connection(conn, object_path + '/Nested')
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='oos',
+ path_keyword='path', rel_path_keyword='rel',
+ connection_keyword='conn')
+ def TestPathAndConnKeywords(self, path=None, conn=None, rel=None):
+ return path, rel, conn.get_unique_name()
+
+ @dbus.service.signal(IFACE, signature='s', rel_path_keyword='rel_path')
+ def SignalOneString(self, test, rel_path=None):
+ logger.info('SignalOneString(%r) @ %r', test, rel_path)
+
+ # Deprecated usage
+ @dbus.service.signal(IFACE, signature='ss', path_keyword='path')
+ def SignalTwoStrings(self, test, test2, path=None):
+ logger.info('SignalTwoStrings(%r, %r) @ %r', test, test2, path)
+
+ @dbus.service.method(IFACE, in_signature='su', out_signature='',
+ path_keyword='path')
+ def EmitSignal(self, signal, value, path=None):
+ sig = getattr(self, str(signal), None)
+ assert sig is not None
+
+ assert path.startswith(OBJECT + '/Fallback')
+ rel_path = path[len(OBJECT + '/Fallback'):]
+ if rel_path == '':
+ rel_path = '/'
+
+ if signal == 'SignalOneString':
+ logger.info('Emitting %s from rel %r', signal, rel_path)
+ sig('I am a fallback', rel_path=rel_path)
+ else:
+ val = ('I am', 'a fallback')
+ logger.info('Emitting %s from abs %r', signal, path)
+ sig('I am', 'a deprecated fallback', path=path)
+
+class MultiPathObject(dbus.service.Object):
+ SUPPORTS_MULTIPLE_OBJECT_PATHS = True
+
+class TestObject(dbus.service.Object, TestInterface):
+ def __init__(self, bus_name, object_path=OBJECT):
+ dbus.service.Object.__init__(self, bus_name, object_path)
+ self._removable = RemovableObject()
+ self._multi = MultiPathObject(bus_name, object_path + '/Multi1')
+ self._multi.add_to_connection(bus_name.get_bus(),
+ object_path + '/Multi2')
+ self._multi.add_to_connection(bus_name.get_bus(),
+ object_path + '/Multi2/3')
+ self._times_no_reply_succeeded = 0
+ self._times_no_reply_failed = 0
+
+ """ Echo whatever is sent
+ """
+ @dbus.service.method(IFACE)
+ def Echo(self, arg):
+ return arg
+
+ @dbus.service.method(IFACE, in_signature='v', out_signature='v')
+ def EchoVariant(self, arg):
+ return arg
+
+ @dbus.service.method(IFACE, in_signature='h', out_signature='h')
+ def EchoFd(self, arg):
+ return arg
+
+ @dbus.service.method(IFACE, in_signature='s', out_signature='s')
+ def AcceptUnicodeString(self, foo):
+ assert isinstance(foo, str), (foo, foo.__class__.__mro__)
+ return foo
+
+ @dbus.service.method(IFACE, in_signature='s', out_signature='s')
+ def AcceptUTF8String(self, foo):
+ assert isinstance(foo, str), (foo, foo.__class__.__mro__)
+ return foo
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='soss',
+ sender_keyword='sender', path_keyword='path',
+ destination_keyword='dest', message_keyword='msg')
+ def MethodExtraInfoKeywords(self, sender=None, path=None, dest=None,
+ msg=None):
+ return (sender, path, dest,
+ msg.__class__.__module__ + '.' + msg.__class__.__name__)
+
+ @dbus.service.method(IFACE, in_signature='ay', out_signature='ay')
+ def AcceptListOfByte(self, foo):
+ assert isinstance(foo, list), (foo, foo.__class__.__mro__)
+ return foo
+
+ @dbus.service.method(IFACE, in_signature='ay', out_signature='ay',
+ byte_arrays=True)
+ def AcceptByteArray(self, foo):
+ assert isinstance(foo, bytes), (foo, foo.__class__.__mro__)
+ return foo
+
+ @dbus.service.method(IFACE)
+ def GetComplexArray(self):
+ ret = []
+ for i in range(0,100):
+ ret.append((random.randint(0,100), random.randint(0,100), str(random.randint(0,100))))
+
+ return dbus.Array(ret, signature="(uus)")
+
+ def returnValue(self, test):
+ if test == 0:
+ return ""
+ elif test == 1:
+ return "",""
+ elif test == 2:
+ return "","",""
+ elif test == 3:
+ return []
+ elif test == 4:
+ return {}
+ elif test == 5:
+ return ["",""]
+ elif test == 6:
+ return ["","",""]
+
+ @dbus.service.method(IFACE, in_signature='u', out_signature='s')
+ def ReturnOneString(self, test):
+ return self.returnValue(test)
+
+ @dbus.service.method(IFACE, in_signature='u', out_signature='ss')
+ def ReturnTwoStrings(self, test):
+ return self.returnValue(test)
+
+ @dbus.service.method(IFACE, in_signature='u', out_signature='(ss)')
+ def ReturnStruct(self, test):
+ logger.info('ReturnStruct(%r) -> %r', test, self.returnValue(test))
+ return self.returnValue(test)
+
+ @dbus.service.method(IFACE, in_signature='u', out_signature='as')
+ def ReturnArray(self, test):
+ return self.returnValue(test)
+
+ @dbus.service.method(IFACE, in_signature='u', out_signature='a{ss}')
+ def ReturnDict(self, test):
+ return self.returnValue(test)
+
+ @dbus.service.signal(IFACE, signature='s')
+ def SignalOneString(self, test):
+ logger.info('SignalOneString(%r)', test)
+
+ @dbus.service.signal(IFACE, signature='ss')
+ def SignalTwoStrings(self, test, test2):
+ logger.info('SignalTwoStrings(%r, %r)', test, test2)
+
+ @dbus.service.signal(IFACE, signature='(ss)')
+ def SignalStruct(self, test):
+ logger.info('SignalStruct(%r)', test)
+
+ @dbus.service.signal(IFACE, signature='as')
+ def SignalArray(self, test):
+ pass
+
+ @dbus.service.signal(IFACE, signature='a{ss}')
+ def SignalDict(self, test):
+ pass
+
+ @dbus.service.method(IFACE, in_signature='su', out_signature='')
+ def EmitSignal(self, signal, value):
+ sig = getattr(self, str(signal), None)
+ assert(sig != None)
+
+ val = self.returnValue(value)
+ # make two string case work by passing arguments in by tuple
+ if (signal == 'SignalTwoStrings' and (value == 1 or value == 5)):
+ val = tuple(val)
+ else:
+ val = tuple([val])
+
+ logger.info('Emitting %s with %r', signal, val)
+ sig(*val)
+
+ def CheckInheritance(self):
+ return True
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='b')
+ def TestListExportedChildObjects(self):
+ objs_root = session_bus.list_exported_child_objects('/')
+ assert objs_root == ['org'], objs_root
+ objs_org = session_bus.list_exported_child_objects('/org')
+ assert objs_org == ['freedesktop'], objs_org
+ return True
+
+ @dbus.service.method(IFACE, in_signature='bbv', out_signature='v',
+ async_callbacks=('return_cb', 'error_cb'))
+ def AsynchronousMethod(self, async_, fail, variant, return_cb, error_cb):
+ try:
+ if async_:
+ GLib.timeout_add(500, self.AsynchronousMethod, False, fail,
+ variant, return_cb, error_cb)
+ return
+ else:
+ if fail:
+ raise RuntimeError
+ else:
+ return_cb(variant)
+
+ return False # do not run again
+ except Exception as e:
+ error_cb(e)
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='s',
+ sender_keyword='sender')
+ def WhoAmI(self, sender):
+ return sender
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='b')
+ def AddRemovableObject(self):
+ # Part of test for https://bugs.freedesktop.org/show_bug.cgi?id=10457
+ # Keep the removable object reffed, since that's the use case for this
+ self._removable.add_to_connection(self._connection,
+ OBJECT + '/RemovableObject')
+ return True
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='b')
+ def HasRemovableObject(self):
+ # Part of test for https://bugs.freedesktop.org/show_bug.cgi?id=10457
+ objs = session_bus.list_exported_child_objects(OBJECT)
+ return ('RemovableObject' in objs)
+
+ @dbus.service.method(IFACE)
+ def MultipleReturnWithoutSignature(self):
+ # https://bugs.freedesktop.org/show_bug.cgi?id=10174
+ return dbus.String('abc'), dbus.Int32(123)
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='')
+ def BlockFor500ms(self):
+ sleep(0.5)
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='',
+ async_callbacks=('return_cb', 'raise_cb'))
+ def AsyncWait500ms(self, return_cb, raise_cb):
+ def return_from_async_wait():
+ return_cb()
+ return False
+ GLib.timeout_add(500, return_from_async_wait)
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='')
+ def RaiseValueError(self):
+ raise ValueError('Wrong!')
+
+ @dbus.service.method(IFACE, in_signature='bb', out_signature='sii')
+ def TestNoReply(self, succeed, report):
+ """Callers are meant to call this with the NO_REPLY flag,
+ unless they call it with report=true."""
+
+ if report:
+ return ('TestNoReply report',
+ self._times_no_reply_succeeded,
+ self._times_no_reply_failed)
+
+ if succeed:
+ self._times_no_reply_succeeded += 1
+ return (SHOULD_NOT_HAPPEN, 0, 0)
+ else:
+ self._times_no_reply_failed += 1
+ raise ValueError(SHOULD_NOT_HAPPEN)
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='')
+ def RaiseDBusExceptionNoTraceback(self):
+ class ServerError(dbus.DBusException):
+ """Exception representing a normal "environmental" error"""
+ include_traceback = False
+ _dbus_error_name = 'com.example.Networking.ServerError'
+
+ raise ServerError('Server not responding')
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='')
+ def RaiseDBusExceptionWithTraceback(self):
+ class RealityFailure(dbus.DBusException):
+ """Exception representing a programming error"""
+ include_traceback = True
+ _dbus_error_name = 'com.example.Misc.RealityFailure'
+
+ raise RealityFailure('Botched invariant')
+
+ @dbus.service.method(IFACE, in_signature='', out_signature='',
+ async_callbacks=('return_cb', 'raise_cb'))
+ def AsyncRaise(self, return_cb, raise_cb):
+ class Fdo12403Error(dbus.DBusException):
+ _dbus_error_name = 'org.freedesktop.bugzilla.bug12403'
+
+ raise_cb(Fdo12403Error())
+
+
+def main():
+ global session_bus
+ logger.info('getting session bus')
+ session_bus = dbus.SessionBus()
+ logger.info('getting bus name %s', NAME)
+ global_name = dbus.service.BusName(NAME, bus=session_bus)
+ logger.info('making TestObject')
+ object = TestObject(global_name)
+ logger.info('making TestGObject')
+ g_object = TestGObject(global_name)
+ logger.info('making Fallback')
+ fallback_object = Fallback(session_bus)
+ logger.info('creating mainloop')
+ loop = GLib.MainLoop()
+ logger.info('running')
+ loop.run()
+ logger.info('done')
+
+
+if __name__ == '__main__':
+ session_bus = None
+ try:
+ logger.info('entering main')
+ main()
+ except:
+ logger.exception('test-service main failure')
+ raise
diff --git a/test/test-signals.py b/test/test-signals.py
new file mode 100755
index 0000000..458f84b
--- /dev/null
+++ b/test/test-signals.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2004 Red Hat Inc. <http://www.redhat.com/>
+# Copyright (C) 2005-2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import sys
+import os
+import unittest
+import time
+import logging
+
+import dbus
+import _dbus_bindings
+import dbus.glib
+import dbus.service
+import dbus_test_utils
+
+try:
+ from gi.repository import GLib
+except ImportError:
+ print('1..0 # SKIP cannot import GLib')
+ raise SystemExit(0)
+
+logging.basicConfig()
+logging.getLogger().setLevel(1)
+logger = logging.getLogger('test-signals')
+
+if 'DBUS_TEST_UNINSTALLED' in os.environ:
+ builddir = os.path.normpath(os.environ["DBUS_TOP_BUILDDIR"])
+ pydir = os.path.normpath(os.environ["DBUS_TOP_SRCDIR"])
+ pkg = dbus.__file__
+
+ if not pkg.startswith(pydir):
+ raise Exception("DBus modules (%s) are not being picked up from the "
+ "package" % pkg)
+
+ if not _dbus_bindings.__file__.startswith(builddir):
+ raise Exception("DBus modules (%s) are not being picked up from the "
+ "package" % _dbus_bindings.__file__)
+
+NAME = "org.freedesktop.DBus.TestSuitePythonService"
+IFACE = "org.freedesktop.DBus.TestSuiteInterface"
+OBJECT = "/org/freedesktop/DBus/TestSuitePythonObject"
+
+
+class TestSignals(unittest.TestCase):
+ def setUp(self):
+ logger.info('setUp()')
+ self.bus = dbus.SessionBus()
+ self.remote_object = self.bus.get_object(NAME, OBJECT)
+ self.remote_object_fallback_trivial = self.bus.get_object(NAME,
+ OBJECT + '/Fallback')
+ self.remote_object_fallback = self.bus.get_object(NAME,
+ OBJECT + '/Fallback/Badger')
+ self.remote_object_follow = self.bus.get_object(NAME, OBJECT,
+ follow_name_owner_changes=True)
+ self.iface = dbus.Interface(self.remote_object, IFACE)
+ self.iface_follow = dbus.Interface(self.remote_object_follow, IFACE)
+ self.fallback_iface = dbus.Interface(self.remote_object_fallback, IFACE)
+ self.fallback_trivial_iface = dbus.Interface(
+ self.remote_object_fallback_trivial, IFACE)
+ self.in_test = None
+
+ def signal_test_impl(self, iface, name, test_removal=False):
+ self.in_test = name
+ # using append rather than assignment here to avoid scoping issues
+ result = []
+
+ def _timeout_handler():
+ logger.debug('_timeout_handler for %s: current state %s', name, self.in_test)
+ if self.in_test == name:
+ main_loop.quit()
+ def _signal_handler(s, sender, path):
+ logger.debug('_signal_handler for %s: current state %s', name, self.in_test)
+ if self.in_test not in (name, name + '+removed'):
+ return
+ logger.info('Received signal from %s:%s, argument is %r',
+ sender, path, s)
+ result.append('received')
+ main_loop.quit()
+ def _rm_timeout_handler():
+ logger.debug('_timeout_handler for %s: current state %s', name, self.in_test)
+ if self.in_test == name + '+removed':
+ main_loop.quit()
+
+ logger.info('Testing %s', name)
+ match = iface.connect_to_signal('SignalOneString', _signal_handler,
+ sender_keyword='sender',
+ path_keyword='path')
+ logger.info('Waiting for signal...')
+ iface.EmitSignal('SignalOneString', 0)
+ source_id = GLib.timeout_add(1000, _timeout_handler)
+ main_loop.run()
+ if not result:
+ raise AssertionError('Signal did not arrive within 1 second')
+ logger.debug('Removing match')
+ match.remove()
+ GLib.source_remove(source_id)
+ if test_removal:
+ self.in_test = name + '+removed'
+ logger.info('Testing %s', name)
+ result = []
+ iface.EmitSignal('SignalOneString', 0)
+ source_id = GLib.timeout_add(1000, _rm_timeout_handler)
+ main_loop.run()
+ if result:
+ raise AssertionError('Signal should not have arrived, but did')
+ GLib.source_remove(source_id)
+
+ def testFallback(self):
+ self.signal_test_impl(self.fallback_iface, 'Fallback')
+
+ def testFallbackTrivial(self):
+ self.signal_test_impl(self.fallback_trivial_iface, 'FallbackTrivial')
+
+ def testSignal(self):
+ self.signal_test_impl(self.iface, 'Signal')
+
+ def testRemoval(self):
+ self.signal_test_impl(self.iface, 'Removal', True)
+
+ def testSignalAgain(self):
+ self.signal_test_impl(self.iface, 'SignalAgain')
+
+ def testRemovalAgain(self):
+ self.signal_test_impl(self.iface, 'RemovalAgain', True)
+
+ def testSignalF(self):
+ self.signal_test_impl(self.iface_follow, 'Signal')
+
+ def testRemovalF(self):
+ self.signal_test_impl(self.iface_follow, 'Removal', True)
+
+ def testSignalAgainF(self):
+ self.signal_test_impl(self.iface_follow, 'SignalAgain')
+
+ def testRemovalAgainF(self):
+ self.signal_test_impl(self.iface_follow, 'RemovalAgain', True)
+
+if __name__ == '__main__':
+ main_loop = GLib.MainLoop()
+ dbus.glib.init_threads()
+
+ logger.info('Starting unit test')
+ dbus_test_utils.main()
diff --git a/test/test-standalone.py b/test/test-standalone.py
new file mode 100755
index 0000000..055704e
--- /dev/null
+++ b/test/test-standalone.py
@@ -0,0 +1,587 @@
+#!/usr/bin/env python
+
+"""Tests that don't need an active D-Bus connection to run, but can be
+run in isolation.
+"""
+
+# Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from __future__ import unicode_literals
+
+import struct
+import sys
+import os
+import unittest
+
+import _dbus_bindings
+import dbus
+import dbus.lowlevel as lowlevel
+import dbus.types as types
+import dbus_test_utils
+
+
+if 'DBUS_TEST_UNINSTALLED' in os.environ:
+ builddir = os.path.normpath(os.environ["DBUS_TOP_BUILDDIR"])
+ pydir = os.path.normpath(os.environ["DBUS_TOP_SRCDIR"])
+ pkg = dbus.__file__
+
+ if not pkg.startswith(pydir):
+ raise Exception("DBus modules (%s) are not being picked up from the "
+ "package" % pkg)
+
+ if not _dbus_bindings.__file__.startswith(builddir):
+ raise Exception("DBus modules (%s) are not being picked up from the "
+ "package" % _dbus_bindings.__file__)
+
+ assert _dbus_bindings.__version__ == os.environ['DBUS_PYTHON_VERSION'], \
+ '_dbus_bindings was compiled as version %s but Automake says '\
+ 'we should be version %s' \
+ % (_dbus_bindings.__version__, os.environ['DBUS_PYTHON_VERSION'])
+
+assert (_dbus_bindings._python_version & 0xffff0000
+ == sys.hexversion & 0xffff0000), \
+ '_dbus_bindings was compiled for Python %x but this is Python %x, '\
+ 'a different major version'\
+ % (_dbus_bindings._python_version, sys.hexversion)
+
+def uni(x):
+ """Return a Unicode string consisting of the single Unicode character
+ with the given codepoint (represented as a surrogate pair if this is
+ a "narrow" Python build). This is a generalization of unichr().
+
+ uni(0x0001f639) == u'\\U0001f639' in versions where that syntax is
+ supported.
+ """
+ if x <= 0xFFFF:
+ return chr(x)
+ else:
+ return struct.pack('>I', x).decode('utf_32_be')
+
+class TestTypes(unittest.TestCase):
+
+ def test_Dictionary(self):
+ self.assertEqual(types.Dictionary({'foo':'bar'}), {'foo':'bar'})
+ self.assertEqual(types.Dictionary({}, variant_level=2), {})
+ self.assertEqual(types.Dictionary({}, variant_level=2).variant_level, 2)
+
+ def test_Array(self):
+ self.assertEqual(types.Array(['foo','bar']), ['foo','bar'])
+ self.assertEqual(types.Array([], variant_level=2), [])
+ self.assertEqual(types.Array([], variant_level=2).variant_level, 2)
+
+ def test_Double(self):
+ self.assertEqual(types.Double(0.0), 0.0)
+ self.assertEqual(types.Double(0.125, variant_level=2), 0.125)
+ self.assertEqual(types.Double(0.125, variant_level=2).variant_level, 2)
+ self.assertEqual(str(types.Double(0.125)), '0.125')
+ self.assertEqual(float(types.Double(0.125)), 0.125)
+ self.assertIs(type(float(types.Double(0.125))), float)
+
+ def test_Struct(self):
+ x = types.Struct(('',))
+ self.assertEqual(x.variant_level, 0)
+ self.assertEqual(x, ('',))
+ x = types.Struct('abc', variant_level=42)
+ self.assertEqual(x.variant_level, 42)
+ self.assertEqual(x, ('a','b','c'))
+
+ def test_Byte(self):
+ self.assertEqual(types.Byte(b'x', variant_level=2),
+ types.Byte(ord('x')))
+ self.assertEqual(types.Byte(1), 1)
+ self.assertRaises(Exception, lambda: types.Byte(b'ab'))
+ self.assertRaises(TypeError, types.Byte, '\x12xxxxxxxxxxxxx')
+ self.assertEqual(str(types.Byte(b'x')), 'x')
+
+ # Byte from a unicode object: what would that even mean?
+ self.assertRaises(Exception,
+ lambda: types.Byte(b'a'.decode('latin-1')))
+
+ def test_ByteArray(self):
+ self.assertEqual(types.ByteArray(b''), b'')
+
+ def test_object_path_attr(self):
+ class MyObject(object):
+ __dbus_object_path__ = '/foo'
+ from _dbus_bindings import SignalMessage
+ self.assertEqual(SignalMessage.guess_signature(MyObject()), 'o')
+
+ def test_integers(self):
+ subclasses = [int]
+ subclasses = tuple(subclasses)
+ # This is an API guarantee. Note that exactly which of these types
+ # are ints and which of them are longs is *not* guaranteed.
+ for cls in (types.Int16, types.UInt16, types.Int32, types.UInt32,
+ types.Int64, types.UInt64):
+ self.assertTrue(issubclass(cls, subclasses))
+ self.assertTrue(isinstance(cls(0), subclasses))
+ self.assertEqual(cls(0), 0)
+ self.assertEqual(cls(23, variant_level=1), 23)
+ self.assertEqual(cls(23, variant_level=1).variant_level, 1)
+ self.assertEqual(int(cls(42)), 42)
+ self.assertIs(type(int(cls(42))), int)
+ self.assertEqual(str(cls(42)), '42')
+ self.assertIs(type(str(cls(42))), str)
+
+ def test_integer_limits_16(self):
+ self.assertEqual(types.Int16(0x7fff), 0x7fff)
+ self.assertEqual(types.Int16(-0x8000), -0x8000)
+ self.assertEqual(types.UInt16(0xffff), 0xffff)
+ self.assertRaises(Exception, types.Int16, 0x8000)
+ self.assertRaises(Exception, types.Int16, -0x8001)
+ self.assertRaises(Exception, types.UInt16, 0x10000)
+
+ def test_integer_limits_32(self):
+ self.assertEqual(types.Int32(0x7fffffff), 0x7fffffff)
+ self.assertEqual(types.Int32(-0x80000000), -0x80000000)
+ self.assertEqual(types.UInt32(0xffffffff), 0xffffffff)
+ self.assertRaises(Exception, types.Int32, 0x80000000)
+ self.assertRaises(Exception, types.Int32, -0x80000001)
+ self.assertRaises(Exception, types.UInt32, 0x100000000)
+
+ def test_integer_limits_64(self):
+ self.assertEqual(types.Int64(0x7fffffffffffffff),
+ 0x7fffffffffffffff)
+ self.assertEqual(types.Int64(-0x8000000000000000),
+ -0x8000000000000000)
+ self.assertEqual(types.UInt64(0xffffffffffffffff),
+ 0xffffffffffffffff)
+ self.assertRaises(Exception, types.Int64, 0x8000000000000000)
+ self.assertRaises(Exception, types.Int64, -0x8000000000000001)
+ self.assertRaises(Exception, types.UInt64, 0x10000000000000000)
+
+ def test_Signature(self):
+ self.assertRaises(Exception, types.Signature, 'a')
+ self.assertEqual(types.Signature('ab', variant_level=23), 'ab')
+ self.assertTrue(isinstance(types.Signature('ab'), str))
+ self.assertEqual(tuple(types.Signature('ab(xt)a{sv}')),
+ ('ab', '(xt)', 'a{sv}'))
+ self.assertTrue(isinstance(tuple(types.Signature('ab'))[0],
+ types.Signature))
+ self.assertEqual(str(types.Signature('ab')), 'ab')
+ self.assertIs(type(str(types.Signature('ab'))), str)
+
+ def test_string(self):
+ self.assertEqual(types.String('hello', variant_level=23), 'hello')
+ self.assertEqual(types.String('hello', variant_level=23).variant_level, 23)
+ self.assertTrue(isinstance(types.String('hello'), str))
+ self.assertEqual(str(types.String('hello')), 'hello')
+ self.assertIs(type(str(types.String('hello'))), str)
+
+ def test_object_path(self):
+ self.assertRaises(Exception, types.ObjectPath, 'a')
+ self.assertEqual(types.ObjectPath('/ab', variant_level=23), '/ab')
+ self.assertEqual(types.ObjectPath('/ab', variant_level=23).variant_level, 23)
+ self.assertTrue(isinstance(types.ObjectPath('/ab'), str))
+ self.assertEqual(str(types.ObjectPath('/ab')), '/ab')
+ self.assertIs(type(str(types.ObjectPath('/ab'))), str)
+
+ def test_boolean(self):
+ self.assertEqual(types.Boolean(True, variant_level=23), True)
+ self.assertEqual(types.Boolean(True, variant_level=23).variant_level, 23)
+ self.assertEqual(str(types.Boolean(False)), '0')
+ self.assertEqual(str(types.Boolean(True)), '1')
+ self.assertEqual(str(types.Boolean(47)), '1')
+ self.assertEqual(int(types.Boolean(False)), 0)
+ self.assertEqual(int(types.Boolean(True)), 1)
+ self.assertEqual(int(types.Boolean(47)), 1)
+ self.assertIs(type(int(types.Boolean(False))), int)
+ self.assertIs(type(int(types.Boolean(True))), int)
+
+
+class TestMessageMarshalling(unittest.TestCase):
+
+ def test_path(self):
+ s = lowlevel.SignalMessage('/a/b/c', 'foo.bar', 'baz')
+ self.assertEqual(s.get_path(), types.ObjectPath('/a/b/c'))
+ self.assertEqual(type(s.get_path()), types.ObjectPath)
+ self.assertEqual(s.get_path_decomposed(), ['a', 'b', 'c'])
+ # this is true in both major versions: it's a bytestring in
+ # Python 2 and a Unicode string in Python 3
+ self.assertEqual(type(s.get_path_decomposed()[0]), str)
+ self.assertTrue(s.has_path('/a/b/c'))
+ self.assertFalse(s.has_path('/a/b'))
+ self.assertFalse(s.has_path('/a/b/c/d'))
+
+ s = lowlevel.SignalMessage('/', 'foo.bar', 'baz')
+ self.assertEqual(s.get_path(), types.ObjectPath('/'))
+ self.assertEqual(s.get_path().__class__, types.ObjectPath)
+ self.assertEqual(s.get_path_decomposed(), [])
+ self.assertTrue(s.has_path('/'))
+ self.assertFalse(s.has_path(None))
+
+ def test_sender(self):
+ s = lowlevel.SignalMessage('/a/b/c', 'foo.bar', 'baz')
+ self.assertEqual(s.get_sender(), None)
+ self.assertFalse(s.has_sender(':1.23'))
+ s.set_sender(':1.23')
+ self.assertEqual(s.get_sender(), ':1.23')
+ # bytestring in Python 2, Unicode string in Python 3
+ self.assertEqual(type(s.get_sender()), str)
+ self.assertTrue(s.has_sender(':1.23'))
+
+ def test_destination(self):
+ s = lowlevel.SignalMessage('/a/b/c', 'foo.bar', 'baz')
+ self.assertEqual(s.get_destination(), None)
+ self.assertFalse(s.has_destination(':1.23'))
+ s.set_destination(':1.23')
+ self.assertEqual(s.get_destination(), ':1.23')
+ # bytestring in Python 2, Unicode string in Python 3
+ self.assertEqual(type(s.get_destination()), str)
+ self.assertTrue(s.has_destination(':1.23'))
+
+ def test_interface(self):
+ s = lowlevel.SignalMessage('/a/b/c', 'foo.bar', 'baz')
+ self.assertEqual(s.get_interface(), 'foo.bar')
+ # bytestring in Python 2, Unicode string in Python 3
+ self.assertEqual(type(s.get_interface()), str)
+ self.assertTrue(s.has_interface('foo.bar'))
+
+ def test_member(self):
+ s = lowlevel.SignalMessage('/a/b/c', 'foo.bar', 'baz')
+ self.assertEqual(s.get_member(), 'baz')
+ # bytestring in Python 2, Unicode string in Python 3
+ self.assertEqual(type(s.get_member()), str)
+ self.assertTrue(s.has_member('baz'))
+
+ def test_count(self):
+ from _dbus_bindings import SignalMessage
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ try:
+ s.append('a', signature='ss')
+ except TypeError:
+ pass
+ else:
+ raise AssertionError('Appending too few things in a message '
+ 'should fail')
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ try:
+ s.append('a','b','c', signature='ss')
+ except TypeError:
+ pass
+ else:
+ raise AssertionError('Appending too many things in a message '
+ 'should fail')
+
+ def test_append(self):
+ aeq = self.assertEqual
+ from _dbus_bindings import SignalMessage
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ s.append([types.Byte(1)], signature='ay')
+ aeq(s.get_signature(), 'ay')
+ aeq(s.get_args_list(), [[types.Byte(1)]])
+
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ s.append([], signature='ay')
+ aeq(s.get_args_list(), [[]])
+
+ def test_append_Byte(self):
+ aeq = self.assertEqual
+ from _dbus_bindings import SignalMessage
+
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ s.append(0xFE, signature='y')
+ aeq(s.get_args_list(), [types.Byte(0xFE)])
+
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ s.append(b'\xfe', signature='y')
+ aeq(s.get_args_list(), [types.Byte(0xFE)])
+
+ # appending a unicode object (including str in Python 3)
+ # is not allowed
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ self.assertRaises(Exception,
+ lambda: s.append('a'.decode('latin-1'), signature='y'))
+
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ self.assertRaises(Exception,
+ lambda: s.append(b'ab', signature='y'))
+
+ def test_append_ByteArray(self):
+ aeq = self.assertEqual
+ from _dbus_bindings import SignalMessage
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ s.append(types.ByteArray(b'ab'), signature='ay')
+ aeq(s.get_args_list(), [[types.Byte(b'a'), types.Byte(b'b')]])
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ s.append(types.ByteArray(b'ab'), signature='av')
+ aeq(s.get_args_list(), [[types.Byte(b'a'), types.Byte(b'b')]])
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ s.append(types.ByteArray(b''), signature='ay')
+ aeq(s.get_args_list(), [[]])
+ aeq(s.get_args_list(byte_arrays=True), [types.ByteArray(b'')])
+
+ def test_append_Variant(self):
+ aeq = self.assertEqual
+ from _dbus_bindings import SignalMessage
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ s.append(types.Int32(1, variant_level=0),
+ types.String('a', variant_level=42),
+ types.Array([types.Byte(b'a', variant_level=1),
+ types.UInt32(123, variant_level=1)],
+ signature='v'),
+ signature='vvv')
+ aeq(s.get_signature(), 'vvv')
+ args = s.get_args_list()
+ aeq(args[0].__class__, types.Int32)
+ aeq(args[0].variant_level, 1)
+ aeq(args[1].__class__, types.String)
+ aeq(args[1].variant_level, 42)
+ aeq(args[2].__class__, types.Array)
+ aeq(args[2].variant_level, 1)
+ aeq(args[2].signature, 'v')
+
+ def test_guess_signature(self):
+ aeq = self.assertEqual
+ from _dbus_bindings import Message
+ aeq(Message.guess_signature(('a','b')), '(ss)')
+ aeq(Message.guess_signature('a','b'), 'ss')
+ aeq(Message.guess_signature(['a','b']), 'as')
+ aeq(Message.guess_signature(('a',)), '(s)')
+ aeq(Message.guess_signature('abc'), 's')
+ aeq(Message.guess_signature(types.Int32(123)), 'i')
+ aeq(Message.guess_signature(types.ByteArray(b'abc')), 'ay')
+ aeq(Message.guess_signature(('a',)), '(s)')
+ aeq(Message.guess_signature(['a']), 'as')
+ aeq(Message.guess_signature({'a':'b'}), 'a{ss}')
+ aeq(Message.guess_signature(types.ObjectPath('/')), 'o')
+ aeq(Message.guess_signature(types.Signature('x')), 'g')
+
+ def test_guess_signature_python_ints(self):
+ aeq = self.assertEqual
+ from _dbus_bindings import Message
+ aeq(Message.guess_signature(7), 'i')
+
+ def test_guess_signature_dbus_types(self):
+ aeq = self.assertEqual
+ from _dbus_bindings import Message
+ gs = Message.guess_signature
+ aeq(gs(types.Dictionary({'a':'b'})), 'a{ss}')
+ aeq(gs(types.Dictionary({'a':'b'}, signature='sv')), 'a{sv}')
+ aeq(gs(types.Dictionary({}, signature='iu')), 'a{iu}')
+ aeq(gs(types.Array([types.Int32(1)])), 'ai')
+ aeq(gs(types.Array([types.Int32(1)], signature='u')), 'au')
+
+ def test_get_args_options(self):
+ aeq = self.assertEqual
+ s = _dbus_bindings.SignalMessage('/', 'foo.bar', 'baz')
+ s.append(b'b', b'bytes', -1, 1, 'str', 'var', signature='yayiusv')
+ aeq(s.get_args_list(), [
+ ord('b'),
+ [ord('b'),ord('y'),ord('t'),ord('e'), ord('s')],
+ -1, 1, 'str', 'var'
+ ])
+ byte, bytes, int32, uint32, string, variant = s.get_args_list()
+ aeq(byte.__class__, types.Byte)
+ aeq(bytes.__class__, types.Array)
+ aeq(bytes[0].__class__, types.Byte)
+ aeq(int32.__class__, types.Int32)
+ aeq(uint32.__class__, types.UInt32)
+ aeq(string.__class__, types.String)
+ aeq(string.variant_level, 0)
+ aeq(variant.__class__, types.String)
+ aeq(variant.variant_level, 1)
+
+ byte, bytes, int32, uint32, string, variant = s.get_args_list(
+ byte_arrays=True)
+ aeq(byte.__class__, types.Byte)
+ aeq(bytes.__class__, types.ByteArray)
+ aeq(bytes, b'bytes')
+ aeq(bytes[0].__class__, int)
+ aeq(int32.__class__, types.Int32)
+ aeq(uint32.__class__, types.UInt32)
+ aeq(string.__class__, types.String)
+ aeq(variant.__class__, types.String)
+ aeq(variant.variant_level, 1)
+
+ byte, bytes, int32, uint32, string, variant = s.get_args_list()
+ aeq(byte.__class__, types.Byte)
+ aeq(bytes.__class__, types.Array)
+ aeq(bytes[0].__class__, types.Byte)
+ aeq(int32.__class__, types.Int32)
+ aeq(uint32.__class__, types.UInt32)
+ aeq(string, 'str')
+ aeq(variant.variant_level, 1)
+ aeq(variant, 'var')
+
+ def test_object_path_attr(self):
+ from _dbus_bindings import SignalMessage
+ class MyObject(object):
+ __dbus_object_path__ = '/foo'
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ s.append(MyObject(), signature='o')
+ s.append(MyObject())
+ self.assertEqual(s.get_args_list(), ['/foo', '/foo'])
+
+ def test_struct(self):
+ from _dbus_bindings import SignalMessage
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ try:
+ s.append(('a',), signature='(ss)')
+ except TypeError:
+ pass
+ else:
+ raise AssertionError('Appending too few things in a struct '
+ 'should fail')
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ try:
+ s.append(('a','b','c'), signature='(ss)')
+ except TypeError:
+ pass
+ else:
+ raise AssertionError('Appending too many things in a struct '
+ 'should fail')
+
+ def test_utf8(self):
+ from _dbus_bindings import SignalMessage
+
+ for bad in [
+ uni(0xD800),
+ b'\xed\xa0\x80',
+ ]:
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ try:
+ s.append(bad, signature='s')
+ except UnicodeError:
+ pass
+ else:
+ raise AssertionError('Appending %r should fail' % bad)
+ for good in [
+ uni(0xfdcf),
+ uni(0xfdf0),
+ uni(0xfeff),
+ uni(0x0001feff),
+ uni(0x00020000),
+ uni(0x0007feff),
+ uni(0x00080000),
+ uni(0x0010feff),
+ ]:
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ s.append(good, signature='s')
+ s.append(good.encode('utf-8'), signature='s')
+ for noncharacter in [
+ uni(0xFDD0),
+ b'\xef\xb7\x90',
+ uni(0xFDD7),
+ b'\xef\xb7\x97',
+ uni(0xFDEF),
+ b'\xef\xb7\xaf',
+ uni(0xFFFE),
+ b'\xef\xbf\xbe',
+ uni(0xFFFF),
+ b'\xef\xbf\xbf',
+ uni(0x0001FFFE),
+ b'\xf0\x9f\xbf\xbe',
+ uni(0x0001FFFF),
+ b'\xf0\x9f\xbf\xbf',
+ uni(0x0007FFFE),
+ b'\xf1\xbf\xbf\xbe',
+ uni(0x0007FFFF),
+ b'\xf1\xbf\xbf\xbf',
+ uni(0x0010FFFE),
+ b'\xf4\x8f\xbf\xbe',
+ uni(0x0010FFFF),
+ b'\xf4\x8f\xbf\xbf',
+ ]:
+ s = SignalMessage('/', 'foo.bar', 'baz')
+ try:
+ s.append(noncharacter, signature='s')
+ except UnicodeError:
+ raise AssertionError(
+ 'Appending %r should succeed' % noncharacter)
+ else:
+ pass # libdbus >= 1.6.10 allows noncharacters
+
+ @unittest.skipIf(sys.platform.startswith("win"), "requires Unix")
+ def test_unix_fd(self):
+ plain_fd = os.open('/dev/null', os.O_RDONLY)
+
+ try:
+ with open('/dev/null', 'r') as file_like_object:
+ fd = types.UnixFd(plain_fd)
+ self.assertEqual(fd.variant_level, 0)
+ other = fd.take()
+ os.close(other)
+
+ with self.assertRaises(ValueError):
+ fd.take()
+
+ fd = types.UnixFd(plain_fd, variant_level=42)
+ self.assertEqual(fd.variant_level, 42)
+
+ fd = types.UnixFd(file_like_object)
+ self.assertEqual(fd.variant_level, 0)
+
+ fd = types.UnixFd(file_like_object, variant_level=42)
+ self.assertEqual(fd.variant_level, 42)
+
+ with self.assertRaises(TypeError):
+ int(fd)
+
+ with self.assertRaises(TypeError):
+ types.UnixFd(plain_fd, invalid_kwarg='nope')
+
+ with self.assertRaises(TypeError):
+ types.UnixFd(plain_fd, variant_level='nope')
+
+ with self.assertRaises(ValueError):
+ types.UnixFd(plain_fd, variant_level=-1)
+ finally:
+ os.close(plain_fd)
+
+class TestMatching(unittest.TestCase):
+ def setUp(self):
+ from _dbus_bindings import SignalMessage
+ from dbus.connection import SignalMatch
+ self._message = SignalMessage('/', 'a.b', 'c')
+ class FakeConn(object): pass
+ def ignore_cb(*args, **kws): pass
+ self._match = SignalMatch(FakeConn(), None, '/', None, None,
+ ignore_cb, arg0='/')
+
+ def test_string_match(self):
+ self._message.append('/', signature='s')
+ self.assertTrue(self._match.maybe_handle_message(self._message))
+
+ def test_object_path_no_match(self):
+ self._message.append('/', signature='o')
+ self.assertFalse(self._match.maybe_handle_message(self._message))
+
+class TestVersion(unittest.TestCase):
+ if sys.version_info[:2] < (2, 7):
+ def assertGreater(self, first, second):
+ self.assertTrue(first > second)
+
+ def assertLess(self, first, second):
+ self.assertTrue(first < second)
+
+ def test_version(self):
+ self.assertGreater(dbus.version, (0, 41))
+ self.assertLess(dbus.version, (23, 0))
+ self.assertGreater(dbus.__version__, '0')
+ self.assertLess(dbus.__version__, '9')
+
+if __name__ == '__main__':
+ dbus_test_utils.main()
diff --git a/test/test-unusable-main-loop.py b/test/test-unusable-main-loop.py
new file mode 100755
index 0000000..54d2a16
--- /dev/null
+++ b/test/test-unusable-main-loop.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from __future__ import print_function
+
+import unittest
+
+import dbus
+import dbus_test_utils
+
+from dbus_py_test import UnusableMainLoop
+
+class Test(unittest.TestCase):
+ def test_unusable_main_loop(self):
+ UnusableMainLoop(set_as_default=True)
+ self.assertRaises(ValueError, lambda: dbus.SessionBus())
+
+if __name__ == '__main__':
+ dbus_test_utils.main()
diff --git a/test/tmp-session-bus.conf.in b/test/tmp-session-bus.conf.in
new file mode 100644
index 0000000..1d79e29
--- /dev/null
+++ b/test/tmp-session-bus.conf.in
@@ -0,0 +1,23 @@
+<!-- @configure_input@ -->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <!-- Our well-known bus type, don't change this -->
+ <type>session</type>
+ <listen>unix:tmpdir=/tmp</listen>
+
+ <servicedir>@G_TEST_BUILDDIR@/test</servicedir>
+
+ <policy context="default">
+ <!-- Allow everything to be sent -->
+ <allow send_destination="*"/>
+ <!-- Allow everything to be received -->
+ <allow eavesdrop="true"/>
+ <!-- Allow anyone to own anything -->
+ <allow own="*"/>
+ <!-- Allow unrequested replies -->
+ <allow send_destination="*" send_requested_reply="false"/>
+ </policy>
+
+</busconfig>
+<!-- vim:set ft=xml sts=2 sw=2 et: -->
diff --git a/test/wait-for-name.py b/test/wait-for-name.py
new file mode 100755
index 0000000..55b1800
--- /dev/null
+++ b/test/wait-for-name.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+# Copyright © 2016 Simon McVittie
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+import sys
+
+from gi.repository import GLib
+
+import dbus
+import dbus.mainloop.glib
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ timed_out = False
+ name = sys.argv[1]
+ bus = dbus.SessionBus()
+ loop = GLib.MainLoop()
+
+ def time_out(*args):
+ global timed_out
+ timed_out = True
+ loop.quit()
+
+ GLib.timeout_add_seconds(30, time_out)
+
+ def name_cb(owner):
+ if owner:
+ loop.quit()
+
+ bus.watch_name_owner(name, name_cb)
+
+ loop.run()
+
+ if timed_out:
+ raise SystemExit('timed out')
diff --git a/tools/check-c-style.sh b/tools/check-c-style.sh
new file mode 100644
index 0000000..b8fef94
--- /dev/null
+++ b/tools/check-c-style.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+# Copyright 2006-2022 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+fail=0
+
+/bin/sh "${top_srcdir}"/tools/check-whitespace.sh "$@" || fail=$?
+
+# at the moment we're not actually checking much C style here... to be added
+
+if test -n "$CHECK_FOR_LONG_LINES"
+then
+ if egrep -n '.{80,}' "$@"
+ then
+ echo "^^^ The above files contain long lines"
+ fail=1
+ fi
+fi
+
+exit $fail
diff --git a/tools/check-coding-style.mk b/tools/check-coding-style.mk
new file mode 100644
index 0000000..7f8ef05
--- /dev/null
+++ b/tools/check-coding-style.mk
@@ -0,0 +1,28 @@
+# Copyright 2006-2022 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+check-local::
+ @fail=0; \
+ cd $(srcdir) || exit $$?; \
+ if test -n "$(check_misc_sources)"; then \
+ echo check-coding-style.mk: checking misc sources...; \
+ top_srcdir=$(abs_top_srcdir) \
+ sh $(abs_top_srcdir)/tools/check-whitespace.sh \
+ $(check_misc_sources) || fail=1; \
+ fi; \
+ if test -n "$(check_py_sources)"; then \
+ echo check-coding-style.mk: checking Python sources...; \
+ top_srcdir=$(abs_top_srcdir) \
+ sh $(abs_top_srcdir)/tools/check-py-style.sh \
+ $(check_py_sources) || fail=1; \
+ fi;\
+ if test -n "$(check_c_sources)"; then \
+ echo check-coding-style.mk: checking C sources...; \
+ top_srcdir=$(abs_top_srcdir) \
+ sh $(abs_top_srcdir)/tools/check-c-style.sh \
+ $(check_c_sources) || fail=1; \
+ fi;\
+ if test yes = "@ENABLE_CODING_STYLE_CHECKS@"; then \
+ exit "$$fail";\
+ else \
+ exit 0;\
+ fi
diff --git a/tools/check-py-style.sh b/tools/check-py-style.sh
new file mode 100644
index 0000000..8dd6b0b
--- /dev/null
+++ b/tools/check-py-style.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+# Copyright 2006-2022 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+fail=0
+
+/bin/sh "${top_srcdir}"/tools/check-whitespace.sh "$@" || fail=$?
+
+# at the moment we're not actually checking much Python style here...
+# to be added
+
+if test -n "$CHECK_FOR_LONG_LINES"
+then
+ if egrep -n '.{80,}' "$@"
+ then
+ echo "^^^ The above files contain long lines"
+ fail=1
+ fi
+fi
+
+exit $fail
diff --git a/tools/check-whitespace.sh b/tools/check-whitespace.sh
new file mode 100644
index 0000000..c8e39c7
--- /dev/null
+++ b/tools/check-whitespace.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+# Copyright 2006-2022 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+
+fail=0
+
+if grep -n ' $' "$@"
+then
+ echo "^^^ The above files contain unwanted trailing spaces"
+ fail=1
+fi
+
+if grep -n ' ' "$@"
+then
+ echo "^^^ The above files contain tabs"
+ fail=1
+fi
+
+exit $fail
diff --git a/tools/ci-Dockerfile.in b/tools/ci-Dockerfile.in
new file mode 100644
index 0000000..83e1d86
--- /dev/null
+++ b/tools/ci-Dockerfile.in
@@ -0,0 +1,12 @@
+# Copyright 2006-2022 Collabora Ltd.
+# SPDX-License-Identifier: MIT
+FROM @ci_docker@
+ENV container docker
+
+ADD tools/ci-install.sh /ci-install.sh
+RUN ci_suite="@ci_suite@" ci_distro="@ci_distro@" ci_in_docker=yes dbus_ci_system_python="@dbus_ci_system_python@" /ci-install.sh
+
+ADD . /home/user/ci
+RUN chown -R user:user /home/user/ci
+WORKDIR /home/user/ci
+USER user
diff --git a/tools/ci-build.sh b/tools/ci-build.sh
new file mode 100755
index 0000000..a24798a
--- /dev/null
+++ b/tools/ci-build.sh
@@ -0,0 +1,98 @@
+#!/bin/sh
+
+# Copyright © 2016 Simon McVittie
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+set -e
+set -x
+
+NULL=
+srcdir="$(pwd)"
+builddir="$(mktemp -d -t "builddir.XXXXXX")"
+prefix="$(mktemp -d -t "prefix.XXXXXX")"
+
+if [ -z "$dbus_ci_parallel" ]; then
+ dbus_ci_parallel=2
+fi
+
+if [ -n "$ci_docker" ]; then
+ exec docker run \
+ --env=ci_distro="${ci_distro}" \
+ --env=ci_docker="" \
+ --env=ci_suite="${ci_suite}" \
+ --env=dbus_ci_parallel="${dbus_ci_parallel}" \
+ --env=dbus_ci_system_python="${dbus_ci_system_python-}" \
+ --privileged \
+ ci-image \
+ tools/ci-build.sh \
+ "$@"
+fi
+
+if [ -n "$dbus_ci_system_python" ]; then
+ # Reset to standard paths to use the Ubuntu version of python
+ unset LDFLAGS
+ unset PYTHONPATH
+ unset PYTHON_CFLAGS
+ unset PYTHON_CONFIGURE_OPTS
+ unset VIRTUAL_ENV
+ export PATH=/usr/bin:/bin
+ export PYTHON="$(command -v "$dbus_ci_system_python")"
+fi
+
+NOCONFIGURE=1 ./autogen.sh
+
+e=0
+(
+ cd "$builddir" && "${srcdir}/configure" \
+ --enable-installed-tests \
+ --prefix="$prefix" \
+ --with-python-prefix='${prefix}' \
+ --with-python-exec-prefix='${exec_prefix}' \
+ "$@" \
+ ${NULL}
+) || e=1
+if [ "x$e" != x0 ]; then
+ cat "$builddir/config.log"
+fi
+test "x$e" = x0
+
+make="make -j${dbus_ci_parallel} V=1 VERBOSE=1"
+
+$make -C "$builddir"
+$make -C "$builddir" check
+$make -C "$builddir" distcheck
+$make -C "$builddir" install
+( cd "$prefix" && find . -ls )
+
+dbus_ci_pyversion="$(${PYTHON:-python3} -c 'import sysconfig; print(sysconfig.get_config_var("VERSION"))')"
+export PYTHONPATH="$prefix/lib/python$dbus_ci_pyversion/site-packages:$PYTHONPATH"
+export XDG_DATA_DIRS="$prefix/share:/usr/local/share:/usr/share"
+gnome-desktop-testing-runner dbus-python
+
+# re-run the tests with dbus-python only installed via pip
+if [ -n "$VIRTUAL_ENV" ]; then
+ rm -fr "${prefix}/lib/python$dbus_ci_pyversion/site-packages"
+ pip install -vvv "${builddir}"/dbus-python-*.tar.gz
+ gnome-desktop-testing-runner dbus-python
+fi
diff --git a/tools/ci-install.sh b/tools/ci-install.sh
new file mode 100755
index 0000000..2a03179
--- /dev/null
+++ b/tools/ci-install.sh
@@ -0,0 +1,217 @@
+#!/bin/bash
+
+# Copyright © 2015-2018 Collabora Ltd.
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+set -euo pipefail
+set -x
+
+NULL=
+
+# ci_distro:
+# OS distribution in which we are testing
+# Typical values: ubuntu, debian; maybe fedora in future
+: "${ci_distro:=ubuntu}"
+
+# ci_docker:
+# If non-empty, this is the name of a Docker image. ci-install.sh will
+# fetch it with "docker pull" and use it as a base for a new Docker image
+# named "ci-image" in which we will do our testing.
+: "${ci_docker:=}"
+
+# ci_host:
+# Either "native", or an Autoconf --host argument to cross-compile
+# the package (not currently supported for dbus-python)
+: "${ci_host:=native}"
+
+# ci_in_docker:
+# Used internally by ci-install.sh. If yes, we are inside the Docker image
+# (ci_docker is empty in this case).
+: "${ci_in_docker:=no}"
+
+# ci_suite:
+# OS suite (release, branch) in which we are testing.
+# Typical values for ci_distro=debian: sid, buster
+# Typical values for ci_distro=fedora might be 25, rawhide
+: "${ci_suite:=xenial}"
+
+if [ $(id -u) = 0 ]; then
+ sudo=
+else
+ sudo=sudo
+fi
+
+have_system_sphinx=
+have_system_tappy=
+
+if [ -n "$ci_docker" ]; then
+ sed \
+ -e "s/@ci_distro@/${ci_distro}/" \
+ -e "s/@ci_docker@/${ci_docker}/" \
+ -e "s/@ci_suite@/${ci_suite}/" \
+ -e "s/@dbus_ci_system_python@/${dbus_ci_system_python-}/" \
+ < tools/ci-Dockerfile.in > Dockerfile
+ exec docker build -t ci-image .
+fi
+
+if [ -n "${dbus_ci_system_python-}" ]; then
+ if [ -z "${dbus_ci_system_python_module_suffix-}" ]; then
+ case "$dbus_ci_system_python}" in
+ (*-dbg)
+ dbus_ci_system_python_module_suffix=-dbg
+ ;;
+ (*)
+ dbus_ci_system_python_module_suffix=
+ ;;
+ esac
+ fi
+fi
+
+case "$ci_distro" in
+ (debian|ubuntu)
+ # Don't ask questions, just do it
+ sudo="$sudo env DEBIAN_FRONTEND=noninteractive"
+
+ # Debian Docker images use httpredir.debian.org but it seems to be
+ # unreliable; use a CDN instead
+ $sudo sed -i -e 's/httpredir\.debian\.org/deb.debian.org/g' \
+ /etc/apt/sources.list
+
+ $sudo apt-get -qq -y update
+
+ $sudo apt-get -qq -y install --no-install-recommends \
+ autoconf \
+ autoconf-archive \
+ automake \
+ autotools-dev \
+ ccache \
+ dbus \
+ debhelper \
+ dh-autoreconf \
+ docbook-xml \
+ docbook-xsl \
+ gcc \
+ gnome-desktop-testing \
+ libdbus-1-dev \
+ libglib2.0-dev \
+ libtool \
+ make \
+ sudo \
+ wget \
+ xmlto \
+ ${NULL}
+
+ if [ -n "${dbus_ci_system_python-}" ]; then
+ sudo apt-get -qq -y install \
+ ${dbus_ci_system_python} \
+ ${dbus_ci_system_python%-dbg}-dev \
+ python3-docutils \
+ python3-gi${dbus_ci_system_python_module_suffix} \
+ python3-pip \
+ python3-setuptools \
+ ${NULL}
+
+ if [ "$dbus_ci_system_python" = python ]; then
+ sudo apt-get -qq -y install python-gobject-2
+ fi
+
+ case "$ci_suite" in
+ (jessie|xenial)
+ ;;
+
+ (*)
+ $sudo apt-get -qq -y install python3-tap
+ have_system_tappy=yes
+ ;;
+ esac
+
+ case "$ci_suite" in
+ (jessie|xenial|stretch|bionic)
+ ;;
+
+ (*)
+ $sudo apt-get -qq -y install \
+ python3-sphinx \
+ python3-sphinx-rtd-theme \
+ ${NULL}
+ have_system_sphinx=yes
+ ;;
+ esac
+ fi
+
+ if [ "$ci_in_docker" = yes ]; then
+ # Add the user that we will use to do the build inside the
+ # Docker container, and let them use sudo
+ adduser --disabled-password --gecos "" user
+ echo "user ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/nopasswd
+ chmod 0440 /etc/sudoers.d/nopasswd
+ fi
+
+ case "$ci_suite" in
+ (xenial)
+ # autoconf-archive in Ubuntu 16.04 is too old, use the one
+ # from Debian 9 instead
+ wget http://deb.debian.org/debian/pool/main/a/autoconf-archive/autoconf-archive_20160916-1_all.deb
+ $sudo dpkg -i autoconf-archive_*_all.deb
+ rm autoconf-archive_*_all.deb
+ ;;
+ esac
+ ;;
+
+ (*)
+ echo "Don't know how to set up ${ci_distro}" >&2
+ exit 1
+ ;;
+esac
+
+if [ -n "$have_system_sphinx" ]; then
+ :
+elif [ -n "${dbus_ci_system_python-}" ]; then
+ "$dbus_ci_system_python" -m pip install --user \
+ sphinx \
+ sphinx_rtd_theme \
+ ${NULL}
+else
+ pip install \
+ sphinx \
+ sphinx_rtd_theme \
+ ${NULL}
+fi
+
+if [ -n "$have_system_tappy" ]; then
+ :
+elif "${PYTHON:-${dbus_ci_system_python-false}}" -c 'import sys; exit(sys.version_info[0] > 2)'; then
+ # Don't install tap.py for Python 2
+ :
+elif [ -n "${dbus_ci_system_python-}" ]; then
+ "$dbus_ci_system_python" -m pip install --user \
+ tap.py \
+ ${NULL}
+else
+ pip install \
+ tap.py \
+ ${NULL}
+fi
+
+# vim:set sw=4 sts=4 et: