Imported Upstream version 3.30.1 upstream/3.30.1
authorHyunjee Kim <hj0426.kim@samsung.com>
Fri, 10 Apr 2020 00:37:57 +0000 (09:37 +0900)
committerHyunjee Kim <hj0426.kim@samsung.com>
Fri, 10 Apr 2020 00:38:10 +0000 (09:38 +0900)
Change-Id: Ieb40b7008d6534e99f1ac8970c8536776e1db06b
Signed-off-by: Hyunjee Kim <hj0426.kim@samsung.com>
318 files changed:
.gitlab-ci.yml [new file with mode: 0644]
.gitlab-ci/Dockerfile [new file with mode: 0644]
.gitlab-ci/Dockerfile.old [new file with mode: 0644]
.gitlab-ci/README.rst [new file with mode: 0644]
.gitlab-ci/coverage-docker.sh [new file with mode: 0755]
.gitlab-ci/fixup-cov-paths.py [new file with mode: 0644]
.gitlab-ci/run-docker-old.sh [new file with mode: 0755]
.gitlab-ci/run-docker.sh [new file with mode: 0755]
.gitlab-ci/test-docker-old.sh [new file with mode: 0755]
.gitlab-ci/test-docker.sh [new file with mode: 0755]
.gitlab-ci/test-flatpak.sh [new file with mode: 0755]
.gitlab-ci/test-msys2.sh [new file with mode: 0755]
COPYING [new file with mode: 0644]
MANIFEST.in [new file with mode: 0644]
NEWS [new file with mode: 0644]
PKG-INFO [new file with mode: 0644]
PKG-INFO.in [new file with mode: 0644]
PyGObject.egg-info/PKG-INFO [new file with mode: 0644]
PyGObject.egg-info/SOURCES.txt [new file with mode: 0644]
PyGObject.egg-info/dependency_links.txt [new file with mode: 0644]
PyGObject.egg-info/not-zip-safe [new file with mode: 0644]
PyGObject.egg-info/requires.txt [new file with mode: 0644]
PyGObject.egg-info/top_level.txt [new file with mode: 0644]
README.rst [new file with mode: 0644]
docs/Makefile [new file with mode: 0644]
docs/bugs_repo.rst [new file with mode: 0644]
docs/changelog.rst [new file with mode: 0644]
docs/conf.py [new file with mode: 0644]
docs/contact.rst [new file with mode: 0644]
docs/devguide/building_testing.rst [new file with mode: 0644]
docs/devguide/dev_environ.rst [new file with mode: 0644]
docs/devguide/index.rst [new file with mode: 0644]
docs/devguide/override_guidelines.rst [new file with mode: 0644]
docs/devguide/overview.rst [new file with mode: 0644]
docs/devguide/style_guide.rst [new file with mode: 0644]
docs/extra.css [new file with mode: 0644]
docs/further.rst [new file with mode: 0644]
docs/getting_started.rst [new file with mode: 0644]
docs/guide/api/api.rst [new file with mode: 0644]
docs/guide/api/basic_types.rst [new file with mode: 0644]
docs/guide/api/error_handling.rst [new file with mode: 0644]
docs/guide/api/flags_enums.rst [new file with mode: 0644]
docs/guide/api/gobject.rst [new file with mode: 0644]
docs/guide/api/index.rst [new file with mode: 0644]
docs/guide/api/properties.rst [new file with mode: 0644]
docs/guide/api/signals.rst [new file with mode: 0644]
docs/guide/cairo_integration.rst [new file with mode: 0644]
docs/guide/code/cairo-demo.py [new file with mode: 0755]
docs/guide/debug_profile.rst [new file with mode: 0644]
docs/guide/deploy.rst [new file with mode: 0644]
docs/guide/faq.rst [new file with mode: 0644]
docs/guide/images/cairo_integration.png [new file with mode: 0644]
docs/guide/index.rst [new file with mode: 0644]
docs/guide/porting.rst [new file with mode: 0644]
docs/guide/testing.rst [new file with mode: 0644]
docs/guide/threading.rst [new file with mode: 0644]
docs/icons.rst [new file with mode: 0644]
docs/images/LICENSE [new file with mode: 0644]
docs/images/favicon.ico [new file with mode: 0644]
docs/images/logo.svg [new file with mode: 0644]
docs/images/overview.dia [new file with mode: 0644]
docs/images/overview.svg [new file with mode: 0644]
docs/images/pygobject-small.svg [new file with mode: 0644]
docs/images/pygobject.svg [new file with mode: 0644]
docs/images/start_linux.png [new file with mode: 0644]
docs/images/start_macos.png [new file with mode: 0644]
docs/images/start_windows.png [new file with mode: 0644]
docs/index.rst [new file with mode: 0644]
docs/maintguide.rst [new file with mode: 0644]
docs/packagingguide.rst [new file with mode: 0644]
examples/cairo-demo.py [new file with mode: 0755]
examples/demo/demo.py [new file with mode: 0755]
examples/demo/demos/Css/__init__.py [new file with mode: 0644]
examples/demo/demos/Css/css_accordion.py [new file with mode: 0644]
examples/demo/demos/Css/css_basics.py [new file with mode: 0644]
examples/demo/demos/Css/css_multiplebgs.py [new file with mode: 0644]
examples/demo/demos/Entry/__init__.py [new file with mode: 0644]
examples/demo/demos/Entry/entry_buffer.py [new file with mode: 0644]
examples/demo/demos/Entry/entry_completion.py [new file with mode: 0644]
examples/demo/demos/Entry/search_entry.py [new file with mode: 0644]
examples/demo/demos/IconView/__init__.py [new file with mode: 0644]
examples/demo/demos/IconView/iconviewbasics.py [new file with mode: 0644]
examples/demo/demos/IconView/iconviewedit.py [new file with mode: 0644]
examples/demo/demos/TreeView/__init__.py [new file with mode: 0644]
examples/demo/demos/TreeView/liststore.py [new file with mode: 0644]
examples/demo/demos/TreeView/treemodel_filelist.py [new file with mode: 0644]
examples/demo/demos/TreeView/treemodel_filetree.py [new file with mode: 0644]
examples/demo/demos/TreeView/treemodel_large.py [new file with mode: 0644]
examples/demo/demos/__init__.py [new file with mode: 0644]
examples/demo/demos/appwindow.py [new file with mode: 0644]
examples/demo/demos/assistant.py [new file with mode: 0644]
examples/demo/demos/builder.py [new file with mode: 0644]
examples/demo/demos/button_box.py [new file with mode: 0644]
examples/demo/demos/clipboard.py [new file with mode: 0644]
examples/demo/demos/colorselector.py [new file with mode: 0644]
examples/demo/demos/combobox.py [new file with mode: 0644]
examples/demo/demos/data/alphatest.png [new file with mode: 0644]
examples/demo/demos/data/apple-red.png [new file with mode: 0644]
examples/demo/demos/data/background.jpg [new file with mode: 0644]
examples/demo/demos/data/brick.png [new file with mode: 0644]
examples/demo/demos/data/brick2.png [new file with mode: 0644]
examples/demo/demos/data/css_accordion.css [new file with mode: 0644]
examples/demo/demos/data/css_basics.css [new file with mode: 0644]
examples/demo/demos/data/css_multiplebgs.css [new file with mode: 0644]
examples/demo/demos/data/cssview.css [new file with mode: 0644]
examples/demo/demos/data/demo.gresource [new file with mode: 0644]
examples/demo/demos/data/demo.gresource.xml [new file with mode: 0644]
examples/demo/demos/data/demo.ui [new file with mode: 0644]
examples/demo/demos/data/floppybuddy.gif [new file with mode: 0644]
examples/demo/demos/data/gnome-applets.png [new file with mode: 0644]
examples/demo/demos/data/gnome-calendar.png [new file with mode: 0644]
examples/demo/demos/data/gnome-foot.png [new file with mode: 0644]
examples/demo/demos/data/gnome-fs-directory.png [new file with mode: 0644]
examples/demo/demos/data/gnome-fs-regular.png [new file with mode: 0644]
examples/demo/demos/data/gnome-gimp.png [new file with mode: 0644]
examples/demo/demos/data/gnome-gmush.png [new file with mode: 0644]
examples/demo/demos/data/gnome-gsame.png [new file with mode: 0644]
examples/demo/demos/data/gnu-keys.png [new file with mode: 0644]
examples/demo/demos/data/gtk-logo-rgb.gif [new file with mode: 0644]
examples/demo/demos/data/reset.css [new file with mode: 0644]
examples/demo/demos/dialogs.py [new file with mode: 0644]
examples/demo/demos/drawingarea.py [new file with mode: 0644]
examples/demo/demos/expander.py [new file with mode: 0644]
examples/demo/demos/flowbox.py [new file with mode: 0755]
examples/demo/demos/images.py [new file with mode: 0644]
examples/demo/demos/infobars.py [new file with mode: 0644]
examples/demo/demos/links.py [new file with mode: 0644]
examples/demo/demos/menus.py [new file with mode: 0644]
examples/demo/demos/pickers.py [new file with mode: 0644]
examples/demo/demos/pixbuf.py [new file with mode: 0644]
examples/demo/demos/printing.py [new file with mode: 0644]
examples/demo/demos/rotatedtext.py [new file with mode: 0644]
examples/demo/demos/test.py [new file with mode: 0644]
examples/option.py [new file with mode: 0644]
examples/properties.py [new file with mode: 0644]
examples/signal.py [new file with mode: 0644]
gi/__init__.py [new file with mode: 0644]
gi/_compat.py [new file with mode: 0644]
gi/_constants.py [new file with mode: 0644]
gi/_error.py [new file with mode: 0644]
gi/_gtktemplate.py [new file with mode: 0644]
gi/_option.py [new file with mode: 0644]
gi/_ossighelper.py [new file with mode: 0644]
gi/_propertyhelper.py [new file with mode: 0644]
gi/_signalhelper.py [new file with mode: 0644]
gi/docstring.py [new file with mode: 0644]
gi/gimodule.c [new file with mode: 0644]
gi/gimodule.h [new file with mode: 0644]
gi/importer.py [new file with mode: 0644]
gi/meson.build [new file with mode: 0644]
gi/module.py [new file with mode: 0644]
gi/overrides/GIMarshallingTests.py [new file with mode: 0644]
gi/overrides/GLib.py [new file with mode: 0644]
gi/overrides/GObject.py [new file with mode: 0644]
gi/overrides/Gdk.py [new file with mode: 0644]
gi/overrides/GdkPixbuf.py [new file with mode: 0644]
gi/overrides/Gio.py [new file with mode: 0644]
gi/overrides/Gtk.py [new file with mode: 0644]
gi/overrides/Pango.py [new file with mode: 0644]
gi/overrides/__init__.py [new file with mode: 0644]
gi/overrides/keysyms.py [new file with mode: 0644]
gi/overrides/meson.build [new file with mode: 0644]
gi/pygboxed.c [new file with mode: 0644]
gi/pygboxed.h [new file with mode: 0644]
gi/pygenum.c [new file with mode: 0644]
gi/pygenum.h [new file with mode: 0644]
gi/pygflags.c [new file with mode: 0644]
gi/pygflags.h [new file with mode: 0644]
gi/pygi-argument.c [new file with mode: 0644]
gi/pygi-argument.h [new file with mode: 0644]
gi/pygi-array.c [new file with mode: 0644]
gi/pygi-array.h [new file with mode: 0644]
gi/pygi-basictype.c [new file with mode: 0644]
gi/pygi-basictype.h [new file with mode: 0644]
gi/pygi-boxed.c [new file with mode: 0644]
gi/pygi-boxed.h [new file with mode: 0644]
gi/pygi-cache.c [new file with mode: 0644]
gi/pygi-cache.h [new file with mode: 0644]
gi/pygi-ccallback.c [new file with mode: 0644]
gi/pygi-ccallback.h [new file with mode: 0644]
gi/pygi-closure.c [new file with mode: 0644]
gi/pygi-closure.h [new file with mode: 0644]
gi/pygi-enum-marshal.c [new file with mode: 0644]
gi/pygi-enum-marshal.h [new file with mode: 0644]
gi/pygi-error.c [new file with mode: 0644]
gi/pygi-error.h [new file with mode: 0644]
gi/pygi-foreign-api.h [new file with mode: 0644]
gi/pygi-foreign-cairo.c [new file with mode: 0644]
gi/pygi-foreign.c [new file with mode: 0644]
gi/pygi-foreign.h [new file with mode: 0644]
gi/pygi-hashtable.c [new file with mode: 0644]
gi/pygi-hashtable.h [new file with mode: 0644]
gi/pygi-info.c [new file with mode: 0644]
gi/pygi-info.h [new file with mode: 0644]
gi/pygi-invoke-state-struct.h [new file with mode: 0644]
gi/pygi-invoke.c [new file with mode: 0644]
gi/pygi-invoke.h [new file with mode: 0644]
gi/pygi-list.c [new file with mode: 0644]
gi/pygi-list.h [new file with mode: 0644]
gi/pygi-marshal-cleanup.c [new file with mode: 0644]
gi/pygi-marshal-cleanup.h [new file with mode: 0644]
gi/pygi-object.c [new file with mode: 0644]
gi/pygi-object.h [new file with mode: 0644]
gi/pygi-property.c [new file with mode: 0644]
gi/pygi-property.h [new file with mode: 0644]
gi/pygi-python-compat.h [new file with mode: 0644]
gi/pygi-repository.c [new file with mode: 0644]
gi/pygi-repository.h [new file with mode: 0644]
gi/pygi-resulttuple.c [new file with mode: 0644]
gi/pygi-resulttuple.h [new file with mode: 0644]
gi/pygi-signal-closure.c [new file with mode: 0644]
gi/pygi-signal-closure.h [new file with mode: 0644]
gi/pygi-source.c [new file with mode: 0644]
gi/pygi-source.h [new file with mode: 0644]
gi/pygi-struct-marshal.c [new file with mode: 0644]
gi/pygi-struct-marshal.h [new file with mode: 0644]
gi/pygi-struct.c [new file with mode: 0644]
gi/pygi-struct.h [new file with mode: 0644]
gi/pygi-type.c [new file with mode: 0644]
gi/pygi-type.h [new file with mode: 0644]
gi/pygi-util.c [new file with mode: 0644]
gi/pygi-util.h [new file with mode: 0644]
gi/pygi-value.c [new file with mode: 0644]
gi/pygi-value.h [new file with mode: 0644]
gi/pyginterface.c [new file with mode: 0644]
gi/pyginterface.h [new file with mode: 0644]
gi/pygobject-internal.h [new file with mode: 0644]
gi/pygobject-object.c [new file with mode: 0644]
gi/pygobject-object.h [new file with mode: 0644]
gi/pygobject.h [new file with mode: 0644]
gi/pygoptioncontext.c [new file with mode: 0644]
gi/pygoptioncontext.h [new file with mode: 0644]
gi/pygoptiongroup.c [new file with mode: 0644]
gi/pygoptiongroup.h [new file with mode: 0644]
gi/pygparamspec.c [new file with mode: 0644]
gi/pygparamspec.h [new file with mode: 0644]
gi/pygpointer.c [new file with mode: 0644]
gi/pygpointer.h [new file with mode: 0644]
gi/pygspawn.c [new file with mode: 0644]
gi/pygspawn.h [new file with mode: 0644]
gi/pygtkcompat.py [new file with mode: 0644]
gi/repository/__init__.py [new file with mode: 0644]
gi/repository/meson.build [new file with mode: 0644]
gi/types.py [new file with mode: 0644]
meson.build [new file with mode: 0644]
meson_options.txt [new file with mode: 0644]
pygobject-3.0.pc.in [new file with mode: 0644]
pygobject.doap [new file with mode: 0644]
pygtkcompat/__init__.py [new file with mode: 0644]
pygtkcompat/generictreemodel.py [new file with mode: 0644]
pygtkcompat/meson.build [new file with mode: 0644]
pygtkcompat/pygtkcompat.py [new file with mode: 0644]
setup.cfg [new file with mode: 0644]
setup.py [new file with mode: 0755]
subprojects/glib.wrap [new file with mode: 0644]
subprojects/gobject-introspection.wrap [new file with mode: 0644]
subprojects/libffi.wrap [new file with mode: 0644]
subprojects/pycairo.wrap [new file with mode: 0644]
tests/__init__.py [new file with mode: 0644]
tests/conftest.py [new file with mode: 0644]
tests/gi/overrides/Regress.py [new file with mode: 0644]
tests/gi/overrides/__init__.py [new file with mode: 0644]
tests/gimarshallingtestsextra.c [new file with mode: 0644]
tests/gimarshallingtestsextra.h [new file with mode: 0644]
tests/helper.py [new file with mode: 0644]
tests/meson.build [new file with mode: 0644]
tests/org.gnome.test.gschema.xml [new file with mode: 0644]
tests/regressextra.c [new file with mode: 0644]
tests/regressextra.h [new file with mode: 0644]
tests/runtests.py [new file with mode: 0755]
tests/test-floating.c [new file with mode: 0644]
tests/test-floating.h [new file with mode: 0644]
tests/test-thread.c [new file with mode: 0644]
tests/test-thread.h [new file with mode: 0644]
tests/test-unknown.c [new file with mode: 0644]
tests/test-unknown.h [new file with mode: 0644]
tests/test_atoms.py [new file with mode: 0644]
tests/test_cairo.py [new file with mode: 0644]
tests/test_docstring.py [new file with mode: 0644]
tests/test_error.py [new file with mode: 0644]
tests/test_everything.py [new file with mode: 0644]
tests/test_fields.py [new file with mode: 0644]
tests/test_gdbus.py [new file with mode: 0644]
tests/test_generictreemodel.py [new file with mode: 0644]
tests/test_gi.py [new file with mode: 0644]
tests/test_gio.py [new file with mode: 0644]
tests/test_glib.py [new file with mode: 0644]
tests/test_gobject.py [new file with mode: 0644]
tests/test_gtk_template.py [new file with mode: 0644]
tests/test_gtype.py [new file with mode: 0644]
tests/test_import_machinery.py [new file with mode: 0644]
tests/test_interface.py [new file with mode: 0644]
tests/test_internal_api.py [new file with mode: 0644]
tests/test_iochannel.py [new file with mode: 0644]
tests/test_mainloop.py [new file with mode: 0644]
tests/test_object_marshaling.py [new file with mode: 0644]
tests/test_option.py [new file with mode: 0644]
tests/test_ossig.py [new file with mode: 0644]
tests/test_overrides_gdk.py [new file with mode: 0644]
tests/test_overrides_gdkpixbuf.py [new file with mode: 0644]
tests/test_overrides_gio.py [new file with mode: 0644]
tests/test_overrides_glib.py [new file with mode: 0644]
tests/test_overrides_gtk.py [new file with mode: 0644]
tests/test_overrides_pango.py [new file with mode: 0644]
tests/test_properties.py [new file with mode: 0644]
tests/test_pycapi.py [new file with mode: 0644]
tests/test_pygtkcompat.py [new file with mode: 0644]
tests/test_repository.py [new file with mode: 0644]
tests/test_resulttuple.py [new file with mode: 0644]
tests/test_signal.py [new file with mode: 0644]
tests/test_source.py [new file with mode: 0644]
tests/test_subprocess.py [new file with mode: 0644]
tests/test_thread.py [new file with mode: 0644]
tests/test_typeclass.py [new file with mode: 0644]
tests/test_unknown.py [new file with mode: 0644]
tests/testhelpermodule.c [new file with mode: 0644]
tests/valgrind.supp [new file with mode: 0644]
tools/pygi-convert.sh [new file with mode: 0755]

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644 (file)
index 0000000..3a25c6a
--- /dev/null
@@ -0,0 +1,125 @@
+image: registry.gitlab.gnome.org/gnome/pygobject/main:v8
+
+stages:
+  - build_and_test
+  - coverage
+  - deploy
+
+cache:
+  paths:
+    - _ccache/
+
+.defaults: &defaults
+  stage: build_and_test
+  artifacts:
+    paths:
+      - coverage/
+  script:
+   - bash -x ./.gitlab-ci/test-docker.sh
+
+.mingw-defaults: &mingw-defaults
+  stage: build_and_test
+  tags:
+    - win32
+  artifacts:
+    paths:
+      - coverage/
+  script:
+    - C:\msys64\usr\bin\pacman --noconfirm -Syyuu
+    - C:\msys64\usr\bin\bash -lc "bash -x ./.gitlab-ci/test-msys2.sh"
+
+coverage:
+  stage: coverage
+  artifacts:
+    paths:
+      - coverage/
+  variables:
+    PYENV_VERSION: "3.6.6"
+  script:
+    - bash -x ./.gitlab-ci/coverage-docker.sh
+
+pages:
+  stage: deploy
+  dependencies:
+    - coverage
+  script:
+    - mv coverage/ public/
+  artifacts:
+    paths:
+      - public
+    expire_in: 30 days
+  only:
+    - master
+
+python2-mingw32:
+  variables:
+    PYTHON: "python2"
+    MSYSTEM: "MINGW32"
+    CHERE_INVOKING: "yes"
+  <<: *mingw-defaults
+
+python2-mingw64:
+  variables:
+    PYTHON: "python2"
+    MSYSTEM: "MINGW64"
+    CHERE_INVOKING: "yes"
+  <<: *mingw-defaults
+
+python3-mingw32:
+  variables:
+    PYTHON: "python3"
+    MSYSTEM: "MINGW32"
+    CHERE_INVOKING: "yes"
+  <<: *mingw-defaults
+
+python3-mingw64:
+  variables:
+    PYTHON: "python3"
+    MSYSTEM: "MINGW64"
+    CHERE_INVOKING: "yes"
+  <<: *mingw-defaults
+
+python2.7:
+  variables:
+    PYENV_VERSION: "2.7.15-debug"
+  <<: *defaults
+
+python3.5:
+  variables:
+    PYENV_VERSION: "3.5.6"
+  <<: *defaults
+
+python3.6:
+  variables:
+    PYENV_VERSION: "3.6.6"
+  <<: *defaults
+
+python3.7:
+  variables:
+    PYENV_VERSION: "3.7.0-debug"
+  <<: *defaults
+
+pypy2:
+  allow_failure: true
+  variables:
+    PYENV_VERSION: "pypy2.7-6.0.0"
+  <<: *defaults
+
+pypy3:
+  allow_failure: true
+  variables:
+    PYENV_VERSION: "pypy3.5-6.0.0"
+  <<: *defaults
+
+xenial-i386-py2:
+  stage: build_and_test
+  image: registry.gitlab.gnome.org/gnome/pygobject/old:v2
+  script:
+   - bash -x ./.gitlab-ci/test-docker-old.sh
+
+gnome-master:
+  allow_failure: true
+  stage: build_and_test
+  image: registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master
+  script:
+    - xvfb-run -a flatpak run --filesystem=host --share=network --socket=x11 --command=bash org.gnome.Sdk//master -x .gitlab-ci/test-flatpak.sh
diff --git a/.gitlab-ci/Dockerfile b/.gitlab-ci/Dockerfile
new file mode 100644 (file)
index 0000000..a72c718
--- /dev/null
@@ -0,0 +1,49 @@
+FROM ubuntu:bionic
+
+RUN apt-get update && apt-get install -y \
+    build-essential \
+    ccache \
+    curl \
+    dbus \
+    gir1.2-gtk-3.0 \
+    git \
+    gobject-introspection \
+    lcov \
+    libbz2-dev \
+    libcairo2-dev \
+    libffi-dev \
+    libgirepository1.0-dev \
+    libglib2.0-dev \
+    libgtk-3-0 \
+    libreadline-dev \
+    libsqlite3-dev \
+    libssl-dev \
+    ninja-build \
+    python3-pip \
+    xauth \
+    xvfb \
+    && rm -rf /var/lib/apt/lists/*
+
+ARG HOST_USER_ID=5555
+ENV HOST_USER_ID ${HOST_USER_ID}
+RUN useradd -u $HOST_USER_ID -ms /bin/bash user
+
+USER user
+WORKDIR /home/user
+
+ENV LANG C.UTF-8
+ENV CI true
+ENV PYENV_ROOT /home/user/.pyenv
+ENV PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${PATH}"
+ENV PYTHON_CONFIGURE_OPTS="--enable-shared"
+
+RUN curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash
+
+RUN pyenv install --debug 2.7.15
+RUN pyenv install 3.5.6
+RUN pyenv install 3.6.6
+RUN pyenv install --debug 3.7.0
+RUN pyenv install pypy2.7-6.0.0
+RUN pyenv install pypy3.5-6.0.0
+
+ENV PATH="/usr/lib/ccache:${PATH}"
diff --git a/.gitlab-ci/Dockerfile.old b/.gitlab-ci/Dockerfile.old
new file mode 100644 (file)
index 0000000..fa135de
--- /dev/null
@@ -0,0 +1,36 @@
+FROM i386/ubuntu:xenial
+
+RUN apt-get update && apt-get install -y \
+    autoconf-archive \
+    build-essential \
+    ccache \
+    curl \
+    dbus \
+    gir1.2-gtk-3.0 \
+    git \
+    gobject-introspection \
+    lcov \
+    libcairo2-dev \
+    libffi-dev \
+    libgirepository1.0-dev \
+    libglib2.0-dev \
+    libgtk-3-0 \
+    libtool \
+    locales \
+    python-dev \
+    python-virtualenv \
+    xauth \
+    xvfb \
+    && rm -rf /var/lib/apt/lists/*
+
+ARG HOST_USER_ID=5555
+ENV HOST_USER_ID ${HOST_USER_ID}
+RUN useradd -u $HOST_USER_ID -ms /bin/bash user
+
+USER user
+WORKDIR /home/user
+
+ENV LANG C.UTF-8
+ENV CI true
+
+ENV PATH="/usr/lib/ccache:${PATH}"
diff --git a/.gitlab-ci/README.rst b/.gitlab-ci/README.rst
new file mode 100644 (file)
index 0000000..3ebab30
--- /dev/null
@@ -0,0 +1,2 @@
+The images are available at
+https://gitlab.gnome.org/GNOME/pygobject/container_registry
diff --git a/.gitlab-ci/coverage-docker.sh b/.gitlab-ci/coverage-docker.sh
new file mode 100755 (executable)
index 0000000..2a51d03
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+set -e
+
+python -m pip install coverage
+
+# Make the Windows paths match our current layout
+python ./.gitlab-ci/fixup-cov-paths.py coverage/.coverage* coverage/*.lcov
+
+# Remove external headers (except gi tests)
+for path in coverage/*.lcov; do
+    lcov --rc lcov_branch_coverage=1 -r "${path}" '/usr/include/*' -o "${path}"
+    lcov --rc lcov_branch_coverage=1 -r "${path}" '/home/*' -o "${path}"
+done
+
+python -m coverage combine coverage
+python -m coverage html --ignore-errors -d coverage/report-python
+genhtml --ignore-errors=source --rc lcov_branch_coverage=1 \
+    coverage/*.lcov -o coverage/report-c
+
+cd coverage
+rm -f .coverage*
+rm -f *.lcov
+
+ln -s report-python/index.html index-python.html
+ln -s report-c/index.html index-c.html
+
+cat >index.html <<EOL
+<html>
+<body>
+<ul>
+<li><a href="report-c/index.html">C Coverage</a></li>
+<li><a href="report-python/index.html">Python Coverage</a></li>
+</ul>
+</body>
+</html>
+EOL
diff --git a/.gitlab-ci/fixup-cov-paths.py b/.gitlab-ci/fixup-cov-paths.py
new file mode 100644 (file)
index 0000000..a6f43e4
--- /dev/null
@@ -0,0 +1,35 @@
+from __future__ import print_function
+
+import sys
+import os
+import io
+
+
+def main(argv):
+    # Fix paths in coverage files to match our current source layout
+    # so that coverage report generators can find the source.
+    # Mostly needed for Windows.
+    paths = argv[1:]
+
+    for path in paths:
+        print("cov-fixup:", path)
+        text = io.open(path, "r", encoding="utf-8").read()
+        text = text.replace("\\\\", "/")
+        end = text.index("/gi/")
+        try:
+            # coverage.py
+            start = text[:end].rindex("\"") + 1
+        except ValueError:
+            # lcov
+            start = text[:end].rindex(":") + 1
+        old_root = text[start:end]
+        new_root = os.getcwd()
+        if old_root != new_root:
+            print("replacing %r with %r" % (old_root, new_root))
+        text = text.replace(old_root, new_root)
+        with io.open(path, "w", encoding="utf-8") as h:
+            h.write(text)
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
diff --git a/.gitlab-ci/run-docker-old.sh b/.gitlab-ci/run-docker-old.sh
new file mode 100755 (executable)
index 0000000..4dd1752
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+set -e
+
+TAG="registry.gitlab.gnome.org/gnome/pygobject/old:v2"
+
+sudo docker build --build-arg HOST_USER_ID="$UID" --tag "${TAG}" \
+    --file "Dockerfile.old" .
+sudo docker run --rm --security-opt label=disable \
+    --volume "$(pwd)/..:/home/user/app" --workdir "/home/user/app" \
+    --tty --interactive "${TAG}" bash
diff --git a/.gitlab-ci/run-docker.sh b/.gitlab-ci/run-docker.sh
new file mode 100755 (executable)
index 0000000..bcfd707
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+set -e
+
+TAG="registry.gitlab.gnome.org/gnome/pygobject/main:v8"
+
+sudo docker build --build-arg HOST_USER_ID="$UID" --tag "${TAG}" \
+    --file "Dockerfile" .
+sudo docker run -e PYENV_VERSION='3.7.0-debug' --rm --security-opt label=disable \
+    --volume "$(pwd)/..:/home/user/app" --workdir "/home/user/app" \
+    --tty --interactive "${TAG}" bash
diff --git a/.gitlab-ci/test-docker-old.sh b/.gitlab-ci/test-docker-old.sh
new file mode 100755 (executable)
index 0000000..74c81c7
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -e
+
+python --version
+virtualenv --python=python _venv
+source _venv/bin/activate
+
+# ccache setup
+mkdir -p _ccache
+export CCACHE_BASEDIR="$(pwd)"
+export CCACHE_DIR="${CCACHE_BASEDIR}/_ccache"
+
+# test
+python -m pip install git+https://github.com/pygobject/pycairo.git
+python -m pip install pytest pytest-faulthandler
+xvfb-run -a python setup.py test
diff --git a/.gitlab-ci/test-docker.sh b/.gitlab-ci/test-docker.sh
new file mode 100755 (executable)
index 0000000..1a6dddb
--- /dev/null
@@ -0,0 +1,58 @@
+#!/bin/bash
+
+set -e
+
+python --version
+
+PYVER=$(python -c "import sys; sys.stdout.write('.'.join(map(str, sys.version_info[:2])))")
+PYIMPL=$(python -c "import sys, platform; sys.stdout.write(platform.python_implementation())")
+SOURCE_DIR="$(pwd)"
+COV_DIR="${SOURCE_DIR}/coverage"
+export MALLOC_CHECK_=3
+export MALLOC_PERTURB_=$((${RANDOM} % 255 + 1))
+export G_SLICE="debug-blocks"
+export COVERAGE_FILE="${COV_DIR}/.coverage.${CI_JOB_NAME}"
+export CCACHE_BASEDIR="$(pwd)"
+export CCACHE_DIR="${CCACHE_BASEDIR}/_ccache"
+
+# https://docs.python.org/3/using/cmdline.html#envvar-PYTHONDEVMODE
+export PYTHONDEVMODE=1
+
+mkdir -p "${CCACHE_DIR}"
+mkdir -p "${COV_DIR}"
+
+python -m pip install git+https://github.com/pygobject/pycairo.git
+python -m pip install flake8 pytest pytest-faulthandler coverage
+
+export CFLAGS="-coverage -ftest-coverage -fprofile-arcs -Werror"
+
+# MESON
+/usr/bin/python3 -m pip install --user git+https://github.com/mesonbuild/meson.git
+export PATH="${HOME}/.local/bin:${PATH}"
+export PKG_CONFIG_PATH="$(python -c 'import sys; sys.stdout.write(sys.prefix)')/lib/pkgconfig"
+# pycairo install under PyPy doesn't install a .pc file
+if [[ "${PYIMPL}" == "PyPy" ]]; then
+    meson _build -Dpython="$(which python)" -Dpycairo=false
+else
+    meson _build -Dpython="$(which python)"
+fi
+ninja -C _build
+xvfb-run -a meson test -C _build -v
+rm -Rf _build
+
+# CODE QUALITY
+python -m flake8
+
+# DOCUMENTATION CHECKS
+if [[ "${PYVER}" == "2.7" ]] && [[ "${PYIMPL}" == "CPython" ]]; then
+    python -m pip install sphinx sphinx_rtd_theme
+    python -m sphinx -W -a -E -b html -n docs docs/_build
+fi;
+
+# BUILD & TEST AGAIN USING SETUP.PY
+python setup.py build_tests
+xvfb-run -a python -m coverage run tests/runtests.py
+
+# COLLECT GCOV COVERAGE
+lcov --rc lcov_branch_coverage=1 --directory . --capture --output-file \
+    "${COV_DIR}/${CI_JOB_NAME}.lcov"
diff --git a/.gitlab-ci/test-flatpak.sh b/.gitlab-ci/test-flatpak.sh
new file mode 100755 (executable)
index 0000000..3e3a992
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+set -e
+
+python3 -m pip install --user pytest
+python3 setup.py test
diff --git a/.gitlab-ci/test-msys2.sh b/.gitlab-ci/test-msys2.sh
new file mode 100755 (executable)
index 0000000..2b11aea
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/bash
+
+set -e
+
+# skip the fontconfig cache, it's slooowww
+export MSYS2_FC_CACHE_SKIP=1
+export PANGOCAIRO_BACKEND=win32
+
+export PATH="/c/msys64/$MSYSTEM/bin:$PATH"
+if [[ "$MSYSTEM" == "MINGW32" ]]; then
+    export MSYS2_ARCH="i686"
+else
+    export MSYS2_ARCH="x86_64"
+fi
+
+pacman --noconfirm -Suy
+
+pacman --noconfirm -S --needed \
+    base-devel \
+    mingw-w64-$MSYS2_ARCH-toolchain \
+    mingw-w64-$MSYS2_ARCH-ccache \
+    mingw-w64-$MSYS2_ARCH-$PYTHON-cairo \
+    mingw-w64-$MSYS2_ARCH-$PYTHON \
+    mingw-w64-$MSYS2_ARCH-$PYTHON-pip \
+    mingw-w64-$MSYS2_ARCH-$PYTHON-pytest \
+    mingw-w64-$MSYS2_ARCH-$PYTHON-coverage \
+    mingw-w64-$MSYS2_ARCH-gobject-introspection \
+    mingw-w64-$MSYS2_ARCH-libffi \
+    mingw-w64-$MSYS2_ARCH-glib2 \
+    mingw-w64-$MSYS2_ARCH-gtk3 \
+    git \
+    perl
+
+# ccache setup
+export PATH="$MSYSTEM/lib/ccache/bin:$PATH"
+mkdir -p _ccache
+export CCACHE_BASEDIR="$(pwd)"
+export CCACHE_DIR="${CCACHE_BASEDIR}/_ccache"
+
+# coverage setup
+export CFLAGS="-coverage -ftest-coverage -fprofile-arcs -Werror"
+PYVER=$($PYTHON -c "import sys; sys.stdout.write(''.join(map(str, sys.version_info[:3])))")
+COV_DIR="$(pwd)/coverage"
+COV_KEY="${MSYSTEM}.${PYVER}"
+mkdir -p "${COV_DIR}"
+export COVERAGE_FILE="${COV_DIR}/.coverage.${COV_KEY}"
+
+# https://docs.python.org/3/using/cmdline.html#envvar-PYTHONDEVMODE
+export PYTHONDEVMODE=1
+
+$PYTHON setup.py build_tests
+MSYSTEM= $PYTHON -m coverage run tests/runtests.py
+
+curl -O -J -L "https://github.com/linux-test-project/lcov/archive/master.tar.gz"
+tar -xvzf lcov-master.tar.gz
+
+./lcov-master/bin/lcov \
+    --rc lcov_branch_coverage=1 --no-external \
+    --directory . --capture --output-file \
+    "${COV_DIR}/${COV_KEY}.lcov"
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..ae23fcf
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,504 @@
+                 GNU LESSER GENERAL PUBLIC LICENSE
+                      Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+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 this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+                 GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+\f
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+\f
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+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
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser 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 Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                           NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "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
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey 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 library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..25de999
--- /dev/null
@@ -0,0 +1,17 @@
+include setup.cfg
+include COPYING
+include *.in
+include NEWS
+include tools/pygi-convert.sh
+include pygobject.doap
+include README.rst
+include .gitlab-ci.yml
+include subprojects/*.wrap
+include meson.build
+include meson_options.txt
+recursive-include examples *.py *.png *.css *.ui *.gif *.gresource *.jpg *.xml
+recursive-include gi *.h meson.build
+recursive-include tests *.py *.c *.h *.xml *.supp meson.build
+recursive-include docs *.rst *.svg LICENSE *.ico *.png *.css *.py *.dia Makefile
+recursive-include .gitlab-ci *.sh *.rst *.py Dockerfile*
+recursive-include pygtkcompat meson.build
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..00a08bb
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,3238 @@
+3.30.1 - 2018-09-14
+-------------------
+
+* Fix various crashes on big endian systems.
+  :issue:`247` (:user:`Dan Horák <sharkcz>`)
+* meson: Don't link against libpython on non-Windows systems.
+  :issue:`253` :mr:`84`
+
+
+3.30.0 - 2018-08-31
+-------------------
+
+* Various test suite fixes to get things to pass with Ubuntu 18.10.
+
+
+3.29.3 - 2018-08-16
+-------------------
+
+* meson: Support building pycairo as a subproject. :mr:`76`
+* meson: Declare_dependency for use by potential superprojects
+  (:user:`Mathieu Duponchelle <mathieudu>`)
+* meson: Update glib wrap file. :mr:`80` (:user:`Carlos Soriano <csoriano>`)
+* meson: Fix the Python 2 build not not use the system pycairo extension when
+  running tests. :issue:`242`
+* pygi-convert.sh: Various fixes and updates. :mr:`77` :mr:`78`
+  (:user:`Sander Sweers <infirit>`)
+* Gtk.Template: Fix instantiation error when using the new code with older
+  PyGObject. :mr:`79` (:user:`Kai Willadsen <kaiw>`)
+* Gtk.Template: Don't error out when loading a resource that is only available
+  in an overlay. :issue:`230`
+* Fix various crashes when running against a debug Python 3.7 build. :mr:`82`
+  (:user:`Simon McVittie <smcv>`)
+* overrides: Allow calling GObject.Binding.unbind() multiple times with
+  GLib 2.58+. :issue:`240`
+* overrides: Gio.ListStore overrides use splice() when adding/removing many
+  items with GLib 2.58+. :issue:`115` :mr:`83`
+* Work around pylint reporting bogus warnings regarding a missing self
+  argument for normal functions. :issue:`217`
+* Add override for GdkPixbuf.Pixbuf.new_from_data() to wrap new_from_bytes()
+  to work around a use after free. :issue:`225` :mr:`74`
+
+
+3.28.3 - 2018-05-31
+-------------------
+
+* Fix Gio.Application leak in case no signal handler is set before.
+  :issue:`219`
+* Squash critical warning when using array as hash value
+  (:user:`Philip Withnall <pwithnall>`)
+
+
+3.29.2 - 2018-05-16
+-------------------
+
+* Add a meson build system. :issue:`165`
+  (:user:`Mathieu Duponchelle<mathieudu>`)
+* Gtk.Template: Allow marking children as "internal-child". :mr:`58`
+* Gio.ListModel: implement most of the mutable sequence protocol.
+  :issue:`115` :mr:`59`
+* Gio.Settings: implement __iter__.
+* Gio.Settings: support range types in __setitem__. :issue:`134`
+* Add overrides for Gio.ListStore.sort and Gio.ListStore.insert_sorted.
+  :issue:`130`
+* Make Gtk.Widget.freeze_child_notify a context manager. :issue:`45`
+* OptionParser.parse_args: return leftover arguments. :issue:`200`
+* Release the GIL when emitting a signal. :mr:`66`
+  (John Bassett <john.bassett@pexip.com>)
+* Add ActionMap and ActionMap.add_action_entries() to overrides.
+  :issue:`29` :mr:`65` (:user:`yangfl`)
+* importer: raise ImportError in load_module() and not find_module().
+  :issue:`213`
+* Don't wrap GValue in GValue when creating GValueArray. :mr:`66`
+  (Stian Selnes <stian@pexip.com>)
+* ossig: Don't leak the callbacks in case the event loops are not stopped
+  through SIGINT. :issue:`219` :mr:`72`
+* Various fixes (Havard Graff <havard.graff@gmail.com>)
+* Destroy custom GLib.Source instances when they get freed. :issue:`193`
+* Revert "Add PEP518/pyproject.toml file", fixes installation with pip 10,
+  see https://github.com/pypa/pip/issues/5244
+* Various fixes/improvements for PyPy.
+* Don't crash on multiple calls to GObject.Value.__del__. :mr:`66`
+
+Documentation:
+  * Added StackOverflow (with PyGObject tag) as an contact resource.
+    (:user:`buhtz`)
+  * Add introduction to handling GLib.Error. :mr:`68`
+    (:user:`Kai Willadsen <kaiw>`)
+  * Add pycairo requires for development setup. :mr:`70`
+    (:user:`Kai Willadsen <kaiw>`)
+
+
+3.29.1 - 2018-04-15
+-------------------
+
+* Support for `PyPy <https://pypy.org/>`__ and PyPy3. :issue:`180`
+* cairo: support :class:`cairo.Matrix` conversion. :issue:`16`
+* Speed up repeated closure creation by caching the closure cache in the
+  argument cache :issue:`103` (:user:`Garrett Regier <gregier>`\,
+  :user:`Christoph Reiter <creiter>`)
+* setup.py: make setuptools/pkg_resources optional. :issue:`186`
+* setup.py: print installation instructions in case a dependency is missing.
+  :issue:`194`
+* Remove autotools build system.
+* overrides: Make :meth:`Gtk.ListStore.insert_before`,
+  :meth:`Gtk.ListStore.insert_after`, :meth:`Gtk.TreeStore.insert_before` and
+  :meth:`Gtk.TreeStore.insert_after` atomic.
+* Make :class:`GLib.Error` picklable. :issue:`145`
+* Add basic support for template based widgets through ``Gtk.Template``.
+  :issue:`52`
+* Various documentation improvements. :mr:`29` (:user:`Dan Yeaw <danyeaw>`)
+* Add PEP518/pyproject.toml file. :mr:`44` (:user:`James Tocknell <aragilar>`)
+* Avoid truncating value returned from g_value_info_get_value. :mr:`51`
+  (:user:`Tomasz MiÄ…sko <tmiasko>`)
+* Fix typo in BoundSignal disconnect. :mr:`55`
+  (:user:`Vladislav Glinsky <cl0ne>`)
+
+
+3.28.2 - 2018-03-27
+-------------------
+
+* setup.py: Don't install the test C extension when it's built. :issue:`181`
+* setup.py: Always define PY_SSIZE_T_CLEAN. :issue:`185`
+* Fix __str__ return type of Gtk.TreePath with depth == 0. :issue:`143`
+* Fix a crash when setting a str property with a value containing surrogates.
+  :issue:`169`
+* tests: Fix a potential crash during tests
+
+
+3.28.1 - 2018-03-17
+-------------------
+
+* Fix a GValue leak (regression). :issue:`176`
+  (:user:`Mathieu Duponchelle<mathieudu>`)
+* setup.py: don't install the tests package
+* Various fixes for 64bit Windows. :mr:`34`
+  (:user:`Mathieu Duponchelle<mathieudu>`)
+* Fix tests with glib 2.56.0
+* Various fixes for Python 3.7. :issue:`170` :mr:`28`
+
+
+3.28.0 - 2018-03-12
+-------------------
+
+* GLib.Variant: Fix creation of guchar arrays from bytes (3.27.2 regression).
+  :issue:`174` :mr:`30`
+
+
+3.27.5 - 2018-03-01
+-------------------
+
+* Re-revert transfer-none boxed copy changes (:mr:`23`). Now with more fixes
+  and tests. :mr:`24` (:user:`Mathieu Duponchelle <mathieudu>`)
+* Add caching for boxed type lookup and try to avoid the import lock. :mr:`13`
+  (:user:`Mikhail Fludkov <fludkov.me>`)
+
+
+3.27.4 - 2018-02-14
+-------------------
+
+* tests: Fix tests under Wayland. :issue:`163`
+* tests: Make it possible to use pytest directly.
+* Reverted transfer-none boxed copy changes (:mr:`10`) due to regressions in
+  gnome-music. :issue:`164` :mr:`23`
+
+
+3.27.3 - 2018-02-10
+-------------------
+
+* Fix a 3.27.2 regression where functions return invalid boxed values. :mr:`16`
+  (thanks to :user:`Mikhail Fludkov <fludkov.me>` for providing a test)
+* tests: Make tests run without Gtk/Gdk installed. :mr:`17`
+  (:user:`Mikhail Fludkov <fludkov.me>`)
+* tests: Remove dependency on ``localedef``. :commit:`64b02e301`
+* tests: Require/Use pytest. :mr:`20` :issue:`153`
+
+
+3.27.2 - 2018-02-07
+-------------------
+
+* setup.py: Add a "quality" command which is equal to "make check.quality".
+* setup.py: Add a "test" command which is equal to "make check". :mr:`5`
+* setup.py: Install pkg-config and header files.
+* setup.py: Improve pycairo header lookup with pycairo >=1.16. :issue:`150`
+* autotools: "make check.quality" now requires flake8.
+* overrides: Fix ``Gtk.Adjustment.__init__()`` overrides not setting "value"
+  sometimes. :issue:`151` :mr:`3`
+* overrides: ``GLib.Variant``: add support to create maybe types.
+  :issue:`152` :mr:`4` (:user:`Alberto Ruiz <aruiz>`)
+* Make it possible to resolve ambiguous vmethod names. Ambiguities can be
+  resolved by implementing methods named
+  "do_$namespaced_base_class_name_$vfunc_name". :mr:`9` :issue:`105`
+  (:user:`Mathieu Duponchelle <mathieudu>`)
+* Fix setting a property installed in Python from C in some cases. :mr:`8`
+  (:user:`Mathieu Duponchelle <mathieudu>`)
+* pygobject-object: fix memory corruption around list of closures. :mr:`12`
+  :issue:`158` (:user:`Mikhail Fludkov <fludkov.me>`)
+* Don't copy the boxed if we are the sole owner of the wrapper after a closure.
+  :mr:`14`
+* Only copy transfer-none boxed values in closures once the closure exists.
+  This allows modifying the passed boxed while allowing to keep the wrapper
+  around after the closure is done. :mr:`10`
+  (:user:`Mathieu Duponchelle <mathieudu>`)
+
+
+3.27.1 - 2017-12-11
+-------------------
+
+* Revert "setup.py: Also set setup_requires to require pycairo" (Christoph Reiter)
+* setup.py: Also set setup_requires to require pycairo (Christoph Reiter)
+* setup.py: Provide a os.path.samefile fallback for Python 2 under Windows (Christoph Reiter)
+* Add sphinx based documentation (Christoph Reiter) (:bzbug:`791448`)
+* PKG-INFO: Revert name back to PyGObject (Christoph Reiter)
+* setup.py: Rework pycairo discovery to not use pkg-config (Christoph Reiter)
+* setup.py: Fix the distcheck command on Windows (Christoph Reiter)
+* setup.py: Remove various classifiers and the download-url which aren't accepted by pypi (Christoph Reiter)
+* version bump (Christoph Reiter)
+
+3.27.0 - 2017-12-08
+-------------------
+
+* demo: pep8 fixes (Christoph Reiter)
+* Fix ctypes.PyDLL construction under Windows (Christoph Reiter) (:bzbug:`622084`)
+* configure.ac: Error out in case autoconf-archive isn't installed (Christoph Reiter) (:bzbug:`784428`)
+* Move pygi-convert.sh into tools (Christoph Reiter)
+* README: Convert to reST (Christoph Reiter)
+* demo: Move demo into examples and dist it (Christoph Reiter) (:bzbug:`735918`)
+* demo: Add new Gtk.FlowBox example (Gian Mario Tagliaretti) (:bzbug:`735918`)
+* demo: Use HeaderBar for main app window (Simon Feltman) (:bzbug:`735918`)
+* demo: PyFlakes and PEP8 fixes (Simon Feltman) (:bzbug:`735918`)
+* demo: Rename gtk-demo.py to demo.py (Simon Feltman) (:bzbug:`735918`)
+* demo: Rename demos/gtk-demo to simply demo (Simon Feltman) (:bzbug:`735918`)
+* Remove AUTHORS file (Christoph Reiter)
+* Remove pre-commit.hook (Christoph Reiter)
+* setup.py: Port to distutils/setuptools (Christoph Reiter) (:bzbug:`789211`)
+* Install a default SIGINT handler for functions which start an event loop (Christoph Reiter) (:bzbug:`622084`)
+* Make Python OS signal handlers run when an event loop is idling (Christoph Reiter) (:bzbug:`622084`)
+* Drop Python 3.3 support (Christoph Reiter) (:bzbug:`790787`)
+* Drop set_value usage in Gtk.List/TreeStore.set override (Sander Sweers) (:bzbug:`790346`)
+* pygobject-object: Fix Python GC collecting a ref cycle too early (Christoph Reiter) (:bzbug:`731501`)
+* Fix potential uninitialized memory access during GC (Daniel Colascione) (:bzbug:`786872`)
+* test: revert parts of the previous test as it's broken on 32 bit builds (Christoph Reiter) (:bzbug:`786948`)
+* flags: Add testcase for bug 786948 (Christoph Reiter) (:bzbug:`786948`)
+* fix potential overflow when marshalling flags from py interface (Philippe Renon) (:bzbug:`786948`)
+* to_py_array: Properly handle enum array items (Christoph Reiter) (:bzbug:`788890`)
+* pygobject.doap: Add myself as maintainer (Christoph Reiter)
+* closure: Fix unaligned and out-of-bounds access (James Clarke) (:bzbug:`788894`)
+* build: Fix not installing .egg-info file (Christoph Reiter) (:bzbug:`777719`)
+* Drop pygobject-3.0-uninstalled.pc file (Christoph Reiter)
+* tests: Windows fix (Christoph Reiter)
+* tests: some more C locale fixes (Christoph Reiter)
+* tests: Make the test suite pass with the C locale (Christoph Reiter)
+* configure.ac: post-release version bump to 3.27.0 (Christoph Reiter)
+
+3.26.1 - 2017-10-27
+-------------------
+
+* pygobject-object: Fix Python GC collecting a ref cycle too early (Christoph Reiter) (:bzbug:`731501`)
+* Fix potential uninitialized memory access during GC (Daniel Colascione) (:bzbug:`786872`)
+* test: revert parts of the previous test as it's broken on 32 bit builds (Christoph Reiter) (:bzbug:`786948`)
+* flags: Add testcase for bug 786948 (Christoph Reiter) (:bzbug:`786948`)
+* fix potential overflow when marshalling flags from py interface (Philippe Renon) (:bzbug:`786948`)
+* to_py_array: Properly handle enum array items (Christoph Reiter) (:bzbug:`788890`)
+* closure: Fix unaligned and out-of-bounds access (James Clarke) (:bzbug:`788894`)
+* build: Fix not installing .egg-info file (Christoph Reiter) (:bzbug:`777719`)
+* configure.ac: version bump to 3.26.1 (Christoph Reiter)
+
+2.28.7 - 2017-10-13
+-------------------
+
+* Move property and signal creation into _class_init() (Martin Pitt)
+* gio-types.defs: change some enums to flags (Ryan Lortie)
+* Fix set_qdata warning on accessing NULL gobject property (Ivan Stankovic)
+* Disable introspection support by default (Dieter Verfaillie)
+* Don't install codegen for Python 3 (Arfrever Frehtes Taifersar Arahesis)
+* Ship tests/te_ST@nouppera in release tarballs for tests to succeed (Martin Pitt)
+* [gi] Port test_properties from static gio to GI Gio (Martin Pitt)
+* [python3] fix build. PYcairo_IMPORT doesn't exists anymore (Ignacio Casal Quinteiro)
+* [python3] Fix maketrans import (Martin Pitt)
+* [gi-overrides] fix MessageBox so it correctly handles the type constructor param (John (J5) Palmieri)
+* gdbus tests: Fix hang if test case fails (Martin Pitt)
+* Fix crash in Gtk.TextIter overrides (Martin Pitt)
+* correctly initialize the _gi_cairo_functions array to be zero filled (John (J5) Palmieri)
+* [gtk-override] print warning if user imports Gtk 2.0 (John (J5) Palmieri)
+* Add support for enums in gobject.property (Johan Dahlin)
+
+3.26.0 - 2017-09-12
+-------------------
+
+* configure.ac: pre-release version bump to 3.26.0 (Christoph Reiter)
+* closure: silence a new compiler warning (Christoph Reiter)
+* tests: skip some failing test under Windows with Python 3.6 (Christoph Reiter)
+* tests: pyflakes/pep8 fixes (Christoph Reiter)
+* tests: Fix cairo test with pycairo >= 1.13 (Christoph Reiter)
+* Make sure version information passed to require_version is a string. (Benjamin Berg) (:bzbug:`781582`)
+* configure.ac: post-release version bump to 3.25.2 (Christoph Reiter)
+
+3.25.1 - 2017-04-21
+-------------------
+
+* Bump pycairo requirement to 1.11.1 (Christoph Reiter) (:bzbug:`707196`)
+* configure.ac: Always disable -Werror (Christoph Reiter)
+* foreign-cairo: Enable cairo.Region support also on Python 2 if available (Christoph Reiter)
+* configure.ac: remove unused PLATFORM variable (Christoph Reiter)
+* configure.ac: Remove unused PySignal_SetWakeupFd check (Christoph Reiter)
+* tests: remove python 2.5/3.2 compat code (Christoph Reiter)
+* configure.ac: Require Python 3.3 (Christoph Reiter)
+* tests: Make test suite run with GTK+ 4 (Christoph Reiter)
+* tests: always call require_version; add TEST_GTK_VERSION env var (Christoph Reiter)
+* tests: Fix make check.valgrind (Christoph Reiter)
+* tests: Don't skip Regress tests when cairo is missing (Christoph Reiter)
+* tests: fix invalid regex escaping (Christoph Reiter)
+* tests: avoid mapping a GtkWindow (Christoph Reiter) (:bzbug:`780812`)
+* tests: silence some glib deprecation warnings (Christoph Reiter) (:bzbug:`780812`)
+* tests: avoid deprecation warnings for assertRegexpMatches/assertRaisesRegexp (Christoph Reiter) (:bzbug:`780812`)
+* pygi-source: clear exceptions in finalize handler (Christoph Reiter) (:bzbug:`780812`)
+* Fix pep8 errors (Christoph Reiter)
+* Remove gi._gi._gobject and gi._gobject modules (Christoph Reiter) (:bzbug:`735206`)
+* Remove gi._gi._glib module (Christoph Reiter) (:bzbug:`735206`)
+* GValue: add overflow checking for py -> gint; forward marshaling exceptions (Christoph Reiter) (:bzbug:`769789`)
+* pygobject_lookup_class: clear exceptions between calls and don't return with one set (Christoph Reiter) (:bzbug:`773394`)
+* Avoid some new deprecation warnings (Christoph Reiter) (:bzbug:`780768`)
+* Raise RuntimeError in case an uninitilialized GObject.Object is marshaled (Christoph Reiter) (:bzbug:`730908`)
+* closure: support unichar args (Christoph Reiter) (:bzbug:`759276`)
+* Add support for bytes and non-utf-8 file names. (Christoph Reiter) (:bzbug:`746564`)
+* test_gi: use correct min/max constants for gsize/gssize (Christoph Reiter) (:bzbug:`780591`)
+* Don't use long format string for formatting pointers (Christoph Reiter) (:bzbug:`780591`)
+* Fix conversion from pointers to hashfunc return values. (Christoph Reiter) (:bzbug:`780591`)
+* Fix PyLong <-> GPid conversion on 64bit Windows (Christoph Reiter) (:bzbug:`780591`)
+* property: support setting flags (Christoph Reiter) (:bzbug:`726484`)
+* overrides: warn on instantiation of Gio.VolumeMonitor (Christoph Reiter) (:bzbug:`744690`)
+* Remove gi.overrides.overridefunc (Christoph Reiter) (:bzbug:`686835`)
+* tests: Reduce usage of timeout_add() and sleep() (Christoph Reiter) (:bzbug:`698548`)
+* tests: Remove TestMainLoop.test_concurrency (Christoph Reiter) (:bzbug:`698548`)
+* Update .gitignore: add ``*.dll``, ``*.dylib``, ``.DS_STORE`` (Christoph Reiter)
+* tests: Make test suite run on Windows (Christoph Reiter) (:bzbug:`780396`)
+* tests: Make test suite run on macOS (Christoph Reiter) (:bzbug:`780396`)
+* Fix various compiler warnings for 32bit builds (Christoph Reiter) (:bzbug:`780409`)
+* pep8 fix (Christoph Reiter)
+* testhelper: only link against libpython on Windows (Christoph Reiter) (:bzbug:`773803`)
+* overrides: Fix Gtk.TextBuffer.insert_with_tags_by_name() with no tags (Garrett Regier) (:bzbug:`772896`)
+* Make use of instance-argument annotations (Christoph Reiter) (:bzbug:`735076`)
+* Remove pyglib_gil_state_ensure/pyglib_gil_state_release (Christoph Reiter) (:bzbug:`699440`)
+* Remove support for building without threads (Christoph Reiter) (:bzbug:`699440`)
+* pygtkcompat: Allow multiple calls to enable(), enable_gtk() as long as the version matches (Christoph Reiter) (:bzbug:`759009`)
+* tests: Update Makefile for building tests on OS X (Simon Feltman) (:bzbug:`762176`)
+* testhelper: propagate exception if _gobject could not be imported (Mikhail Fludkov) (:bzbug:`772949`)
+* pygi-info: initialize GIArgument before passing it to g_constant_info_get_value (Christoph Reiter) (:bzbug:`772949`)
+* tests: build libregress with disabled cairo (Mikhail Fludkov) (:bzbug:`772949`)
+* tests: use g-ir utils found by pkg-config (Mikhail Fludkov) (:bzbug:`772949`)
+* Add a foreign type for cairo_region_t. (Shyouzou Sugitani) (:bzbug:`667959`)
+* aclocal: make local file discover by reading AC_CONFIG_MACRO_DIR work (Christoph Reiter) (:bzbug:`777713`)
+* Port from gnome-common to autoconf-archive (Christoph Reiter) (:bzbug:`777713`)
+* Fix various potential compiler warnings (Christoph Reiter) (:bzbug:`777713`)
+* configure.ac: post-release version bump to 3.25.0 (Christoph Reiter)
+* Remove egg make target (Christoph Reiter) (:bzbug:`777719`)
+* Remove legacy docs (Christoph Reiter) (:bzbug:`777719`)
+
+3.24.1 - 2017-04-10
+-------------------
+
+* pygi-info: initialize GIArgument before passing it to g_constant_info_get_value (Christoph Reiter) (:bzbug:`772949`)
+* configure.ac: post-release version bump to 3.24.1 (Christoph Reiter)
+
+3.24.0 - 2017-03-20
+-------------------
+
+* configure.ac: pre-release version bump to 3.24.0 (Christoph Reiter)
+
+3.23.92 - 2017-03-13
+--------------------
+
+* overrides: Update for Gdk-4.0 and Gtk+-4.0 (Fabian Orccon) (:bzbug:`777680`)
+* Disable -Werror=missing-prototypes (Christoph Reiter) (:bzbug:`777534`)
+* Fix new PEP8 errors (Christoph Reiter) (:bzbug:`776009`)
+* Move pep8/pyflakes tests from 'make check' to 'make check.quality' (Christoph Reiter) (:bzbug:`764087`)
+* overrides: Update for Gtk-4.0 (Christoph Reiter) (:bzbug:`773315`)
+* Handle exception unreffing Variant at exit (Dan Nicholson) (:bzbug:`776092`)
+* Handle multiple deinit of callable cache (Dan Nicholson) (:bzbug:`776092`)
+* configure.ac: post-release version bump to 3.23.0 (Christoph Reiter)
+
+3.22.0 - 2016-09-19
+-------------------
+
+* configure.ac: pre-release version bump to 3.22.0 (Christoph Reiter)
+
+3.21.92 - 2016-09-11
+--------------------
+
+* Handle nullable filename parameters (Christoph Reiter) (:bzbug:`770821`)
+* Fix list/hashtable enum <-> hash conversion on 64-bit big endian (Aurelien Jarno) (:bzbug:`770608`)
+* Allow passing sockets to io_add_watch on win32 (Lukas K) (:bzbug:`766396`)
+* tests: use dbus-run-session instead of dbus-launch to run tests (Michael Biebl) (:bzbug:`770798`)
+* configure.ac: post-release version bump to 3.21.92 (Christoph Reiter)
+
+3.21.91 - 2016-08-25
+--------------------
+
+* Allow installing with pip (Mathieu Bridon) (:bzbug:`767988`)
+* Skip a test with older glib (Christoph Reiter) (:bzbug:`740301`)
+* Fix a test with Python 3.1/3.2 (Arfrever Frehtes Taifersar Arahesis, Christoph Reiter) (:bzbug:`740324`)
+* tests: Use places kwarg for assertAlmostEqual (Arfrever Frehtes Taifersar Arahesis, Christoph Reiter) (:bzbug:`740337`)
+* Print exception if marshalling a signal argument fails (Christoph Reiter) (:bzbug:`748198`)
+* overrides: allow treemodel sequence shorthands (Marinus Schraal) (:bzbug:`766580`)
+* Remove pygobject-external.h (Christoph Reiter) (:bzbug:`767084`)
+* Remove pygobject-private.h and rename pygobject.c to pygobject-object.c (Christoph Reiter) (:bzbug:`767084`)
+* Merge pyglib-private.h into pyglib.h (Christoph Reiter) (:bzbug:`767084`)
+* Remove pygi.h and pygi-private.h (Christoph Reiter) (:bzbug:`767084`)
+* configure.ac: post-release version bump to 3.21.1 (Simon Feltman)
+
+3.21.0 - 2016-04-24
+-------------------
+
+* gi: Add require_versions() function (Dustin Falgout) (:bzbug:`761141`)
+* test_gerror_novalue: Don't assign the error to a variable
+  (Iain Lane) (:bzbug:`764165`)
+* build: Do not enable code coverage based on lcov
+  (Emmanuele Bassi) (:bzbug:`764075`)
+
+3.20.1 - 2016-04-24
+-------------------
+
+* test_gerror_novalue: Don't assign the error to a variable
+  (Iain Lane) (:bzbug:`764165`)
+
+3.20.0 - 2016-03-21
+-------------------
+
+3.19.92 - 2016-03-15
+--------------------
+
+3.19.91 - 2016-03-01
+--------------------
+
+* Fix marshaling of GError stored in GValue
+  (Simon Feltman) (Thibault Saunier) (:bzbug:`761592`)
+* Fix marshaling or GError from Python to C from function calls
+  (Simon Feltman) (:bzbug:`685197`)
+* Error handling/reporting fixes (Christoph Reiter) (:bzbug:`751956`)
+* Fix crash due to GVariant implemented as PyGBoxed not PyGIStruct
+  (Christoph Reiter) (:bzbug:`751956`)
+* Fix crash with GValueArray stored in GValue
+  (Mikhail Fludkov) (:bzbug:`754359`)
+
+3.19.90 - 2016-02-20
+--------------------
+
+* tests: Set the active style context state before retrieving values
+  (Simon Feltman)
+* tests: Fix crash with empty drag source icon names
+  (Simon Feltman) (:bzbug:`762392`)
+* Try to import GdkX11 in Gdk overrides (Christoph Reiter) (:bzbug:`673396`)
+* Fix import warnings pointing to the wrong code with CPython 3.3/3.5
+  (Christoph Reiter) (:bzbug:`757184`)
+
+3.19.2 - 2015-10-31
+-------------------
+
+* tests: Fix failure due to new GTK+ warning regarding size_allocate()
+  (Simon Feltman)
+* Fix build warnings regarding _POSIX_C_SOURCE redefinition
+  (Simon Feltman)
+* Drop -std=c90 for now (Matthias Clasen)
+
+3.19.1 - 2015-10-30
+-------------------
+
+* Use a named tuple for returning multiple values (Christoph Reiter) (:bzbug:`727374`)
+* enum/flags: use gir info for type names and __repr__ instead of the gtype name (Christoph Reiter) (:bzbug:`657915`)
+* Improve and unify __repr__ format for PyGObject, PyGBoxed and PyGIStruct (Christoph Reiter) (:bzbug:`657915`)
+* Don't leak internal RepositoryError on import. (Christoph Reiter) (:bzbug:`756033`)
+* Import dependencies when importing typelibs from gi.repository (Christoph Reiter) (:bzbug:`656314`)
+* Fix Gdk.rectangle_intersect/rectangle_union missing with GTK+ 3.18 (Christoph Reiter) (:bzbug:`756364`)
+* Don't import inspect at module level (Christoph Reiter)
+* invoke state: add a free memory cache for PyGIInvokeArgState (Christoph Reiter) (:bzbug:`750658`)
+* invoke/closure: reduce g_slice_alloc usage (Christoph Reiter) (:bzbug:`750658`)
+* pep8: ignore new errors reported by pep8 1.6 (Christoph Reiter)
+* Bump g-i dependency to latest stable (Garrett Regier)
+* Avoid calling g_slist_length() during invoke (Garrett Regier)
+* Simplify closure_convert_arguments() (Garrett Regier)
+* Remove a level of indentation in convert_ffi_arguments() (Garrett Regier)
+* Prevent passing the user data multiple times to callbacks (Garrett Regier) (:bzbug:`750347`)
+* Support throwing exceptions in closures (Garrett Regier) (:bzbug:`710671`)
+* Don't emit require_version warning if namespace was loaded previously using g_irepository_require (Christoph Reiter) (:bzbug:`754491`)
+* configure.ac: post release version bump to 3.19.1 (Garrett Regier)
+
+3.18.2 - 2015-10-24
+-------------------
+
+* configure.ac: post release version bump to 3.18.2 (Christoph Reiter)
+
+3.18.1 - 2015-10-23
+-------------------
+
+* Fix Gdk.rectangle_intersect/rectangle_union missing with GTK+ 3.18 (Christoph Reiter) (:bzbug:`756364`)
+* pep8: ignore new errors reported by pep8 1.6 (Christoph Reiter)
+* Don't emit require_version warning if namespace was loaded previously using g_irepository_require (Christoph Reiter) (:bzbug:`754491`)
+* configure.ac: post release version bump to 3.18.1 (Garrett Regier)
+
+3.18.0 - 2015-09-22
+-------------------
+
+3.17.90 - 2015-08-19
+--------------------
+
+* Allow passing unicode lists to GStrv properties on Python 2
+  (Christoph Reiter) (:bzbug:`744745`)
+* Avoid a silent long to int truncation (Rui Matos) (:bzbug:`749698`)
+* Handle gtype marshalling (Mathieu Bridon) (:bzbug:`749696`)
+* pygi-foreign-cairo.c: fix include for py3cairo.h
+  (Daniel Hahler) (:bzbug:`746742`)
+* tests: Silence various error messages and warnings
+  (Christoph Reiter) (:bzbug:`751156`)
+* Fix test regression when xdg-user-dirs is not installed
+  (Christoph Reiter) (:bzbug:`751299`)
+* Explicitly check if an override exists instead of ImportError
+  (Garrett Regier) (:bzbug:`749532`)
+
+3.17.1 - 2015-06-15
+-------------------
+
+* Add gi.PyGIWarning used when import version is not specified
+  (Christoph Reiter) (:bzbug:`727379`)
+* Remove Gdk.Rectangle alias with newer gobject-introspection and GTK+
+  (Christoph Reiter) (:bzbug:`749625`)
+* overrides: Provide _overrides_module attribute
+  (Christoph Reiter) (:bzbug:`736678`)
+* overrides: Conditionalize touch override support in Gdk
+  (Simon Feltman) (:bzbug:`747717`)
+* Field setters: Remove unneeded type/range checks and unused code
+  (Christoph Reiter) (:bzbug:`746985`)
+* pygi-argument: Remove unused imports/includes
+  (Christoph Reiter) (:bzbug:`746985`)
+* Improve test coverage for field setters/getters
+  (Christoph Reiter) (:bzbug:`746985`)
+
+3.16.2 - 2015-06-15
+-------------------
+
+* overrides: Provide _overrides_module attribute
+  (Christoph Reiter) (:bzbug:`736678`)
+
+3.16.1 - 2015-04-13
+-------------------
+
+* overrides: Conditionalize touch override support in Gdk
+  (Simon Feltman) (:bzbug:`747717`)
+
+3.16.0 - 2015-03-24
+-------------------
+
+3.15.91 - 2015-03-05
+--------------------
+
+* tests: Don't use deprecated override attributes
+  (Christoph Reiter) (:bzbug:`743514`)
+* Add GLib.MINFLOAT etc. and mark GObject.G_MINFLOAT etc. deprecated
+  (Christoph Reiter) (:bzbug:`743514`)
+* Emit PyGIDeprecationWarning when accessing deprecated override attributes
+  (Christoph Reiter) (:bzbug:`743514`)
+* Add namespace and container name to all warnings/error messages
+  (Christoph Reiter) (:bzbug:`743468`)
+* tests: Add test for GIRepository.UnionInfo.get_size()
+  (Garrett Regier) (:bzbug:`745362`)
+* Avoid duping filenames when marshalling from Python to C
+  (Garrett Regier) (:bzbug:`744719`)
+
+3.15.0 - 2015-02-20
+-------------------
+
+* Avoid copying bytearrays from Python to C when transfer nothing (Garrett Regier) (:bzbug:`743278`)
+* Allows passing arguments to opaque Boxed types (Garrett Regier) (:bzbug:`743214`)
+* Emit ImportWarning when gi.require_version() is not used (Christoph Reiter) (:bzbug:`727379`)
+* Refactor overrides import/modules (Christoph Reiter) (:bzbug:`736678`)
+* Replace statically bound GLib.Variant.new_tuple() with GI (Simon Feltman) (:bzbug:`735199`)
+* overrides: Add Gdk.EventTouch union discrimination (Simon Feltman) (:bzbug:`736380`)
+* PyGObjectFlags: Remove a trailing comma on the enum. (Murray Cumming)
+* Remove redefinitions of function and vfunc cache typedefs (Simon Feltman) (:bzbug:`737874`)
+
+3.14.0 - 2014-09-22
+-------------------
+
+* configure.ac: pre release version bump to 3.14.0 (Simon Feltman)
+
+3.13.92 - 2014-09-15
+--------------------
+
+* tests: Add test for Gio.Application.add_main_option() (Simon Feltman)
+* tests: Split up various test cases (Simon Feltman) (:bzbug:`735193`)
+* Fix invalid read error in argument cleanup code (Simon Feltman)
+* Fix memory management problems with struct arguments to signals
+  (Simon Feltman) (:bzbug:`736175`)
+
+3.13.91 - 2014-09-01
+--------------------
+
+* docs: Fix return types in auto-generated doc strings (Simon Feltman)
+* Special case signal output arguments which are structs as
+  pass-by-reference (Simon Feltman) (:bzbug:`735486`)
+* Ignore closure callbacks when Python is not initialized
+  (Simon Feltman) (:bzbug:`722562`)
+* Change boxed init with args to warn instead of raise
+  (Christoph Reiter) (:bzbug:`727810`)
+* Fix crash in GList/GSList marshaling error handling path.
+  (Christoph Reiter) (:bzbug:`735201`)
+* Fix reference counting problems with GLib.Variant.new_tuple()
+  (Simon Feltman) (:bzbug:`735166`)
+* Skip marshalling NULL output arguments in Python closures
+  (Simon Feltman) (:bzbug:`735090`)
+
+3.13.90 - 2014-08-18
+--------------------
+
+* Support array lengths on struct fields (Simon Feltman) (:bzbug:`688792`)
+* Fast path Python property get access (Simon Feltman) (:bzbug:`723872`)
+* Unify accessing properties through props and get_property()
+  (Simon Feltman) (:bzbug:`726999`)
+* Refactor boxed wrapper memory management strategy
+  (Simon Feltman) (:bzbug:`726999`)
+* Replace GObject.signal_query with introspected version
+  (Simon Feltman) (:bzbug:`688792`)
+* Fix memory leak with unboxed caller allocated structs (Simon Feltman)
+* tests: Add failing tests for GObject sub-class doc-strings
+  (Piotr Iwaniuk) (:bzbug:`731452`)
+* Don't mask GObject sub-class doc strings in meta-class
+  (Tobias Mueller) (:bzbug:`731452`)
+
+3.13.4 - 2014-08-14
+-------------------
+
+* Marshaling cache refactor and cache usage in vfuncs
+  (Garrett Regier) (:bzbug:`727004`)
+* Fix array argument out and inout marshaling from vfuncs
+  (Garrett Regier) (:bzbug:`702508`)
+* Cleanup input args when marshaling in closures
+  (Garrett Regier) (:bzbug:`727004`)
+* Add deprecation warning for connect_object() with non-GObject argument
+  (Simon Feltman) (:bzbug:`688064`)
+* Add Python implementation of Object.connect_data()
+  (Simon Feltman) (:bzbug:`701843`)
+* Add GClosure marshaling cleanup (Simon Feltman) (:bzbug:`695128`)
+* Fix GCallback Python wrapper leak (Simon Feltman) (:bzbug:`695130`)
+* tests: Add failing test for marshaling an array of GValues through
+  signals (Martin Pitt) (:bzbug:`669496`)
+* Add protection against attempts at importing static bindings
+  (Simon Feltman) (:bzbug:`709183`)
+* Update and deprecate gi.overrides.keysyms (Simon Feltman) (:bzbug:`721295`)
+* Generate .dll libraries on windows (Ignacio Casal Quinteiro) (:bzbug:`734288`)
+* Windows build fixes (Alexey Pavlov)
+  (:bzbug:`734284`, :bzbug:`734289`, :bzbug:`734286`, :bzbug:`734287`)
+
+3.13.3 - 2014-06-23
+-------------------
+
+* demos: Cleanup CSS accordion demo to use a loop for adding buttons
+  (Simon Feltman)
+* refactor: Move builder connection utilities outside of Builder class
+  (Simon Feltman) (:bzbug:`701843`)
+* tests: Move TestSignals from test_everything into test_signal
+  (Simon Feltman) (:bzbug:`701843`)
+
+3.13.2 - 2014-05-26
+-------------------
+
+* Python 3.4 make check fixes (Simon Feltman) (:bzbug:`730411`)
+* overrides: Add Gtk.Container.child_get/set overrides (Simon Feltman)
+  (:bzbug:`685076`)
+* overrides: Make value argument to Widget.style_get_property optional
+  (Simon Feltman) (:bzbug:`685076`)
+* overrides: Make value argument to Container.child_get_property optional
+  (Simon Feltman) (:bzbug:`685076`)
+* Add GTypeClass methods as Python GObject class methods
+  (Johan Dahlin) (:bzbug:`685218`)
+* Add marshalling coercion for Python classes and instances to GTypeClass
+  (Simon Feltman) (:bzbug:`685218`)
+* Replace direct parent class call by super() (Andrew Grigorev) (:bzbug:`729970`)
+* Add cairo marshaling support for non-introspected signals
+  (Simon Feltman) (:bzbug:`694604`)
+* [New API] Add gi.require_foreign (Simon Feltman) (:bzbug:`707735`)
+* Initialize the foreign API at PyGI load time (Simon Feltman) (:bzbug:`694604`)
+* Move pygi foreign API into pygi-foreign-api.h (Simon Feltman) (:bzbug:`694604`)
+* Unify GLib.GError and GLib.Error (Simon Feltman) (:bzbug:`712519`)
+* PEP8 fixes (Simon Feltman)
+
+3.12.2 - 2014-05-26
+-------------------
+
+* PEP8 fixes (Simon Feltman)
+* Python 3.4 make check fixes (Simon Feltman) (:bzbug:`730411`)
+
+3.13.1 - 2014-04-28
+-------------------
+
+* Raise TypeError if arguments are passed to Boxed.__init__
+  (Christoph Reiter) (:bzbug:`727810`)
+* Gdk.Event: Override __setattr__ to set fields based on the event type
+  (Christoph Reiter) (:bzbug:`727810`)
+* Gdk.Event: Include GdkEventType in __repr__ (Christoph Reiter) (:bzbug:`727810`)
+* Fix crash with type checking for GObject arguments (Simon Feltman) (:bzbug:`727604`)
+* Do not leak info of destroy notify (Paolo Borelli)
+* Ignore GValueArray deprecations (Simon Feltman)
+* Raise ImportError when importing modules not found in repository
+  (Simon Feltman) (:bzbug:`726877`)
+* tests: Rename test_overrides to test_import_machinery
+  (Simon Feltman) (:bzbug:`726877`)
+
+3.12.1 - 2014-04-14
+-------------------
+
+* Fix crash with type checking invalid GObject arguments
+  (Simon Feltman) (:bzbug:`727604`)
+* Do not leak info of destroy notify (Paolo Borelli)
+
+3.12.0 - 2014-03-24
+-------------------
+
+3.11.92 - 2014-03-17
+--------------------
+
+* configure.ac: Remove option to build without libffi (Simon Feltman)
+* docs: Standardize Python doc strings (Simon Feltman)
+* Fix reference leaks with (transfer full) foreign struct returns
+  (Owen W. Taylor) (:bzbug:`726206`)
+
+3.11.91 - 2014-03-03
+--------------------
+
+* Use ffi_call directly instead of g_callable_info_invoke
+  (Simon Feltman) (:bzbug:`723642`)
+* configure.ac: Use -std=c90 and error on declaration-after-statement
+  (Simon Feltman)
+* Fix Build on Visual Studio (Chun-wei Fan) (:bzbug:`725122`)
+
+3.11.90 - 2014-02-17
+--------------------
+
+* Use GObject type checking for instance arguments (Simon Feltman) (:bzbug:`724009`)
+* configure.ac: post release version bump to 3.11.90 (Simon Feltman)
+
+3.11.5 - 2014-02-03
+-------------------
+
+* cache refactoring: Move all cache marshalers into files based on type
+  (Simon Feltman) (:bzbug:`709700`)
+* tests: Add test for an owned boxed struct passed in a callback
+  (Mike Gorse) (:bzbug:`722899`)
+* build: Add --without-common configure option for package maintainers
+  (Patrick Welche) (:bzbug:`721646`)
+* demo: Add TreeModel interface implementation demonstration
+  (Simon Feltman)
+* build: Set PLATFORM_VERSION again to 3.0 (Colin Walters)
+* tests: Run PyFlakes and PEP8 only on SUBDIRS (Simon Feltman)
+* Merge static PyGLib and PyGObject modules into PyGI
+  (Simon Feltman) (:bzbug:`712197`)
+* Add test for callback user data arguments with following arguments
+  (Martin Pitt) (:bzbug:`722104`)
+
+3.11.4 - 2014-01-13
+-------------------
+
+* overrides: Fix __repr__ for various Gdk structs (Simon Feltman)
+* Add enum and flags member methods (Simon Feltman) (:bzbug:`693099`)
+* python.m4: g/c JD_PYTHON_CHECK_VERSION (Patrick Welche) (:bzbug:`721662`)
+* Support union creation with PyGIStruct (Simon Feltman)
+* docs: List constructors in object and struct doc strings
+  (Simon Feltman) (:bzbug:`708060`)
+* docs: Fix array length argument skipping with preceding out arguments
+* docs: Add return values and skip implicit out arguments in functions
+  (Simon Feltman) (:bzbug:`697356`)
+* docs: Skip implicit array length args when building function doc
+  strings (Simon Feltman) (:bzbug:`697356`)
+* gtk-demo: Add CSS demos (Gian Mario Tagliaretti) (:bzbug:`719722`)
+* build: Avoid clash between gi/types.py and stdlib
+  (Colin Watson) (:bzbug:`721025`)
+
+3.11.3 - 2013-12-16
+-------------------
+
+* Replace usage of PyGIBoxed_Type with PyGIStruct_Type
+  (Simon Feltman) (:bzbug:`581525`)
+
+3.11.2 - 2013-11-17
+-------------------
+
+* gkt-demo: Change main info/source notebook into a GtkStack (Simon Feltman)
+* Add deprecation warnings and cleanup class initializer overrides (Simon Feltman) (:bzbug:`705810`)
+* Fix dir method for static GParamSpec in Python 3 (Simon Feltman)
+* Remove overzealous argument checking for callback userdata (Simon Feltman) (:bzbug:`711173`)
+
+3.10.2 - 2013-11-11
+-------------------
+
+* Fix thread safety problems by always enabling the GIL
+  (Simon Feltman) (:bzbug:`709223`, :bzbug:`710447`)
+
+3.11.1 - 2013-10-28
+-------------------
+
+* Fix toggleref safety problems by always enabling the GIL
+  (Simon Feltman) (:bzbug:`709223`)
+* Add consistent GLib.MainLoop SIGINT cleanup (Simon Feltman) (:bzbug:`710978`)
+* docs: Add a keyword value of None for allow-none annotations
+  (Simon Feltman) (:bzbug:`640812`)
+* Remove overrides for supporting pre-3.10 GObject signal functions
+  (Simon Feltman)
+* Add threads_init back as a requirement for non-Python threaded repos
+  (Simon Feltman) (:bzbug:`710447`)
+* Add dir method to GObject props accessor (Simon Feltman) (:bzbug:`705754`)
+* Remove PyGObjectWeakRef now that g_binding_unbind exists
+  (Simon Feltman) (:bzbug:`699571`)
+* Fix lots of memory leaks leaks (Simon Feltman) (:bzbug:`693402`, :bzbug:`709397`)
+* Add support for variable user data arguments (Simon Feltman) (:bzbug:`640812`)
+* Bump glib and g-i dependencies to latest stable. (Martin Pitt)
+* Fix TypeError when setting drag target_list to None (Nuno Araujo)
+  (:bzbug:`709926`)
+* Use qdata for wrapper retrieval in toggle reference notifications
+  (Simon Feltman) (:bzbug:`709223`)
+* Expose all GI enum and flags types (Simon Feltman) (:bzbug:`709008`)
+* Add support for default arguments annotated with allow-none
+  (Simon Feltman) (:bzbug:`640812`)
+* Refactor argument cache handling (Simon Feltman) (:bzbug:`640812`)
+* Remove support for allowing PyObjects as void pointers
+  (Simon Feltman) (:bzbug:`688081`)
+
+3.10.1 - 2013-10-14
+-------------------
+
+* Fix TypeError when setting drag target_list to None (Nuno Araujo)
+  (:bzbug:`709926`)
+* Use qdata for wrapper retrieval in toggle reference notifications
+  (Simon Feltman) (:bzbug:`709223`)
+* Fix memory leak for caller allocated GValue out arguments
+  (Simon Feltman) (:bzbug:`709397`)
+
+3.10.0 - 2013-09-23
+-------------------
+
+* Fix test_gi.TestProjectVersion.test_version_str() (Martin Pitt)
+
+3.9.92 - 2013-09-16
+-------------------
+
+* Fix union argument regression when marshaling from python
+  (Simon Feltman) (:bzbug:`703873`)
+* Fix GLib.Source sub-classing with initializer args (Simon Feltman)
+  (:bzbug:`707904`)
+* Copy __doc__ when wrapping function (Vratislav Podzimek)
+
+3.9.91 - 2013-09-02
+-------------------
+
+* Fix GObject signal methods to work with new annotations
+  (Simon Feltman) (:bzbug:`707280`)
+* Fix build on C89 Compilers (Chun-wei Fan) (:bzbug:`707264`)
+* Change boxed type checking in marshaling to use __gtype__ attribute
+  (Simon Feltman) (:bzbug:`707140`)
+* Use G_IS_VALUE for checking return values in closure marshaling
+  (Simon Feltman) (:bzbug:`707170`)
+* Fix PEP-8 errors in propertyhelper.py (Yanko Kaneti) (:bzbug:`706319`)
+
+3.9.90 - 2013-08-19
+-------------------
+
+* Create GLib.Pid in the same way on python 2 and 3 (Benjamin Berg)
+  (:bzbug:`705451`)
+* Use PyLong_Type.tp_new for GLib.Pid (Benjamin Berg) (:bzbug:`705451`)
+* Add accumulator and accu_data arguments to GObject.Signal decorator
+  (Simon Feltman) (:bzbug:`705533`)
+* Pass return values through the GObject.Signal.emit wrapper
+  (Simon Feltman) (:bzbug:`705530`)
+
+3.9.5 - 2013-07-29
+------------------
+
+* Ensure exceptions set in closure out argument marshaling are printed
+  (Simon Feltman) (:bzbug:`705064`)
+* Always raise OverflowError for marshaling integers from Python
+  (not ValueError or OverflowError) (Simon Feltman) (:bzbug:`705057`)
+* Cleanup invoke args and kwargs combiner code (Simon Feltman) (:bzbug:`640812`)
+* gtk-demo: Change demo to use Gtk.Application (Simon Feltman) (:bzbug:`698547`)
+* Add callable and descriptor protocols to PyGICallableInfo
+  (Simon Feltman) (:bzbug:`704037`)
+* Unify basic type argument marshaling for methods, closures, and
+  properties (Simon Feltman) (:bzbug:`693405`)
+* Override GValue.set/get_boxed with static C marshaler
+  (Simon Feltman) (:bzbug:`688081`, :bzbug:`693405`)
+* Add deprecation warning for marshaling arbitrary objects as pointers
+  (Simon Feltman) (:bzbug:`688081`)
+* Replace usage of __import__ with importlib.import_module
+  (Simon Feltman) (:bzbug:`682320`)
+* Always unref the GiTypeInfo when generating an argument cache
+  (Mike Gorse) (:bzbug:`703973`)
+* Unref interface info when fetching enums or flags
+  (Mike Gorse) (:bzbug:`703960`)
+* Speed up MRO calculation (Daniel Drake) (:bzbug:`703829`)
+* Add GIL safety to pyobject_copy for copying boxed PyObjects
+  (Simon Feltman) (:bzbug:`688081`)
+* Add marshaling of GI_TYPE_TAG_VOID held in a GValue to int
+  (Simon Feltman) (:bzbug:`694233`)
+* GTK overrides: Make connect_signals handle tuple
+  (Cole Robinson) (:bzbug:`693994`)
+* Re-add support for passing GValue's by reference
+  (Simon Feltman) (:bzbug:`701058`)
+* Clear return value of closures to zero when an exception occurs
+  (Simon Feltman) (:bzbug:`702552`)
+* Don't use doctest syntax in docstrings for examples
+  (Martin Pitt) (:bzbug:`701009`)
+* Add support for properties of type GInterface
+  (Garrett Regier) (:bzbug:`703456`)
+* pygtkcompat: Fix for missing methods on Windows
+  (Martin Pitt) (:bzbug:`702787`)
+* gi/pygi-info.c: Avoid C99-style variable declaration
+  (Chun-wei Fan) (:bzbug:`702786`)
+
+3.8.3 - 2013-07-05
+------------------
+
+* Add marshalling of GI_TYPE_TAG_VOID held in a GValue to int.  While
+  not particularly useful this allows some callbacks in WebKit to
+  function without causing a segfault. (Simon Feltman) (:bzbug:`694233`)
+* pygtkcompat: Fix for missing methods on Windows (Martin Pitt)
+  (:bzbug:`702787`)
+* gi/pygi-info.c: Avoid C99-style variable declaration (Chun-wei Fan)
+  (:bzbug:`702786`)
+* Clear return value of closures to zero when an exception occures
+  (Simon Feltman) (:bzbug:`702552`)
+* Re-add support for passing GValue's by reference (Simon Feltman)
+  (:bzbug:`701058`)
+* Don't use doctest syntax in docstrings for examples, to fix test
+  failures with pyflakes 0.7.x (Martin Pitt) (:bzbug:`701009`)
+* examples/option.py: Port to GI and Python 3 (Martin Pitt)
+
+3.9.2 - 2013-05-28
+------------------
+
+* examples/option.py: Port to GI and Python 3 (Martin Pitt)
+* Fix vfunc info search for classes with multiple inheritance
+  (Simon Feltman) (:bzbug:`700092`)
+* Fix closure argument conversion for enum and flag in args
+  (Simon Feltman)
+* Fix marshaling Python to FFI return value for enum and flags
+  (Simon Feltman)
+* Remove half implemented GC in PyGIBaseInfo, PyGIStruct, and PyGIBoxed
+  (Simon Feltman) (:bzbug:`677091`)
+* Replace usage of pyg_begin_allow_threads with Py_BEGIN_ALLOW_THREADS
+  (Simon Feltman) (:bzbug:`699440`)
+* Remove and deprecate API for setting of thread blocking functions
+  (Simon Feltman) (:bzbug:`699440`)
+
+3.8.2 - 2013-05-13
+------------------
+
+* Fix vfunc info search for classes with multiple inheritance
+  (Simon Feltman) (:bzbug:`700092`)
+* Fix closure argument conversion for enum and flag in args
+  (Simon Feltman)
+* Fix marshaling Python to FFI return value for enum and flags
+  (Simon Feltman)
+* Use correct class for GtkEditable's get_selection_bounds() function
+  (Mike Ruprecht) (:bzbug:`699096`)
+* Test results of g_base_info_get_name for NULL (Simon Feltman)
+  (:bzbug:`698829`)
+* Change interpretation of NULL pointer field from None to 0
+  (Simon Feltman) (:bzbug:`698366`)
+* Remove Python keyword escapement in Repository.find_by_name
+  (Simon Feltman) (:bzbug:`697363`)
+
+3.9.1 - 2013-04-30
+------------------
+
+* gtk-demo: Wrap description strings at 80 characters (Simon Feltman)
+  (:bzbug:`698547`)
+* gtk-demo: Use textwrap to reformat description for Gtk.TextView
+  (Simon Feltman) (:bzbug:`698547`)
+* gtk-demo: Use GtkSource.View for showing source code (Simon Feltman)
+  (:bzbug:`698547`)
+* Use correct class for GtkEditable's get_selection_bounds() function
+  (Mike Ruprecht) (:bzbug:`699096`)
+* Test results of g_base_info_get_name for NULL (Simon Feltman)
+  (:bzbug:`698829`)
+* Add ObjectInfo.get_class_struct() (Johan Dahlin) (:bzbug:`685218`)
+* Change interpretation of NULL pointer field from None to 0
+  (Simon Feltman) (:bzbug:`698366`)
+* Do not build tests until needed (Sobhan Mohammadpour) (:bzbug:`698444`)
+* pygi-convert: Support toolbar styles (Kai Willadsen) (:bzbug:`698477`)
+* pygi-convert: Support new-style constructors for Gio.File
+  (Kai Willadsen) (:bzbug:`698477`)
+* pygi-convert: Add some support for recent manager constructs
+  (Kai Willadsen) (:bzbug:`698477`)
+* pygi-convert: Don't transform arbitrary keysym imports
+  (Kai Willadsen) (:bzbug:`698477`)
+* Remove Python keyword escapement in Repository.find_by_name
+  (Simon Feltman) (:bzbug:`697363`)
+* Optimize signal lookup in gi repository (Daniel Drake) (:bzbug:`696143`)
+* Optimize connection of Python-implemented signals (Daniel Drake)
+  (:bzbug:`696143`)
+* Consolidate signal connection code (Daniel Drake) (:bzbug:`696143`)
+* Fix setting of struct property values (Daniel Drake)
+* Optimize property get/set when using GObject.props (Daniel Drake)
+  (:bzbug:`696143`)
+* configure.ac: Fix PYTHON_SO with Python3.3 (Christoph Reiter)
+  (:bzbug:`696646`)
+* Simplify registration of custom types (Daniel Drake) (:bzbug:`696143`)
+* pygi-convert.sh: Add GStreamer rules (Christoph Reiter) (:bzbug:`697951`)
+* pygi-convert: Add rule for TreeModelFlags (Jussi Kukkonen)
+* Unify GI marshalling code (Simon Feltman) (:bzbug:`693405`)
+* Use distutils.sysconfig to retrieve the python include path.
+  (Christoph Reiter) (:bzbug:`696648`)
+* Support PEP 3149 (ABI version tagged .so files) (Christoph Reiter)
+  (:bzbug:`696646`)
+* Fix stack corruption due to incorrect format for argument parser
+  (Simon Feltman) (:bzbug:`696892`)
+* Deprecate GLib and GObject threads_init (Simon Feltman) (:bzbug:`686914`)
+* Drop support for Python 2.6 (Martin Pitt)
+* Remove static PollFD bindings (Martin Pitt) (:bzbug:`686795`)
+* Drop test skipping due to too old g-i (Martin Pitt)
+* Bump glib and g-i dependencies (Martin Pitt)
+
+3.8.1 - 2013-04-15
+------------------
+
+* pygi-convert.sh: Add GStreamer rules (Christoph Reiter) (:bzbug:`697951`)
+* pygi-convert: Add rule for TreeModelFlags (Jussi Kukkonen)
+* Fix enum and flags marshaling type assumptions (Simon Feltman)
+* Use g_strdup() consistently (Martin Pitt) (:bzbug:`696650`)
+* Fix stack corruption due to incorrect format for argument parser
+  (Simon Feltman) (:bzbug:`696892`)
+
+3.8.0 - 2013-03-25
+------------------
+
+* tests: Fix incorrect assumption when testing pyglib version
+  (Simon Feltman)
+
+3.7.92 - 2013-03-18
+-------------------
+
+* Fix stack smasher when marshaling enums as a vfunc return value
+  (Simon Feltman) (:bzbug:`637832`)
+* Change base class of PyGIDeprecationWarning based on minor version
+  (Simon Feltman) (:bzbug:`696011`)
+* autogen.sh: Source gnome-autogen to fix out of source builddir (Alban
+  Browaeys) (:bzbug:`694889`)
+* pygtkcompat: Make gdk.Window.get_geometry return tuple of 5
+  (Simon Feltman)
+* pygtkcompat: Initialize hint to zero in set_geometry_hints
+  (Simon Feltman)
+* Remove incorrect bounds check with property helper flags
+  (Simon Feltman)
+* Fix crash when setting property of type object to an incorrect type
+  (Simon Feltman) (:bzbug:`695420`)
+* Give more informative error when setting property to incorrect type
+  (Simon Feltman) (:bzbug:`695420`)
+
+3.7.91.1 - 2013-03-05
+---------------------
+
+* Revert "Drop gi.overrides.overridefunc()" (Martin Pitt) (:bzbug:`695199`)
+
+3.7.91 - 2013-03-04
+-------------------
+
+* Fix many memory leaks (:bzbug:`675726`, :bzbug:`693402`, :bzbug:`691501`, :bzbug:`510511`, :bzbug:`691501`,
+  :bzbug:`672224`, and several more which are detected by our test suite)
+  (Martin Pitt)
+* Dot not clobber original Gdk/Gtk functions with overrides
+  (Martin Pitt) (:bzbug:`686835`)
+* Optimize GValue.get/set_value by setting GValue.g_type to a local
+  (Simon Feltman) (:bzbug:`694857`)
+* Run tests with G_SLICE=debug_blocks (Martin Pitt) (:bzbug:`691501`)
+* Add override helper for stripping boolean returns (Martin Pitt)
+  (:bzbug:`694431`)
+* Drop obsolete pygobject_register_sinkfunc() declaration (Martin Pitt)
+  (:bzbug:`639849`)
+* Fix marshalling of C arrays with explicit length in signal arguments
+  (Martin Pitt) (:bzbug:`662241`)
+* Fix signedness, overflow checking, and 32 bit overflow of GFlags
+  (Martin Pitt) (:bzbug:`693121`)
+* gi/pygi-marshal-from-py.c: Fix build on Visual C++ (Chun-wei Fan)
+  (:bzbug:`692856`)
+* Raise DeprecationWarning on deprecated callables (Martin Pitt)
+  (:bzbug:`665084`)
+* pygtkcompat: Add Widget.window, scroll_to_mark, and window methods
+  (Simon Feltman) (:bzbug:`694067`)
+* pygtkcompat: Add Gtk.Window.set_geometry_hints which accepts keyword
+  arguments (Simon Feltman) (:bzbug:`694067`)
+* Ship pygobject.doap for autogen.sh (Martin Pitt) (:bzbug:`694591`)
+* Fix crashes in various GObject signal handler functions
+  (Simon Feltman) (:bzbug:`633927`)
+* pygi-closure: Protect the GSList prepend with the GIL (Olivier Crête)
+  (:bzbug:`684060`)
+* generictreemodel: Fix bad default return type for get_column_type
+  (Simon Feltman)
+
+3.7.90 - 2013-02-19
+-------------------
+
+* overrides: Fix inconsistencies with drag and drop target list API
+  (Simon Feltman) (:bzbug:`680640`)
+* pygtkcompat: Add pygtk compatible GenericTreeModel implementation
+  (Simon Feltman) (:bzbug:`682933`)
+* overrides: Add support for iterables besides tuples for TreePath
+  creation (Simon Feltman) (:bzbug:`682933`)
+* Prefix __module__ attribute of function objects with gi.repository
+  (Niklas Koep) (:bzbug:`693839`)
+* configure.ac: only enable code coverage when available, to fix
+  autogen.sh with older gnome-commons (Jonathan Ballet) (:bzbug:`693328`)
+* Correctly set properties on object with statically defined properties
+  (Jonathan Ballet) (:bzbug:`693618`)
+* autogen.sh: Use gnome-autogen.sh (Martin Pitt) (:bzbug:`693328`)
+* Fix reference leaks with transient floating objects (Simon Feltman)
+  (:bzbug:`687522`)
+
+3.7.5.1 - 2013-02-05
+--------------------
+
+* Fix ABI break with pygobject.h from 3.7.5 (Simon Feltman) (:bzbug:`675726`)
+
+3.7.5 - 2013-02-04
+------------------
+
+* Move various signal methods from static bindings to GI
+  (Simon Feltman) (:bzbug:`692918`)
+* GLib overrides: Support unpacking 'maybe' variants (Paolo Borelli)
+  (:bzbug:`693032`)
+* Fix ref count leak when creating pygobject wrappers for input args
+  (Mike Gorse) (:bzbug:`675726`)
+* Prefix names of typeless enums and flags for GType registration
+  (Simon Feltman) (:bzbug:`692515`)
+* Fix compilation with non-C99 compilers such as Visual C++
+  (Chun-wei Fan) (:bzbug:`692856`)
+* gi/overrides/Glib.py: Fix running on Windows/non-Unix (Chun-wei Fan)
+* Do not immediately initialize Gdk and Gtk on import (Martin Pitt)
+  (:bzbug:`692300`)
+* Accept Â±inf and NaN as float and double values (Martin Pitt) (:bzbug:`692381`)
+* Fix repr() of GLib.Variant (Martin Pitt)
+* Fix gtk-demo for Python 3 (Martin Pitt)
+* Define GObject.TYPE_VALUE gtype constant (Martin Pitt)
+* gobject: Go through introspection on property setting (Olivier Crête)
+  (:bzbug:`684062`)
+* Clean up caller-allocated GValues and their memory (Mike Gorse)
+  (:bzbug:`691820`)
+* Use GNOME_COMPILE_WARNINGS from gnome-common (Martin Pitt)
+
+3.7.4 - 2013-01-14
+------------------
+
+* Allow setting values through GtkTreeModelFilter (Simonas Kazlauskas)
+  (:bzbug:`689624`)
+* Support GParamSpec signal arguments from Python (Martin Pitt)
+  (:bzbug:`683099`)
+* pygobject_emit(): Fix cleanup on error (Martin Pitt)
+* Add signal emission methods to TreeModel which coerce the path
+  argument (Simon Feltman) (:bzbug:`682933`)
+* Add override for GValue (Bastian Winkler) (:bzbug:`677473`)
+* Mark caller-allocated boxed structures as having a slice allocated
+  (Mike Gorse) (:bzbug:`699501`)
+* pygi-property: Support boxed GSList/GList types (Olivier Crête)
+  (:bzbug:`684059`)
+* tests: Add missing backwards compat methods for Python 2.6
+  (Martin Pitt) (:bzbug:`691646`)
+* Allow setting TreeModel values to None (Simon Feltman) (:bzbug:`684094`)
+* Set clean-up handler for marshalled arrays (Mike Gorse) (:bzbug:`691509`)
+* Support setting string fields in structs (Vadim Rutkovsky) (:bzbug:`678401`)
+* Permit plain integers for "gchar" values (Martin Pitt)
+* Allow single byte values for int8 types (Martin Pitt) (:bzbug:`691524`)
+* Fix invalid memory access handling errors when registering an enum
+  type (Mike Gorse)
+* Fix (out) arguments in callbacks (Martin Pitt)
+* Fix C to Python marshalling of struct pointer arrays (Martin Pitt)
+* Don't let Property.setter() method names define property names
+  (Martin Pitt) (:bzbug:`688971`)
+* Use g-i stack allocation API (Martin Pitt) (:bzbug:`615982`)
+* pyg_value_from_pyobject: support GArray (Ray Strode) (:bzbug:`690514`)
+* Fix obsolete automake macros (Marko Lindqvist) (:bzbug:`691101`)
+* Change dynamic enum and flag gtype creation to use namespaced naming
+  (Simon Feltman) (:bzbug:`690455`)
+* Fix Gtk.UIManager.add_ui_from_string() override for non-ASCII chars
+  (Jonathan Ballet) (:bzbug:`690329`)
+* Don't dup strings before passing them to type registration functions
+  (Mike Gorse) (:bzbug:`690532`)
+* Fix marshalling of arrays of boxed struct values (Carlos Garnacho)
+  (:bzbug:`656312`)
+
+3.7.3 - 2012-12-17
+------------------
+
+* Add support for caller-allocated GArray out arguments (Martin Pitt)
+  (:bzbug:`690041`)
+* [API add] Re-support calling GLib.io_add_watch with an fd or Python
+  file (Martin Pitt)
+* pygtkcompat: Work around IndexError on large flags (Martin Pitt)
+* Fix pyg_value_from_pyobject() range check for uint (Martin Pitt)
+* Fix tests to work with g-i 1.34.2 (Martin Pitt)
+* Fix wrong refcount for GVariant property defaults (Martin Pitt)
+  (:bzbug:`689267`)
+* Fix array arguments on 32 bit architectures (Martin Pitt)
+* Add backwards compatible API for GLib.unix_signal_add_full()
+  (Martin Pitt)
+* Drop MININT64/MAXUINT64 workaround, current g-i gets this right now
+  (Martin Pitt)
+* Fix maximum and minimum ranges of TYPE_(U)INT64 properties
+  (Simonas Kazlauskas) (:bzbug:`688949`)
+* Ship pygi-convert.sh in tarballs (Martin Pitt) (:bzbug:`688697`)
+
+3.7.2 - 2012-11-19
+------------------
+
+* [API change] Drop almost all static GLib bindings and replace them
+  with proper introspection. This gets rid of several cases where the
+  PyGObject API was not matching the real GLib API, makes the full GLib
+  API available through introspection, and makes the code smaller,
+  easier to maintain. For backwards compatibility, overrides are
+  provided to emulate the old static binding API, but this will throw a
+  PyGIDeprecationWarning for the cases that diverge from the official
+  API (in particular, GLib.io_add_watch() and GLib.child_watch_add()
+  being called without a priority argument). (Martin Pitt, Simon Feltman)
+* [API change] Deprecate calling GLib API through the GObject
+  namespace. This has always been a misnomer with introspection, and
+  will be removed in a later version; for now this throws a
+  PyGIDeprecationWarning.
+* [API change] Do not bind gobject_get_data() and gobject_set_data().
+  These have been deprecated for a cycle, now dropped entirely.
+  (Steve Frécinaux) (:bzbug:`641944`)
+* [API change] Deprecate void pointer fields as general PyObject
+  storage. (Simon Feltman) (:bzbug:`683599`)
+* Add support for GVariant properties (Martin Pitt)
+* Add type checking to GVariant argument assignment (Martin Pitt)
+* Fix marshalling of arrays of struct pointers to Python (Carlos Garnacho) (:bzbug:`678620`)
+* Fix Gdk.Atom to have a proper str() and repr() (Martin Pitt) (:bzbug:`678620`)
+* Make sure g_value_set_boxed does not cause a buffer overrun with GStrvs (Simon Feltman) (:bzbug:`688232`)
+* Fix leaks with GValues holding boxed and object types (Simon Feltman) (:bzbug:`688137`)
+* Add doc strings showing method signatures for gi methods (Simon Feltman) (:bzbug:`681967`)
+* Set Property instance doc string and blurb to getter doc string (Simon Feltman) (:bzbug:`688025`)
+* Add GObject.G_MINSSIZE (Martin Pitt)
+* Fix marshalling of GByteArrays (Martin Pitt)
+* Fix marshalling of ssize_t to smaller ints (Martin Pitt)
+* Add support for lcov code coverage, and add a lot of missing
+  GIMarshallingTests and g-i Regress tests. (Martin Pitt)
+* pygi-convert: remove deprecated GLib â†’ GObject conversions (Jose Rostagno)
+* Add support for overriding GObject.Object (Simon Feltman) (:bzbug:`672727`)
+* Add --with-python configure option (Martin Pitt)
+* Do not prefer unversioned "python" when configuring, as some distros
+  have "python" as Python 3. Use Python 3 by default if available. Add
+  --with-python configure option as an alternative to setting $PYTHON,
+  whic is more discoverable. (Martin Pitt)
+* Fix property lookup in class hierarchy (Daniel Drake) (:bzbug:`686942`)
+* Move property and signal creation into _class_init() (Martin Pitt) (:bzbug:`686149`)
+* Fix duplicate symbols error on OSX (John Ralls)
+* [API add] Add get_introspection_module for getting un-overridden modules (Simon Feltman) (:bzbug:`686828`)
+* Work around wrong 64 bit constants in GLib Gir (Martin Pitt) (:bzbug:`685022`)
+* Mark GLib.Source.get_current_time() as deprecated (Martin Pitt)
+* Fix OverflowError in source_remove() (Martin Pitt) (:bzbug:`684526`)
+
+3.4.2 - 2012-11-12
+------------------
+
+* Fix marshalling of GByteArrays (Martin Pitt)
+* Fix marshalling of ssize_t to smaller ints (Martin Pitt)
+* Fix crash with GLib.child_watch_add (Daniel Narvaez) (:bzbug:`688067`)
+* Fix various bugs in GLib.IOChannel (Martin Pitt)
+* Work around wrong 64 bit constants in GLib Gir (Martin Pitt)
+  (:bzbug:`685022`)
+* Fix OverflowError in source_remove() (Martin Pitt) (:bzbug:`684526`)
+* Fix Signal decorator to not use base class gsignals dict
+  (Simon Feltman) (:bzbug:`686496`)
+
+3.7.1 - 2012-10-22
+------------------
+
+* Bump version to 3.7.1; let's follow the real GNOME versioning from
+  now on (Martin Pitt)
+* Change install_properties to not use getattr on classes
+  (Simon Feltman) (:bzbug:`686559`)
+* Move property install function into propertyhelper.py (Simon Feltman)
+  (:bzbug:`686559`)
+* Fix Signal decorator to not use base class gsignals dict
+  (Simon Feltman) (:bzbug:`686496`)
+* tests: Consistently use GLib.MainLoop (Martin Pitt)
+* Install the .egg-info files into correct multilib directory
+  (Kalev Lember) (:bzbug:`686315`)
+* Fix leaked vfunc return values (Simon Feltman) (:bzbug:`686140`)
+* Skip Regress tests with --disable-cairo (Martin Pitt) (:bzbug:`685094`)
+* Fix leak with python callables as closure argument. (Simon Feltman)
+  (:bzbug:`685598`)
+* Gio overrides: Handle setting GSettings enum keys (Martin Pitt)
+  (:bzbug:`685947`)
+* tests: Check reading GSettings enums in Gio overrides (Martin Pitt)
+* Fix unsigned values in GArray/GList/GSList/GHash (Martin Pitt)
+  (:bzbug:`685860`)
+* _pygi_marshal_from_py_uint64(): Use correct data type in py2.7 check
+  (Alban Browaeys) (:bzbug:`685000`)
+* Install an .egg-info file (Johan Dahlin) (:bzbug:`680138`)
+* PyGProps_getattro(): Fix GObjectClass leak (Johan Dahlin) (:bzbug:`685218`)
+* pygobject.c: Don't leak GObjectClass reference (Olivier Crête)
+  (:bzbug:`684062`)
+* Fix memory leak in _pygi_argument_to_array() (Alban Browaeys)
+  (:bzbug:`685082`)
+* Fix error messages for out of range numbers (Martin Pitt) (:bzbug:`684314`)
+* Kill dbus-daemon after running tests (Martin Pitt) (:bzbug:`685009`)
+* GVariant overrides: Support empty tuple arrays (Martin Pitt)
+  (:bzbug:`684928`)
+* tests: Fix wrong return type in test_int64_callback() (Martin Pitt)
+  (:bzbug:`684700`)
+* Fix GValue marshalling of long and unsigned long (Giovanni Campagna)
+  (:bzbug:`684331`)
+* Clean up deprecation message for assigning gpointers to objects.
+  (Simon Feltman) (:bzbug:`683599`)
+* pygi-property: Lookup property in base classes of non-introspected
+  types (Olivier Crête) (:bzbug:`684058`)
+
+3.4.1.1 - 2012-10-17
+--------------------
+
+* Bump g-i dependency to >= 1.34.1.1 (Paolo Borelli)
+* Fix leaked vfunc return values (Simon Feltman) (:bzbug:`686140`)
+* Install egg-info files in the right dir  Kalev Lember) (:bzbug:`686315`)
+
+3.4.1 - 2012-10-15
+------------------
+
+* Skip Regress tests with --disable-cairo (Martin Pitt) (:bzbug:`685094`)
+* Fix leak with python callables as closure argument. (Simon Feltman) (:bzbug:`685598`)
+* Gio overrides: Handle setting GSettings enum keys (Martin Pitt) (:bzbug:`685947`)
+* Fix unsigned values in GArray/GList/GSList/GHash (Martin Pitt) (:bzbug:`685860`)
+* _pygi_marshal_from_py_uint64(): Use correct data type in py2.7 check (Alban Browaeys) (:bzbug:`685000`)
+* Install an .egg-info file (Johan Dahlin) (:bzbug:`680138`)
+* PyGProps_getattro(): Fix GObjectClass leak (Johan Dahlin) (:bzbug:`685218`)
+* pygobject.c: Don't leak GObjectClass reference (Olivier Crête) (:bzbug:`684062`)
+* Fix memory leak in _pygi_argument_to_array() (Alban Browaeys) (:bzbug:`685082`)
+* Fix error messages for out of range numbers (Martin Pitt) (:bzbug:`684314`)
+* Kill dbus-daemon after running tests (Martin Pitt) (:bzbug:`685009`)
+* GVariant overrides: Support empty tuple arrays (Martin Pitt) (:bzbug:`684928`)
+* tests: Fix wrong return type in test_int64_callback() (Martin Pitt) (:bzbug:`684700`)
+* Fix GValue marshalling of long and unsigned long (Giovanni Campagna) (:bzbug:`684331`)
+* Clean up deprecation message for assigning gpointers to objects. (Simon Feltman) (:bzbug:`683599`)
+* pygi-property: Lookup property in base classes of non-introspected types (Olivier Crête) (:bzbug:`684058`)
+
+3.4.0 - 2012-09-24
+------------------
+
+* Bump g-i dependency to 1.33.14 (Martin Pitt)
+
+3.3.92 - 2012-09-17
+-------------------
+
+* [API add] Add ObjectInfo.get_abstract method (Simon Feltman) (:bzbug:`675581`)
+* Add deprecation warning when setting gpointers to anything other than
+  int. (Simon Feltman) (:bzbug:`683599`)
+* argument: Fix 64bit integer convertion from GValue (Nicolas Dufresne)
+  (:bzbug:`683596`)
+* Improve setting pointer fields/arguments to NULL using None
+  (Simon Feltman) (:bzbug:`683150`)
+* Bump g-i dependency to 1.33.10 (Martin Pitt)
+* Fix -uninstalled.pc.in file (Thibault Saunier) (:bzbug:`683379`)
+* Various test suite additions and improvements (Martin Pitt)
+
+3.3.91 - 2012-09-03
+-------------------
+
+* Fix exception test case for Python 2 (Martin Pitt)
+* Bump g-i dependency to >= 1.3.9 (Martin Pitt)
+* Show proper exception when trying to allocate a disguised struct
+  (Martin Pitt) (:bzbug:`639972`)
+* Support marshalling GParamSpec signal arguments (Mark Nauwelaerts)
+  (:bzbug:`683099`)
+* Add test for a signal that returns a GParamSpec (Martin Pitt)
+  (:bzbug:`683265`)
+* [API add] Add Signal class for adding and connecting custom signals.
+  (Simon Feltman) (:bzbug:`434924`)
+* Fix pygtkcompat's Gtk.TreeView.insert_column_with_attributes()
+  (Martin Pitt)
+* Add override for Gtk.TreeView.insert_column_with_attributes()
+  (Marta Maria Casetti) (:bzbug:`679415`)
+* .gitignore: Add missing built files (Martin Pitt)
+* Ship tests/gi in tarball (Martin Pitt)
+* Split test_overrides.py (Martin Pitt) (:bzbug:`683188`)
+* _pygi_argument_to_object(): Clean up array unmarshalling (Martin Pitt)
+* Fix memory leak in _pygi_argument_to_object() (Alban Browaeys)
+  (:bzbug:`682979`)
+* Fix setting pointer fields/arguments to NULL using None.
+  (Simon Feltman) (:bzbug:`683150`)
+* Fix for python 2.6, officially drop support for < 2.6 (Martin Pitt)
+  (:bzbug:`682422`)
+* Allow overrides in other directories than gi itself
+  (Thibault Saunier) (:bzbug:`680913`)
+* Clean up sys.path handling in tests (Simon Feltman) (:bzbug:`680913`)
+* Fix dynamic creation of enum and flag gi types for Python 3.3
+  (Simon Feltman) (:bzbug:`682323`)
+* [API add] Override g_menu_item_set_attribute (Paolo Borelli) (:bzbug:`682436`)
+
+3.3.90 - 2012-08-20
+-------------------
+
+* Implement marshalling for GParamSpec (Mathieu Duponchelle) (:bzbug:`681565`)
+* Fix erronous import statements for Python 3.3 (Simon Feltman)
+  (:bzbug:`682051`)
+* Do not fail tests if pyflakes or pep8 are not installed (Martin Pitt)
+* Fix PEP-8 whitespace checking and issues in the code (Martin Pitt)
+* Fix unmarshalling of gssize (David Malcolm) (:bzbug:`680693`)
+* Fix various endianess errors (David Malcolm) (:bzbug:`680692`)
+* Gtk overrides: Add TreeModelSort.__init__(self, model)
+  (Simon Feltman) (:bzbug:`681477`)
+* Convert Gtk.CellRendererState in the pygi-convert script
+  (Manuel Quiñones) (:bzbug:`681596`)
+
+3.3.5 - 2012-08-06
+------------------
+
+* Fix list marshalling on big-endian machines (Martin Pitt)
+  (:bzbug:`680693`)
+* Beautify class/interface type mismatch error messages (Martin Pitt)
+* Skip instead of fail tests which need Pango, Atk, Gdk, Gtk (Martin Pitt)
+* [API add] pygtkcompat: Add more pixbuf creation functions (Simon Feltman)
+  (:bzbug:`680814`)
+* Fix error messages on interface/class type mismatches (Martin Pitt)
+* Fix crash when returning (False, None) from Gtk.TreeModel.do_get_iter() (Simon Feltman)
+  (:bzbug:`680812`)
+* Add test case for Gtk.TextIter.forward_search() (Martin Pitt)
+  (:bzbug:`679415`)
+* pygi-info.c: Robustify pointer arithmetic (Martin Pitt)
+* Add set_attributes() override to Gtk.TreeViewColumn (Manuel Quiñones)
+* Gtk overrides: Add TreePath.__getitem__() (Simon Feltman)
+  (:bzbug:`680353`)
+* Fix property type mapping from int to TYPE_INT for python3. (Simon Feltman)
+  (:bzbug:`679939`)
+* Convert Gtk.DestDefaults constants in pygi-convert.sh script (Manuel Quiñones)
+  (:bzbug:`680259`)
+* Convert all Gdk.WindowState constants in pygi-convert.sh (Manuel Quiñones)
+  (:bzbug:`680257`)
+* [API add] Add API for checking pygobject's version (Joe R. Nassimian)
+  (:bzbug:`680176`)
+* pygi-convert.sh: Add some missing Gdk.CursorTypes (Manuel Quiñones)
+  (:bzbug:`680050`)
+* pygi-convert.sh: convert rsvg.Handle(data=...) (Manuel Kaufmann)
+  (:bzbug:`680092`)
+
+3.3.4 - 2012-07-16
+------------------
+
+* pygi-convert.sh: Drop bogus filter_new() conversion (Martin Pitt)
+  (:bzbug:`679999`)
+* Fix help() for GI modules (Martin Pitt) (:bzbug:`679804`)
+* Skip gi.CallbackInfo objects from a module's dir() (Martin Pitt)
+  (:bzbug:`679804`)
+* Fix __path__ module attribute (Martin Pitt)
+* pygi-convert.sh: Fix some child â†’ getChild() false positives
+  (Joe R. Nassimian) (:bzbug:`680004`)
+* Fix array handling for interfaces, properties, and signals
+  (Mikkel Kamstrup Erlandsen) (:bzbug:`667244`)
+* Add conversion of the Gdk.PropMode constants to pygi-convert.sh
+  script (Manuel Quiñones) (:bzbug:`679775`)
+* Add the same rules for pack_start to convert pack_end (Manuel
+  Quiñones) (:bzbug:`679760`)
+* Add error-checking for the case where _arg_cache_new() fails
+  (Dave Malcolm) (:bzbug:`678914`)
+* Add conversion of the Gdk.NotifyType constants to pygi-convert.sh
+  script (Manuel Quiñones) (:bzbug:`679754`)
+* Fix PyObject_Repr and PyObject_Str reference leaks (Simon Feltman)
+  (:bzbug:`675857`)
+* [API add] Gtk overrides: Add TreePath.__len__() (Martin Pitt)
+  (:bzbug:`679199`)
+* GLib.Variant: Fix repr(), add proper str() (Martin Pitt) (:bzbug:`679336`)
+* m4/python.m4: Update Python version list (Martin Pitt)
+* Remove "label" property from Gtk.MenuItem if it is not set
+  (Micah Carrick) (:bzbug:`670575`)
+
+3.3.3.1 - 2012-06-25
+--------------------
+
+* Do not escape enum and flag names that are Python keywords (Martin Pitt)
+
+3.3.3 - 2012-06-25
+------------------
+
+* Remove obsolete release-tag make target (Martin Pitt)
+* Do not do any python calls when GObjects are destroyed after the
+  python interpreter has been finalized (Simon Schampijer) (:bzbug:`678046`)
+* Do not change constructor-only "type" Window property (Martin Pitt)
+  (:bzbug:`678510`)
+* Escape identifiers which are Python keywords (Martin Pitt) (:bzbug:`676746`)
+* Fix code for PEP-8 violations detected by the latest pep8 checker.
+  (Martin Pitt)
+* Fix crash in GLib.find_program_in_path() (Martin Pitt) (:bzbug:`678119`)
+* Revert "Do not bind gobject_get_data() and gobject_set_data()" (Martin Pitt)
+  (:bzbug:`641944`)
+* GVariant: Raise proper TypeError on invalid tuple input (David Keijser)
+  (:bzbug:`678317`)
+
+3.3.2 - 2012-06-05
+------------------
+
+* foreign: Register cairo.Path and cairo.FontOptions foreign structs
+  (Bastian Winkler) (:bzbug:`677388`)
+* Check types in GBoxed assignments (Marien Zwart) (:bzbug:`676603`)
+* [API add] Gtk overrides: Add TreeModelRow.get_previous()
+  (Bastian Winkler) (:bzbug:`677389`)
+* [API add] Add missing GObject.TYPE_VARIANT (Bastian Winkler) (:bzbug:`677387`)
+* Fix boxed type equality (Jasper St. Pierre) (:bzbug:`677249`)
+* Fix TestProperties.testBoxed test (Jose Rostagno) (:bzbug:`676644`)
+* Fix handling of by-reference structs as out parameters
+  (Carlos Garnacho) (:bzbug:`653151`)
+* tests: Add more vfunc checks for GIMarshallingTestsObject
+  (Martin Pitt)
+* Test caller-allocated GValue out parameter (Martin Pitt) (:bzbug:`653151`)
+* GObject.bind_property: Support transform functions (Bastian Winkler)
+  (:bzbug:`676169`)
+* Fix lookup of vfuncs in parent classes (Carlos Garnacho) (:bzbug:`672864`)
+* tests/test_properties.py: Fix whitespace (Martin Pitt)
+* gi: Support zero-terminated arrays with length arguments
+  (Jasper St. Pierre) (:bzbug:`677124`)
+* [API add] Add GObject.bind_property method (Simon Feltman) (:bzbug:`675582`)
+* pygtkcompat: Correctly set flags (Jose Rostagno) (:bzbug:`675911`)
+* Gtk overrides: Implement __delitem__ on TreeModel (Jose Rostagno)
+  (:bzbug:`675892`)
+* Gdk Color override should support red/green/blue_float properties
+  (Simon Feltman) (:bzbug:`675579`)
+* Support marshalling of GVariants for closures (Martin Pitt) (:bzbug:`656554`)
+* _pygi_argument_from_object(): Check for compatible data type
+  (Martin Pitt)
+* pygtkcompat: Fix color conversion (Martin Pitt)
+* test_gi: Check setting properties in constructor (Martin Pitt)
+* Support getting and setting GStrv properties (Martin Pitt)
+* Support defining GStrv properties from Python (Martin Pitt)
+* Add GObject.TYPE_STRV constant (Martin Pitt)
+* Unref GVariants when destroying the wrapper (Martin Pitt) (:bzbug:`675472`)
+* Fix TestArrayGVariant test cases (Martin Pitt)
+* pygtkcompat: Add gdk.pixbuf_get_formats compat code (Jose Rostagno)
+  (:bzbug:`675489`)
+* pygtkcompat: Add some more compat functions (Jose Rostagno) (:bzbug:`675489`)
+* Fix tests for Python 3 (Martin Pitt)
+* Fix building with --disable-cairo (Martin Pitt)
+* tests: Fix deprecated assertions (Martin Pitt)
+* Run tests with ``MALLOC_PERTURB_`` (Martin Pitt)
+
+3.2.2 - 2012-05-14
+------------------
+
+* pygtkcompat: Correctly set flags (Jose Rostagno) (:bzbug:`675911`)
+* Gtk overrides: Implement __delitem__ on TreeModel (Jose Rostagno)
+  (:bzbug:`675892`)
+
+3.2.1 - 2012-05-10
+------------------
+
+* Reindent files in tests to use 4-space indentation (Sebastian Pölsterl
+* Add missing override for TreeModel.iter_previous() (Martin Pitt)
+* GSettings: allow extra keyword arguments (Giovanni Campagna)
+* pygtkcompat: Correct Userlist module use (Jose Rostagno)
+* test_gdbus: Call GetConnectionUnixProcessID() with correct signature (Martin Pitt)
+* GTK overrides: Add missing keyword arguments (Martin Pitt)
+* pygi-convert.py: Drop obsolete drag method conversions (Martin Pitt)
+* Fix len_arg_index for array arguments (Bastian Winkler)
+* Add missing GObject.TYPE_GTYPE (Martin Pitt)
+* Fix "distcheck" and tests with out-of-tree builds (Martin Pitt)
+* Add GtkComboBoxEntry compatibility (Paolo Borelli)
+
+3.3.1 - 2012-04-30
+------------------
+
+* GSettings: allow extra keyword arguments (Giovanni Campagna)
+  (:bzbug:`675105`)
+* pygtkcompat: Correct Userlist module use (Jose Rostagno) (:bzbug:`675084`)
+* Add release-news make rule (Martin Pitt)
+* Add "make check.nemiver" target (Martin Pitt)
+* Test flags and enums in GHash values (Martin Pitt) (:bzbug:`637466`)
+* tests: Activate test_hash_in and apply workaround (Martin Pitt)
+  (:bzbug:`666636`)
+* Add special case for Gdk.Atom array entries from Python (Martin Pitt)
+  (:bzbug:`661709`)
+* test_gdbus: Call GetConnectionUnixProcessID() with correct signature
+  (Martin Pitt) (:bzbug:`667954`)
+* Add test case for Gtk.ListStore custom sort (Martin Pitt) (:bzbug:`674475`)
+* GTK overrides: Add missing keyword arguments (Martin Pitt) (:bzbug:`660018`)
+* [API change] Add missing override for TreeModel.iter_previous()
+  (Martin Pitt) (:bzbug:`660018`)
+* pygi-convert.py: Drop obsolete drag method conversions (Martin Pitt)
+  (:bzbug:`652860`)
+* tests: Replace deprecated assertEquals() with assertEqual()
+  (Martin Pitt)
+* Plug tiny leak in constant_info_get_value (Paolo Borelli) (:bzbug:`642754`)
+* Fix len_arg_index for array arguments (Bastian Winkler) (:bzbug:`674271`)
+* Support defining GType properties from Python (Martin Pitt) (:bzbug:`674351`)
+* Handle GType properties correctly (Bastian Winkler) (:bzbug:`674351`)
+* Add missing GObject.TYPE_GTYPE (Martin Pitt)
+* Fix test_mainloop.py for Python 3 (Martin Pitt)
+* Make callback exception propagation test stricter (Martin Pitt)
+  (:bzbug:`616279`)
+* [API add] Add context management to freeze_notify() and
+  handler_block(). (Simon Feltman) (:bzbug:`672324`)
+* Add support for GFlags properties (Martin Pitt) (:bzbug:`620943`)
+* [API add] Wrap GLib.Source.is_destroyed() method (Martin Pitt)
+  (:bzbug:`524719`)
+* Fix error message when trying to override a non-GI class
+  (Martin Pitt) (:bzbug:`646667`)
+* Fix segfault when accessing __grefcount__ before creating the GObject
+  (Steve Frécinaux) (:bzbug:`640434`)
+* [API change] Do not bind gobject_get_data() and gobject_set_data()
+  (Steve Frécinaux) (:bzbug:`641944`)
+* Add test case for multiple GLib.MainLoop instances (Martin Pitt)
+  (:bzbug:`663068`)
+* Add a ccallback type which is used to invoke callbacks passed to a
+  vfunc (John (J5) Palmieri) (:bzbug:`644926`)
+* Regression test: marshalling GValues in GHashTable (Alberto Mardegan)
+  (:bzbug:`668903`)
+* Update .gitignore (Martin Pitt)
+* Fix "distcheck" and tests with out-of-tree builds (Martin Pitt)
+* Add a pep8 check to the makefile (Johan Dahlin) (:bzbug:`672627`)
+* PEP8 whitespace fixes (Johan Dahlin) (:bzbug:`672627`)
+* PEP8: Remove trailing ; (Johan Dahlin) (:bzbug:`672627`)
+* tests: Replace deprecated Python API (Martin Pitt)
+* Fail tests if they use or encounter deprecations (Martin Pitt)
+* Do not run tests in two phases any more (Martin Pitt)
+* test_overrides: Find local gsettings schema with current glib
+  (Martin Pitt)
+* Add GtkComboBoxEntry compatibility (Paolo Borelli) (:bzbug:`672589`)
+* Correct review comments from Martin (Johan Dahlin) (:bzbug:`672578`)
+* Correct pyflakes warnings/errors (Johan Dahlin) (:bzbug:`672578`)
+* Make tests fail on CRITICAL logs, too, and apply to all tests
+  (Martin Pitt)
+* Support marshalling GI_TYPE_TAG_INTERFACE (Alberto Mardegan)
+  (:bzbug:`668903`)
+* Fix warnings on None values in added tree/list store rows
+  (Martin Pitt) (:bzbug:`672463`)
+* pygtkcompat test: Properly clean up PixbufLoader (Martin Pitt)
+
+3.2.0 - 2012-03-26
+------------------
+
+* No changes since 3.1.93 except version number.
+
+3.1.93 - 2012-03-22
+-------------------
+
+* Fix warnings on None values in added tree/list store rows.
+  (:bzbug:`672463`, Martin Pitt)
+* Support marshalling GI_TYPE_TAG_INTERFACE (:bzbug:`668903`, Alberto Mardegan)
+* test_overrides: Find local gsettings schema with current glib
+  (Martin Pitt)
+* pygtkcompat test: Properly clean up PixbufLoader (Martin Pitt)
+
+3.1.92 - 2012-03-19
+-------------------
+
+* Correct Gtk.TreePath.__iter__ to work with Python 3 (Johan Dahlin)
+* Fix test_everything.TestSignals.test_object_param_signal test case
+  (Martin Pitt)
+* Add a PyGTK compatibility layer (Johan Dahlin)
+* pygtkcompat: Remove first argument for get_origin() (Johan Dahlin)
+* Fix pygtkcompat.py to work with Python 3 (Martin Pitt)
+* GtkViewport: Add a default values for the adjustment constructor
+  parameters (Johan Dahlin)
+* GtkIconSet: Add a default value for the pixbuf constructor parameter
+  (Johan Dahlin)
+* PangoLayout: Add a default value for set_markup() (Johan Dahlin)
+* Gtk[HV]Scrollbar: Add a default value for the adjustment constructor
+  parameter (Johan Dahlin)
+* GtkToolButton: Add a default value for the stock_id constructor
+  parameter (Johan Dahlin)
+* GtkIconView: Add a default value for the model constructor parameter
+  (Johan Dahlin)
+* Add a default value for column in Gtk.TreeView.get_cell_area()
+  (Johan Dahlin)
+* Atomic inserts in Gtk.{List,Tree}Store overrides (Martin Pitt)
+* Fix Gtk.Button constructor to accept use_stock parameter
+  (Martin Pitt)
+* Correct bad rebase, remove duplicate Window (Johan Dahlin)
+* Add bw-compatible arguments to Gtk.Adjustment (Johan Dahlin)
+* GtkTreePath: make it iterable (Johan Dahlin)
+* Add a default argument to TreeModelFilter.set_visible_func()
+  (Johan Dahlin)
+* Add a default argument to Gtk.TreeView.set_cursor (Johan Dahlin)
+* Add a default argument to Pango.Context.get_metrics() (Johan Dahlin)
+* Fix double-freeing GValues in arrays (Martin Pitt)
+* Renamed "property" class to "Property" (Simon Feltman)
+* Fix Python to C marshalling of GValue arrays (Martin Pitt)
+* Correct the Gtk.Window hierarchy (Johan Dahlin)
+* Renamed getter/setter instance attributes to fget/fset respectively.
+  (Simon Feltman)
+* Add Gtk.Arrow/Gtk.Window constructor override (Johan Dahlin)
+* Fix marshalling to/from Python to work on big endian machines.
+  (Michel Dänzer)
+* Use gi_cclosure_marshal_generic instead of duplicating it.
+  (Michel Dänzer)
+* Override Gtk.TreeView.get_visible_range to fix return (René Stadler)
+* Plug memory leak in _is_union_member (Paolo Borelli)
+* tests: Split TestInterfaces into separate tests (Sebastian Pölsterl)
+* README: Update current maintainers (Martin Pitt)
+
+3.1.1 - 2012-02-20
+------------------
+
+* Don't use C99 style (Sebastian Pölsterl)
+* Add test for GPtrArray with transfer full (Martin Pitt)
+* Drop obsolete g_thread_init() (Martin Pitt)
+* Fix deprecated g_source_get_current_time() (Martin Pitt)
+* Fix deprecated g_value_[gs]et_char() (Martin Pitt)
+* Make pygiconvert.sh correctly convert gtk.gdk.x11_* (Simon Schampijer)
+* Raise required glib version to 2.31 because of g_value_(get|set)_schar (Sebastian Pölsterl)
+* Fix cset_first typo (Dieter Verfaillie)
+* pygi-convert: Handle Clutter and Cogl (Bastian Winkler)
+* Provide access to gpointer struct values (Cédric Krier)
+* Add some GType tests (Paolo Borelli)
+* Split GStrv and array variant tests in their own classes (Paolo Borelli)
+* Add unit test for builder's connect_after (Paolo Borelli)
+* fix GtkBuilder signal connection 'after' logic (Ryan Lortie)
+* test(1) uses '=' to test if strings are identical (Patrick Welche)
+* pygspawn: improve error checking (Ryan Lortie)
+
+3.0.4 - 2012-02-09
+------------------
+
+* Revert "Convert all strings to utf-8 encoding when retrieving from TreeModel" (Martin Pitt)
+* Fixed bug where GObject.property did not respect minimum and maximum values (Sebastian Pölsterl)
+* Remove mention of removed option --enable-docs (Tomeu Vizoso)
+
+3.1.0 - 2012-02-06
+------------------
+
+* Updated DOAP file to only include people currently actively working on the project (Sebastian Pölsterl)
+* Revert "Convert all strings to utf-8 encoding when retrieving from TreeModel" (Sebastian Pölsterl)
+* tests: Fixed issues with python3 (Sebastian Pölsterl)
+* Properly distinguish between different integer types for properties (Sebastian Pölsterl)
+* Distinguish between GArray and GPtrArray when cleaning up (Sebastian Pölsterl)
+* Add null_gerror_callback unit test (Paolo Borelli)
+* pyglib_error_check: Re-add missing NULL check (Martin Pitt)
+* Add tests/runtests-windows.py to source tarball (Michael Culbertson)
+* Don't issue a depreciation warning for GtkDialog's NO_SEPARATOR flag, even when unused (Sebastian Pölsterl)
+* Fix bool() operations on GLib.Variant objects (Nirbheek Chauhan)
+* Fix hash() and __eq__() for GLib.Variant objects (Nirbheek Chauhan)
+* Fix method names of callback tests (Martin Pitt)
+* Cairo: add missing braces around array-of-struct definition (Will Thompson)
+* g_instance_init: cast to PyGObject * as needed (Will Thompson)
+* Fix a few set-but-not-used warnings. (Will Thompson)
+* pygmainloop: allow for extra arguments in 'quit' method (Stefano Facchini)
+* Fix bytearray test compatibility with python3 (Alexandre Rostovtsev)
+* Respect transfer-type when demarshalling GErrors (Alberto Mardegan)
+* Support GHashTable and GError as callback/closure arguments (Alberto Mardegan)
+* Don't leak when marshalling GErrors to C (Will Thompson)
+* Support functions which return GError (Will Thompson)
+* Fix indentation of _pygi_argument_to_object() (Alberto Mardegan)
+* Avoid C99 syntax. (Paolo Borelli)
+* Connect to first action of a radio group. (Paolo Borelli)
+* Use g_slist_free_full in pygi-closure. (Paolo Borelli)
+* Avoid O(n^2) behavior when marshalling lists (Paolo Borelli)
+* Handle NULL as a valid case of a char** array (Paolo Borelli)
+* Branching, bump version to 3.1.0 (Tomeu Vizoso)
+* Add notes about branching to HACKING (Tomeu Vizoso)
+* Fixed bug where GObject.property did not respect minimum and maximum values (Sebastian Pölsterl)
+* Remove mention of removed option --enable-docs (Tomeu Vizoso)
+* Fix sebp's name in NEWS (Tomeu Vizoso)
+
+3.0.3 - 2011-12-12
+------------------
+
+* Convert all modifier constants to Gdk.ModifierType (Manuel Quiñones)
+* Convert all strings to utf-8 encoding when retrieving from TreeModel (Sebastian Pölsterl)
+* add test for bytearray variants (John (J5) Palmieri)
+* handle NULL arrays correctly for each array type (John (J5) Palmieri)
+* Revert "Revert "Fix array termination and size calculation"" (John (J5) Palmieri)
+* pygmainloop: avoid lockups if multiple glib.MainLoop exist (Owen W. Taylor)
+* Properly chain up to the class that implements a given vfunc. (Tomeu Vizoso)
+* Revert "Fix array termination and size calculation" (Tomeu Vizoso)
+* Fix array termination and size calculation (Holger Berndt)
+* pygi-convert: fix for Pango.Alignment (Daniel Drake)
+* pygi-convert: fix for Gtk.Orientation (Daniel Drake)
+* Add tests for calling closures (Martin Pitt)
+* fix marshaling of arrays of GVariants (Mikkel Kamstrup Erlandsen)
+
+3.0.2 - 2011-10-21
+------------------
+
+* Add tests for boxed properties. (Ignacio Casal Quinteiro)
+* Allow GBoxed types as property (Timo Vanwynsberghe)
+* when converting an object with transfer none, make sure the wrapper owns a ref (John (J5) Palmieri)
+* unit test for checking ref count of object param in signals (John (J5) Palmieri)
+* Gdk overrides: Unbreak for Gdk-2.0 (Martin Pitt)
+* Do union member checks for unions that are parameters (John (J5) Palmieri)
+
+3.0.1 - 2011-09-30
+------------------
+
+* when checking instances union members are same type as parent
+* add a floating flag to pygobjects
+* Revert "Fix refcount bug by not creating python wrapper during gobject init stage"
+
+3.0.0 - 2011-09-19
+------------------
+
+* up version required of gobject-introspection to 1.29.0 (John (J5) Palmieri)
+* fix most warnings (John (J5) Palmieri)
+
+2.90.4 - 2011-09-15 (3.0 pre-release)
+-------------------------------------
+
+* do not pass in len(str) to the length argument of gtk_test_buffer_insert* apis (John (J5) Palmieri)
+* Switch tarball compression format to tar.xz only. (Dieter Verfaillie)
+* Remove pygtk_version attribute from internal gi._gobject module. (Dieter Verfaillie)
+* remove overridesdir from the .pc file and add it to the gi module (John (J5) Palmieri)
+* fix tests to correctly construct a dummy Gtk.TargetEntry (John (J5) Palmieri)
+* we now assume that C arrays of structs are flat so memcpy them when marshalling (John (J5) Palmieri)
+* only update the arg counts once if child arg comes before parent arg (John (J5) Palmieri)
+* Fix refcount bug by not creating python wrapper during gobject init stage (John (J5) Palmieri)
+* don't destroy just created wrapper when object is created via g_object_new (John (J5) Palmieri)
+* Remove deprecated API from pygobject.h (Steve Frécinaux)
+* Convert gtk.TRUE/FALSE to Python True/False. (Marcin Owsiany)
+* Drop legacy __gobject_init__ method of GObject.Object. (Steve Frécinaux)
+* AM_CHECK_PYTHON_LIBS does not work for lib64 (Dieter Verfaillie)
+* Remove common_ldflags from Makefile.am as it is no longer used. (Dieter Verfaillie)
+* check if object is actually a PyGFlag before trying to access g_type (John (J5) Palmieri)
+* fix regression - add instance type checks since Py3 no longer does this for us (John (J5) Palmieri)
+* refactor in/out marshalling to be to_py/from_py (John (J5) Palmieri)
+* Examples: fix cairo-demo.py imports (Dieter Verfaillie)
+* Fix paths and add missing overridesdir variable used in uninstalled pkgconfig file (Dieter Verfaillie)
+* Remove no longer used variables from pkgconfig files (Dieter Verfaillie)
+* docs/Makefile.am and m4/python.m4: Python3 portability fixes (Dieter Verfaillie)
+* Refactor and clean Makefile.am files (Dieter Verfaillie)
+* Remove all PLATFORM_VERSION = 2.0 traces (Dieter Verfaillie)
+* Remove gi/tests/ directory as all the tests now live in tests/ (Dieter Verfaillie)
+* autogen.sh: Use autoreconf instead of a custom script and honor ACLOCAL_FLAGS (Dieter Verfaillie)
+* use improved python.m4 macros to search for Python headers and libs (Dieter Verfaillie)
+* Make maintiner mode enabled by default (Javier Jardón)
+* Disable documentation for now since they are completely wrong for GI. (Dieter Verfaillie)
+* Fix documentation installation directory (Dieter Verfaillie)
+* Remove distutils based build system. (Dieter Verfaillie)
+* [gtk-demo] Fix syntax highlighter encoding issue (Dieter Verfaillie)
+* overrides: add constants for atoms (Ignacio Casal Quinteiro)
+* Drop pygobject_construct() from public API. (Steve Frécinaux)
+
+2.90.3 - 2011-08-31(3.0 pre-release)
+------------------------------------
+
+* support skip annotation for return values (John (J5) Palmieri)
+* Test GPtrArray regression (Xavier Claessens)
+* Drop support for old constructor style. (Steve Frécinaux)
+* Drop support for sink functions. (Steve Frécinaux)
+* Reinstate copying of in-line structs in arrays (Mike Gorse)
+* fix inline struct array handling (John (J5) Palmieri)
+* fix on demos (Dieter Verfaillie)
+* Added support for __setitem__ to TreeModel and support for slices to TreeModelRow (Sebastian Pölsterl)
+* Convert ACCEL_* constants into Gtk.AccelFlags. (Olav Vitters)
+* Convert TREE_VIEW_DROP_* constants into Gtk.TreeViewDropPosition (Olav Vitters)
+
+2.90.2 - 2011-08-18 (3.0 pre-release)
+-------------------------------------
+
+* remove tests that were removed from gi (John (J5) Palmieri)
+* don't calculate item_size using is_pointer (John (J5) Palmieri)
+* Updated signal example to use GObject introspection (Timo Vanwynsberghe)
+* Updated properties example to use GObject introspection (Timo Vanwynsberghe)
+* Add override for GLib.Variant.split_signature() (Martin Pitt)
+* [pygi-convert.sh] Handle the import of pygtk and require Gtk 3.0 (Timo Vanwynsberghe)
+* Install pygobject.h again. (Ignacio Casal Quinteiro)
+* update the doap file (John (J5) Palmieri)
+
+2.90.1 - 2011-08-14 (3.0 pre-release)
+-------------------------------------
+
+* pass exta keywords to the Box constructor (John (J5) Palmieri)
+* add (Tree|List)Store set method override (John (J5) Palmieri)
+* add test for object arrays (John (J5) Palmieri)
+* only support C pointer arrays for structs and objects (John (J5) Palmieri)
+* revert Gtk.Window override because it causes issues with subclasses (John (J5) Palmieri)
+* take GIL in _pygi_invoke_closure_free (bug :bzbug:`647016`) (Jonathan Matthew)
+* Add a default parameter to GtkTreeModel.filter_new (Johan Dahlin)
+* Add vbox/action_area properties (Johan Dahlin)
+* Add a couple of constructors (Johan Dahlin)
+* Do not always pass in user_data to callbacks. (Johan Dahlin)
+* Add a default detail value for Widget.render_icon (Johan Dahlin)
+* Add an override for Gdk.color_parse() (Johan Dahlin)
+* Support function calling with keyword arguments in invoke. (Laszlo Pandy)
+* remove references to deprecated GI_INFO_TYPE_ERROR_DOMAIN (John (J5) Palmieri)
+* Fix gobject vs. gi.repository warning (Martin Pitt)
+* make GObject and GLib able to take overrides (John (J5) Palmieri)
+* avoid dependency issue by importing the internal gobject (John (J5) Palmieri)
+* fix tests to use the new GLib module (John (J5) Palmieri)
+* add DynamicGLibModule which works like DynamicGObjectModule (John (J5) Palmieri)
+* refactor, add objects and types to the correct internal module (John (J5) Palmieri)
+* rename the pyglib shared library so we don't load the old one (John (J5) Palmieri)
+* refactor tests to only use PyGObject 3 syntax (John (J5) Palmieri)
+* refactor the internal _glib module to import correct modules (John (J5) Palmieri)
+* refactor to use the new internal _glib and _gobject modules (John (J5) Palmieri)
+* refactor gi module to import and use internal _gobject module (John (J5) Palmieri)
+* move the static bits internal to gi and refactor build files (John (J5) Palmieri)
+* remove pygtk.py (John (J5) Palmieri)
+* introspection is no longer optional (John (J5) Palmieri)
+* up platform version to 3.0 (John (J5) Palmieri)
+* [gi] Handle GVariants from callback return values (Martin Pitt)
+* Handle GVariants for callback arguments (Martin Pitt)
+* [gi] Fix crash: check return value of _invoke_state_init_from_callable_cache() before continuing. (Laszlo Pandy)
+* [gi] Pass gtype as first parameter to vfuncs (instead of using kwargs). (Laszlo Pandy)
+* remove codegen (John (J5) Palmieri)
+* remove some left over ifdefs to complete merge of the invoke-rewrite branch (John (J5) Palmieri)
+* rename pygi-invoke-ng to pygi-invoke (John (J5) Palmieri)
+* make invoke-ng the only invoker (John (J5) Palmieri)
+* Merge branch 'master' into invoke-rewrite (John (J5) Palmieri)
+* Merge branch 'master' into invoke-rewrite (John (J5) Palmieri)
+* split the marshalling routines into two source files (John (J5) Palmieri)
+* Ship tests/te_ST@nouppera in release tarballs for tests to succeed (Martin Pitt)
+* [invoke] break out caller_allocates allocating into its own function (John (J5) Palmieri)
+* [invoke] missed a bit when removing constructor_class usage (John (J5) Palmieri)
+* [invoke] don't hold on to the constructor class, just add a TODO (John (J5) Palmieri)
+* [gi] Port test_properties from static gio to GI Gio (Martin Pitt)
+* [python3] Fix maketrans import (Martin Pitt)
+* [caching] remove all inline compiler flags (John (J5) Palmieri)
+* [caching] refactor function names to be less confusing (John (J5) Palmieri)
+* [overrides] deprecate the use of type keyword MessageDialog constructor (John (J5) Palmieri)
+* gdbus tests: Fix hang if test case fails (Martin Pitt)
+* use an enum instead of booleans to denote function type (John (J5) Palmieri)
+* rename aux arguments to child arguments to make their purpose clearer (John (J5) Palmieri)
+* Fixed the cairo example (Timo Vanwynsberghe)
+* Add override binding for Gtk.ListStore.prepend(). (Adam Dingle)
+* Fix crash in Gtk.TextIter overrides (Martin Pitt)
+* use gssize instead of int for arg indexes (John (J5) Palmieri)
+* [cache] remove refrence to default value as it is not implemented yet (John (J5) Palmieri)
+* Handle arguments that are flags correctly (Sebastian Pölsterl)
+* correctly initialize the _gi_cairo_functions array to be zero filled (John (J5) Palmieri)
+* correctly initialize the _gi_cairo_functions array to be zero filled (John (J5) Palmieri)
+* pass in the address of the gerror, not the gerror itself (John (J5) Palmieri)
+* [gi] handle marshalling gerrors arguments for signals (John (J5) Palmieri)
+* [gi-invoke-ng] fix NULL check to check before we access the cache struct (John (J5) Palmieri)
+* [gi-tests] add test for PyGObject->PyObject TreeModel storage (John (J5) Palmieri)
+* [gtk-overrides] special case TreeModel columns of PYGOBJECT types (John (J5) Palmieri)
+* [gi-invoke-ng] copy structs when transfer is full for array (John (J5) Palmieri)
+* [gtk-override] print warning if user imports Gtk 2.0 (John (J5) Palmieri)
+* [gtk-overrides] allow the message_type keyword to be used for MessageDialogs (John (J5) Palmieri)
+* Add support for enums in gobject.property (Johan Dahlin)
+* Add support for enums in gobject.property (Johan Dahlin)
+* [gi-invoke-ng] use g_slice for allocating GValues that are caller allocated (John (J5) Palmieri)
+* [gi-invoke-ng] Convert Overflow errors to ValueErrors when marshalling integers (John (J5) Palmieri)
+* [gi-invoke-ng] only cache caller allocates for interfaces as some API are broken (John (J5) Palmieri)
+* [gi-invoke-ng] handle in pointer array marshalling (John (J5) Palmieri)
+* Adding GPtrArray tests (Alex Eftimie)
+* [gi-invoke-ng] fix array element offset calculations (John (J5) Palmieri)
+* [gi] don't clean up arguments that weren't yet processed during in arg failure (John (J5) Palmieri)
+* [gi-overrides] use new instead of init when constructing a GLib.VariantBuilder (John (J5) Palmieri)
+* [gi-invoke-ng] actual code to import overrides (John (J5) Palmieri)
+* [gi-invoke-ng] import pytypes so we get overrides (John (J5) Palmieri)
+* [gi-invoke-ng] handle gvariants now that they are not foreign (John (J5) Palmieri)
+* [gi-invoke-ng] do not try to clean up NULL arguments (John (J5) Palmieri)
+* Merge branch 'master' into invoke-rewrite (John (J5) Palmieri)
+* Merge branch 'master' into invoke-rewrite (John (J5) Palmieri)
+* closure: avoid double free crash (Ignacio Casal Quinteiro)
+* Added __eq__ method for Gdk.Color and Gdk.RGBA (Jason Siefken)
+* closure: Check the out arg is not null. Fixes bug :bzbug:`651812` (Ignacio Casal Quinteiro)
+* Use constants instead of literals (Tomeu Vizoso)
+* GVariant has now a GType, take that into account (Tomeu Vizoso)
+* GVariantType is a boxed struct (Tomeu Vizoso)
+* Use _gi.Struct to wrap fundamentals (Tomeu Vizoso)
+* Merge gi/HACKING into /HACKING (Tomeu Vizoso)
+* Fix GC-related crash during PyGObject deallocation (Daniel Drake)
+* [gi-invoke-ng] enable invoke-ng by default (John (J5) Palmieri)
+* [gi-invoke-ng] add code to clean up when input values fail to marshal (John (J5) Palmieri)
+* [gi-invoke-ng] add hash cleanup routines (John (J5) Palmieri)
+* [gi-invoke-ng] handle arrays with transfers of GI_TRANSFER_CONTAINER (John (J5) Palmieri)
+* [gi-invoke-ng] add list cleanup routines (John (J5) Palmieri)
+* indentation fix (John (J5) Palmieri)
+* [gi-invoke-ng] add out array cleanup (John (J5) Palmieri)
+* [gi-invoke-ng] do not allocate null terminator for garray (John (J5) Palmieri)
+* [gi-invoke-ng] add array cleanup for in arrays (John (J5) Palmieri)
+* [gi-invoke-ng] remove remaining bits of the invoke stage state machine (John (J5) Palmieri)
+* [gi-invoke-ng] revamp cleanup framework to be orthogonal to cache setup (John (J5) Palmieri)
+* [gi-invoke-ng] stub out a cleaner way of cleaning up after ourselves (John (J5) Palmieri)
+* Doc Extractor: Correct the logic of the --no-since option. (José Alburquerque)
+* Doc Extractor: Add a --no-since option. (José Alburquerque)
+* [gi-invoke-ng] tweek cleanup routines (John (J5) Palmieri)
+* Fix symbol names to be locale independent (Martin Pitt)
+* [gi] pygi-convert.sh: Convert gtk.gdk.CROSSHAIR (Martin Pitt)
+* [gi-invoke-ng] handle filename cleanup with the utf8 cleanup function (John (J5) Palmieri)
+* [gi-invoke-ng] handle caller allocates cleanup (John (J5) Palmieri)
+* [gi-invoke-ng] refactor the cleanup code and add utf8 cleanup as initial test (John (J5) Palmieri)
+* use PyCapsule when importing pycairo/require pycairo 1.10.0 for python3 builds (John (J5) Palmieri)
+* [python3] fix build. PYcairo_IMPORT doesn't exists anymore (Ignacio Casal Quinteiro)
+* Updated DOAP file (Sebastian Pölsterl)
+* [gi] Don't create variant twice (Sebastian Pölsterl)
+* pygi-convert.sh: Make sure the uppercase GObject module is imported instead of the lowercase (Sebastian Pölsterl)
+* [gi] Removed hack to avoid using GLib.Variant.new_variant. (Sebastian Pölsterl)
+* [gi] Added additional test case for GVariant handling (Sebastian Pölsterl)
+* [gi] Added support for GVariant arguments (Sebastian Pölsterl)
+* fix static ABI for setting string gvalues from python objects (John (J5) Palmieri)
+* dsextras.py: ensure eol characters are preserved when writing template files (so \n does not become \r\n) (Dieter Verfaillie)
+* dsextras.py: remove \r as wel as \n character (Dieter Verfaillie)
+* use PyCapsule when importing pycairo/require pycairo 1.10.0 for python3 builds (John (J5) Palmieri)
+* [python3] fix build. PYcairo_IMPORT doesn't exists anymore (Ignacio Casal Quinteiro)
+* Updated DOAP file (Sebastian Pölsterl)
+* [gi] Don't create variant twice (Sebastian Pölsterl)
+* pygi-convert.sh: Make sure the uppercase GObject module is imported instead of the lowercase (Sebastian Pölsterl)
+* [gi] Removed hack to avoid using GLib.Variant.new_variant. (Sebastian Pölsterl)
+* [gi] Added additional test case for GVariant handling (Sebastian Pölsterl)
+* [gi-invoke-ng] fix prototype (John (J5) Palmieri)
+* [gi-invoke-ng] create new framework for cleaning up args (John (J5) Palmieri)
+* [gi] Added support for GVariant arguments (Sebastian Pölsterl)
+* [gi-invoke-ng] fix marshal header that is no longer part of pygi-arguments.h (John (J5) Palmieri)
+* [gi-invoke-ng] code style space fixes (John (J5) Palmieri)
+* [gi-invoke-ng] don't decref value taken from a dict as it is borrowed (John (J5) Palmieri)
+* [gi-invoke-ng] return None when appropriate so we don't crash (John (J5) Palmieri)
+* [gi-invoke-ng] fix aux value caching (John (J5) Palmieri)
+* [gi-invoke-ng] backport handling flags with no gtype (John (J5) Palmieri)
+* [gi-invoke-ng] backport raw gvalue handling (John (J5) Palmieri)
+* [gi-invoke-ng] marshal instances seperately since they differ slightly from other args (John (J5) Palmieri)
+* [gi-invoke-ng] refactor FunctionCache to be more generic CallableCache (John (J5) Palmieri)
+* [gi-invoke-rewrite] backport glib error handling (John (J5) Palmieri)
+* [gi-invoke-ng] backport closure passing from invoke (John (J5) Palmieri)
+* [gi-invoke-ng] handle vfuncs and fix cosntrutors (John (J5) Palmieri)
+* [gi-invoke-ng] handle foreign types correctly (John (J5) Palmieri)
+* [gi] remove the class parameter from the argument list of constructors (John (J5) Palmieri)
+* fix static ABI for setting string gvalues from python objects (John (J5) Palmieri)
+* dsextras.py: ensure eol characters are preserved when writing template files (so \n does not become \r\n) (Dieter Verfaillie)
+* dsextras.py: remove \r as wel as \n character (Dieter Verfaillie)
+* [gi] make new invoke-ng codepath compile correctly (John (J5) Palmieri)
+* [gi] conditionalize invoke code paths (John (J5) Palmieri)
+* [gi] revert back to the type.py from master (John (J5) Palmieri)
+* [gi] revert pygi-argument.h and move the invoke-ng code to pygi-marshal.h (John (J5) Palmieri)
+* Merge branch 'master' into invoke-rewrite (John (J5) Palmieri)
+* [gi] foreign types now take interface infos instead of type infos (John (J5) Palmieri)
+* Fix GSchema tests for separate build tree (Martin Pitt)
+* [gi] start of merge from master (John (J5) Palmieri)
+* [gi] marshal raw closures (John (J5) Palmieri)
+* pygi-convert.sh add GObject.xxx and webkit (John Stowers)
+* pygi-convert.sh remove gobject tests, GObject works now (John Stowers)
+* [gi-demos] add pickers demo (John (J5) Palmieri)
+* [gi-demos] add menu demo (John (J5) Palmieri)
+* [gi-overrides] fix exception block so it works in Python 2.5 (John (J5) Palmieri)
+* Revert "Deduce PYTHON_LIBS in addition to PYTHON_INCLUDES" (Martin Pitt)
+* setup.py: fix user_access_control option (Dieter Verfaillie)
+* [gi] Respect the MessageType for Gtk.MessageDialog (Martin Pitt)
+* [gi] Do not require signature for D-BUS methods without arguments (Martin Pitt)
+* [gi-overrides] TreeViewColumn.set_cell_data_func func_data can be None (John Stowers)
+* [gi-demos] dont try and run demos that represent directories (John Stowers)
+* [gi-demos] some python 3 compat fixes (John (J5) Palmieri)
+* [gi-demos] add liststore demo (John (J5) Palmieri)
+* [gi-demos] catch the correct error class (John (J5) Palmieri)
+* Do not leak python references when using the gobject.property() helper. (Steve Frécinaux)
+* handle uchar as bytes, not strings in python 3 (John (J5) Palmieri)
+* [gi-overrides] handle unichar gvalues when setting treemodels (John (J5) Palmieri)
+* [gi-overrides] special case python 2 keywords that crept in (John (J5) Palmieri)
+* check for the py3 _thread module in configure.ac if thread is not found (John (J5) Palmieri)
+* [gi-demos] add iconview demo (John (J5) Palmieri)
+* [gi] wrap the keyword argument in a dict so we don't break Python 2.5 (John (J5) Palmieri)
+* [gi-demos] add the combobox with string ids section to the demos (John (J5) Palmieri)
+* [gi-overrides] add an override for Gdk.RGBA (John (J5) Palmieri)
+* [gi-demos] fix up search-entry to reflect annotations fixed in Gtk+ master (John (J5) Palmieri)
+* [gi-demos] add search entry demo (John (J5) Palmieri)
+* [gi] wrap map in a list for Python 3 compat (John (J5) Palmieri)
+* [gi-demos] fix up the validation combobox (John (J5) Palmieri)
+* add overridesdir variable in the .pc file for 3rd party overrides (John (J5) Palmieri)
+* setup.py: Set bdist_wininst user-access-control property (Dieter Verfaillie)
+* Fix uninitialized variable in gi.require_version() (Martin Pitt)
+* Run tests with LC_MESSAGES="C" (Martin Pitt)
+* [gi-overrides] override Gtk.stock_lookup to not return success (John (J5) Palmieri)
+
+2.28.6 - 2011-06-11
+-------------------
+
+* closure: avoid double free crash (Ignacio Casal Quinteiro)
+* [gi] backport of "GVariant has a GType" fe386a (John (J5) Palmieri)
+* [gi] fixes to backport commit 6b5a65 - in older glib GVariants are still structs (John (J5) Palmieri)
+* GVariantType is a boxed struct (Tomeu Vizoso)
+* Use _gi.Struct to wrap fundamentals (Tomeu Vizoso)
+* Added __eq__ method for Gdk.Color and Gdk.RGBA (Jason Siefken)
+* Remove useless import (Ignacio Casal Quinteiro)
+* Revert "[gi] Removed hack to avoid using GLib.Variant.new_variant." (Ignacio Casal Quinteiro)
+* closure: Check the out arg is not null. Fixes bug :bzbug:`651812` (Ignacio Casal Quinteiro)
+* Fix GC-related crash during PyGObject deallocation (Daniel Drake)
+* Fix symbol names to be locale independent (Martin Pitt)
+* Updated DOAP file (Sebastian Pölsterl)
+
+2.28.4 - 2011-04-18
+-------------------
+
+* Version bump to 2.24.4 (Sebastian Pölsterl)
+* [gi] Don't create variant twice (Sebastian Pölsterl)
+* pygi-convert.sh: Make sure the uppercase GObject module is imported instead of the lowercase (Sebastian Pölsterl)
+* [gi] Removed hack to avoid using GLib.Variant.new_variant. (Sebastian Pölsterl)
+* [gi] Added additional test case for GVariant handling (Sebastian Pölsterl)
+* [gi] Added support for GVariant arguments (Sebastian Pölsterl)
+* Fix ABI break in old static bindings. (Steve Frécinaux)
+* fetch size from an enum type (Mike Gorse)
+* dsextras.py: ensure eol characters are preserved when writing template files (so \n does not become \r\n) (Dieter Verfaillie)
+
+2.28.3 - 2011-03-23
+-------------------
+
+* fix a typo when converting objects to strings gvalues (John (J5) Palmieri)
+
+2.28.2 - 2011-03-22
+-------------------
+
+* fix static ABI for setting string gvalues from python objects (John (J5) Palmieri)
+* Fix GSchema tests for separate build tree (Martin Pitt)
+* GIO tests: Fix remaining test case for separate build tree (Martin Pit
+* GIO tests: Fix for separate build tree (Martin Pitt)
+
+2.28.1 - 2011-03-21
+-------------------
+
+* pygi-convert.sh remove gobject tests, GObject works now (John Stowers)
+* pygi-convert.sh add GObject.xxx and webkit (John Stowers)
+* [gi] marshal raw closures (John (J5) Palmieri)
+* Revert "Deduce PYTHON_LIBS in addition to PYTHON_INCLUDES" (Martin Pitt)
+* setup.py: fix user_access_control option (Dieter Verfaillie)
+* [gi-overrides] fix marshalling pygobjects in treemodels (John (J5) Palmieri)
+* [gi] Respect the MessageType for Gtk.MessageDialog (Martin Pitt)
+* [gi] Do not require signature for D-BUS methods without arguments (Martin Pitt)
+* [gi-demos] add pickers demo (John (J5) Palmieri)
+* [gi-demos] add menu demo (John (J5) Palmieri)
+* [gi-overrides] TreeViewColumn.set_cell_data_func func_data can be None
+* [gi-demos] dont try and run demos that represent directories (John Stowers)
+* [gi-overrides] fix exception block so it works in Python 2.5 (John (J5) Palmieri)
+
+2.28.0 - 2011-03-08
+-------------------
+
+* [gi-demos] some python 3 compat fixes (John (J5) Palmieri)
+* [gi-demos] catch the correct error class (John (J5) Palmieri)
+* Try not to sink objects returned by C functions. (Steve Frécinaux)
+* Do not leak python references when using the gobject.property() helper. (Steve Frécinaux)
+* [gi] fix try except blocks so they work in Python 2.5 (John (J5) Palmieri)
+* handle uchar as bytes, not strings in python 3 (John (J5) Palmieri)
+* [gi-overrides] handle unichar gvalues when setting treemodels (John (J5) Palmieri)
+* [gi-overrides] special case python 2 keywords that crept in (John (J5) Palmieri)
+* check for the py3 _thread module in configure.ac if thread is not found (John (J5) Palmieri)
+* [gi-demos] add iconview demo (John (J5) Palmieri)
+* [gi] wrap the keyword argument in a dict so we don't break Python 2.5 (John (J5) Palmieri)
+* [gi-demos] add the combobox with string ids section to the demos (John (J5) Palmieri)
+* [gi-overrides] add an override for Gdk.RGBA (John (J5) Palmieri)
+* [gi-demos] fix up search-entry to reflect annotations fixed in Gtk+ master (John (J5) Palmieri)
+* [gi-demos] add search entry demo (John (J5) Palmieri)
+* [gi] wrap map in a list for Python 3 compat (John (J5) Palmieri)
+* [gi-demos] fix up the validation combobox (John (J5) Palmieri)
+* add overridesdir variable in the .pc file for 3rd party overrides (John (J5) Palmieri)
+* [gi] remove unref for closures since they are floating objects that get sunk (John (J5) Palmieri)
+* setup.py: Set bdist_wininst user-access-control property (Dieter Verfaillie)
+* Fix uninitialized variable in gi.require_version() (Martin Pitt)
+* Run tests with LC_MESSAGES="C" (Martin Pitt)
+* [gi-overrides] override Gtk.stock_lookup to not return success (John (J5) Palmieri)
+
+2.27.91 - 2011-02-28 (2.28 pre-release)
+---------------------------------------
+
+* [gi-tests] use Gdk.test_simulate_button instead of emitting event ourselves (John (J5) Palmieri)
+* [gi-tests] tests for EventButton override. (Laszlo Pandy)
+* Skip interfaces when checking for conflicts in the MRO (Tomeu Vizoso)
+* [gi-overrides] Add event methods to all event union members (John (J5) Palmieri)
+* [gi] check to see if object is a member of a union when validating paramaters (John (J5) Palmieri)
+* [gi] Remove DyanmicModule.load() to _load() to prevent overriding GI attrs. (Laszlo Pandy)
+* Test case with John's fix for crash with C arrays and a GError is set. (Laszlo Pandy)
+* [gi-overrides] fix setting rows in treeview to accept None as a value (John (J5) Palmieri)
+* [gi] Add value_name for enum and flags from introspection "c:identifier" (if attr is available). (Laszlo Pandy)
+* Don't force loading of DynamicModule until set in sys.modules (Laszlo Pandy)
+* Fix flags with multiple names for the same value. (Laszlo Pandy)
+* [gi-demos] add liststore demo (John (J5) Palmieri)
+* [gi-demos] run through the demos and remove the FIXMEs that have been fixed (John (J5) Palmieri)
+* Load typelibs at import time, add gi.require_version() (Tomeu Vizoso)
+* use GValue support to marshal GtkTreeModel values correctly (John (J5) Palmieri)
+* [gi] pass raw GValues instead of trying to marshal them (John (J5) Palmieri)
+* [gi-demos] add icon view edit and drag-and-drop demo (John (J5) Palmieri)
+* [gi] Register GType for non-GType enums and flags at runtime. (Laszlo Pandy)
+* [gi-demos] add info bars demo (John (J5) Palmieri)
+* tests/runtests.py: Add missing "import sys" (Martin Pitt)
+* [gi] Add Pythonic gdbus method invocation (Martin Pitt)
+* Skip GError out parameters in Python closure. (Laszlo Pandy)
+* [gi-demos] added rotate text demo (John (J5) Palmieri)
+* [gi-demos] add images demo (John (J5) Palmieri)
+* [gi-demos] add pixbuf demo (John (J5) Palmieri)
+* [gi-demos] remove fixmes from print demo, fixed in pango (John (J5) Palmieri)
+* [gi-demos] add printing demo (John (J5) Palmieri)
+* [gi-overrides] add cursor overrides (John (J5) Palmieri)
+* [gi-demos] add the links demo (John (J5) Palmieri)
+* [gi-demos] add expander demo (John (J5) Palmieri)
+* [gi-overrides] use pop instead of del and add extra tests for Gtk.Table kwargs (John (J5) Palmieri)
+* [tests] Separate processes for GI and static binding tests. (Laszlo Pandy)
+* [GI] Remove implicit loading of gi module preserve the code path for static bindings. (Laszlo Pandy)
+* [gi-demos] add dialogs demo (John (J5) Palmieri)
+* [gi-overrides] fix typo in GtkTable constructor (John (J5) Palmieri)
+* [gi-demos] keep popup menu from destroying itself by holding a ref in app class (John (J5) Palmieri)
+* [gi-overrides] add a Gtk.Menu override for the popup method (John (J5) Palmieri)
+* [gi-demos] fix the about dialog in appwindow demo (John (J5) Palmieri)
+* [gi-demos] fix clipboard demo so DnD works (John (J5) Palmieri)
+* [gi-demos] fix clipboard demo to reflect new API (John (J5) Palmieri)
+* [gi-demo] Fix color dialog demo to run with new draw, style and color apis (John (J5) Palmieri)
+* [gi-demos] fix most of the combobox app (John (J5) Palmieri)
+* Use PyGI type conversion (to fix foreign types) for signal callbacks. (Laszlo Pandy)
+* [gi-demos] fix drawingarea app to use the new draw api (John (J5) Palmieri)
+* [gi-overrides] for Gtk 3 alias Gdk.Rectangle to cairo.RectangleInt (John (J5) Palmieri)
+* [gi-overrides] let user set the proper property names in Gtk.Table (John (J5) Palmieri)
+* [gi-demos] get appwindow demo working again (John (J5) Palmieri)
+* [gi-demos] fixed use of tree_iter_get (John (J5) Palmieri)
+
+2.27.90 - 2011-02-11 (2.28 pre-release)
+---------------------------------------
+
+* fix build to correctly use python-config (John (J5) Palmieri)
+* Run gio tests separately when enabled (Martin Pitt)
+* Revert "Remove gio static bindings" (Martin Pitt)
+* Decrease the refcount for GInitiallyUnowned constructors. (Steve Frécinaux)
+* Ensure the sink functions are only ran once. (Steve Frécinaux)
+* Revert "Fix wrong refcount when calling introspected widget constructors" (Steve Frécinaux)
+* Revert "Fix reference leaks for GInitiallyUnowned objects" (Steve Frécinaux)
+* Run test suite under dbus-launch (Martin Pitt)
+* Fix test_gdbus.py to be Python3 friendly (Martin Pitt)
+* [gi] Provide comfortable GSettings API (Martin Pitt)
+* Fix vfunc search bug when using GInterfaces and a do_* method. (Laszlo Pandy)
+* [GI] Add tests for Gtk.Widget.drag_* methods. (Laszlo Pandy)
+* [python 3] use the right syntaxis to raise exceptions (Ignacio Casal Quinteiro)
+* [gi] return PYGLIB_MODULE_ERROR_RETURN on error and use pygobject_init (Ignacio Casal Quinteiro)
+* [gi] return PYGLIB_MODULE_ERROR_RETURN on error (Ignacio Casal Quinteiro)
+* Fix wrong refcount when calling introspected widget constructors (Steve Frécinaux)
+* Gdk.Window: Map the standard constructor to the *new* constructor (Simon Schampijer)
+* Ship tests/org.gnome.test.gschema.xml in dist tarballs (Martin Pitt)
+* [gi] Add GSettings tests (Martin Pitt)
+* [gi] Provide GtkTextBuffer.insert_with_tags_by_name() (Martin Pitt)
+* [gi] Support tag names in GtkTextBuffer.insert_with_tags() (Martin Pitt)
+* Add MAINTAINERCLEANFILES (Ignacio Casal Quinteiro)
+* Remove .gitignore files and use git.mk (Ignacio Casal Quinteiro)
+* pygi-convert.sh: Convert Pango.TabAlign.* (Martin Pitt)
+* pygi-convert.sh: Drop window -> get_window() conversion (Martin Pitt)
+* pygi-convert.sh: Don't convert self.window assignments (Martin Pitt)
+* Fix leaked python reference in python-defined subclasses (Steve Frécinaux)
+* Add some tests for the number of python refs held at creation time (Steve Frécinaux)
+* Factor out parameter marshalling from construction functions. (Steve Frécinaux)
+* [gi] in python 3 an array of uint8 can be bytes but not string (John (J5) Palmieri)
+* [gi] fix Gio.FileEnumerator to reflect the Python 3 iter protocol (John (J5) Palmieri)
+* [gi] python 3 fixes (John (J5) Palmieri)
+* [gi] fix try/except blocks using depricated raise format (John (J5) Palmieri)
+* [gi] Add docstring to GLib.Variant constructor (Martin Pitt)
+* [gi] update gdbus test cases for previous GVariant change (Martin Pitt)
+* [gi] Accept only a single object in GLib.Variant constructor (Martin Pitt)
+* Speed up _setup_native_vfuncs() (Laszlo Pandy)
+* Speed up class creation: rewrite _setup_vfuncs() to be much more efficient. (Laszlo Pandy)
+* pygi-convert.sh: Convert gtk.UI_MANAGER_* (Sebastian Pölsterl)
+* pygi-convert.sh: Convert gdk.GRAB_* (Sebastian Pölsterl)
+* [gi] set the gtype GValue correctly (Ignacio Casal Quinteiro)
+* [gi] use the right argument type for callback (Ignacio Casal Quinteiro)
+* [gi] Add test cases for GDBus client operations (Martin Pitt)
+* [gi] Add Variant construction/unpack support for boxed Variants (Martin Pitt)
+* Merge branch 'windows-setup-fixes' (Dieter Verfaillie)
+* pygi-convert.sh: GdkPixbuf methods (Thomas Hindoe Paaboel Andersen)
+* pygi-convert.sh: Gdk.COLORSPACE_RGB (Thomas Hindoe Paaboel Andersen)
+* [gi] Support nested objects and empty sequences in GLib.Variant building (Martin Pitt)
+* Uncomment test_gi.TestInterfaceClash (Tomeu Vizoso)
+* Fix reference leaks for GInitiallyUnowned objects (Steve Frécinaux)
+* Add tests for refcount of a GObject owned by a library (Steve Frécinaux)
+* Add a test to check for regular object reference count (Steve Frécinaux)
+* [gi] Update TreeView.enable_model_drag_{source,dest} to current GTK (Martin Pitt)
+* Fix a typo in a private symbol name. (Steve Frécinaux)
+* pygi-convert.sh: Convert glib.source_remove() (Martin Pitt)
+* Fix typo in previous commit to actually convert glib.GError (Martin Pitt)
+* pygi-convert.sh: Move some glib bits which are better handled by gobject (Martin Pitt)
+* Modify override for Gtk.Adjustment to allow position or keyword arguments in __init__(). (Laszlo Pandy)
+* [gi] Fix small typo in previous commit (Martin Pitt)
+* [gi] Add pythonic iterator and indexing for string GVariants (Martin Pitt)
+* Construct structs using default API constructor (Tomeu Vizoso)
+* pygi-convert.sh: Migrate Gdk.Cursor constructor, and some cursor names (Martin Pitt)
+* pygi-convert.sh: Handle .window attributes (Martin Pitt)
+* Also deal with foreign boxed structs (Tomeu Vizoso)
+* [gi] Convert GErrors to GObject.GError exceptions, and throw them upon returning from calling the C function. (Laszlo Pandy)
+* pygi-convert.sh: Don't convert glib -> GLib for now (Martin Pitt)
+* Link libregress.so to GIO_LIBS again (Tomeu Vizoso)
+* Fix attributes 2BUTTON_PRESS and 3BUTTON_PRESS of Gdk.EventType. (Laszlo Pandy)
+* [gi] Fixed typo in exception (Sebastian Pölsterl)
+* [gi] Enable handling of Gdk.EventType.2BUTTON_PRESS and 3BUTTON_PRESS (Sebastian Pölsterl)
+* Revert "Fix Pango FontDescription override" (Martin Pitt)
+* Python iterator interface support for GFileEnumerator. (Tony Young)
+* Remove gio static bindings (Tomeu Vizoso)
+* [gi] set length when marshalling guint8 erases (Ignacio Casal Quinteiro)
+* Convert Gdk.Pixbuf to GdkPixbuf.Pixbuf (Sebastian Pölsterl)
+* Disable calls to PyGILState_* when threads are disabled (Arnaud Charlet)
+* pygi-convert.sh: Do not comment out set_cell_data_func() calls; these should be ported properly (Martin Pitt)
+* pygi-convert.sh: Fix match for adding missing imports (Martin Pitt)
+* pygi-convert.sh: Fix Gtk.Label handling to be idempotent (Martin Pitt)
+* Remove trailing whitespace from gi/overrides/Gtk.py (Laszlo Pandy)
+* Fix Pango FontDescription override (Martin Pitt)
+* tests: Respect existing $GI_TYPELIB_PATH (Martin Pitt)
+* Merge branch 'value' (Sebastian Pölsterl)
+* GTK overrides: Do type conversion to column types of ListStore and TreeStore in set_value (Sebastian Pölsterl)
+* Always register a new GType when a GObject class is subclassed (Steve Frécinaux)
+* Raise required versions of GLib and GObject-Introspection (Simon van der Linden)
+* pygi-convert.sh: Handle keysyms (Martin Pitt)
+* GLib overrides: Add test case for array variant building (Martin Pitt)
+* Remove cairo.RectangleInt from the foreign module (Tomeu Vizoso)
+* Dont try to guess the transfer if its a boxed (Tomeu Vizoso)
+* The tags can be Empty not None. (Ignacio Casal Quinteiro)
+* Add Pythonic iterators and indexing to GVariant (Martin Pitt)
+* Add GLib.Variant.unpack() (Martin Pitt)
+* Add override for gtk_text_buffer_insert_with_tags (Ignacio Casal Quinteiro)
+* Deduce PYTHON_LIBS in addition to PYTHON_INCLUDES (Simon van der Linden)
+* Kill JD_CHECK_PYTHON_HEADERS (Simon van der Linden)
+* Revert "Override Gtk.Box.pack_start and pack_end to set default values to be compliant with pygtk" (Sebastian Pölsterl)
+* Revert "Override Gtk.CellLayout.pack_start and pack_end to add default values to be compliant with pygtk" (Sebastian Pölsterl)
+* Revert "Override Gtk.TreeViewColumn.pack_start, pack_end and set_cell_data_func to add default values to be compliant with pygtk" (Sebastian Pölsterl)
+* pygi-convert.sh: Handle gtk.combo_box_new_text() (Martin Pitt)
+* Override TreeSortable.set_sort_func and set_default_sort_func to add default values to be pygtk compliant (Sebastian Pölsterl)
+* Override Gtk.TreeViewColumn.pack_start, pack_end and set_cell_data_func to add default values to be compliant with pygtk (Sebastian Pölsterl)
+* Override Gtk.CellLayout.pack_start and pack_end to add default values to be compliant with pygtk (Sebastian Pölsterl)
+* Override Gtk.Paned pack1 and pack2 to add default values to be compliant with pygtk (Sebastian Pölsterl)
+* Override Gtk.Box.pack_start and pack_end to set default values to be compliant with pygtk (Sebastian Pölsterl)
+* Handle GObject subclasses in the property helper. (Steve Frécinaux)
+* Fix handling of unicode for GtkTreeModels (Martin Pitt)
+* In IntrospectionModule and DynamicModule classes, make all instance attributes start with an underscore. (Laszlo Pandy)
+* Amend previous enum wrapping commit to remove redundant setting of __info__ attribute. (Laszlo Pandy)
+* pygi-convert.sh: Handle GdkPixbuf.InterpType (Martin Pitt)
+* Fix wrapping of enums: Create new Python type for each non-gtype enum. (Laszlo Pandy)
+* Use g_vfunc_info_invoke for chaining up in vfuncs (Tomeu Vizoso)
+* Move pyglib_{main_context, option_context, option_group}_new into _PyGLib_API (Simon van der Linden)
+* pygi-convert.sh: Handle Gdk.DragAction (Martin Pitt)
+* pygi-convert.sh: Generalize Gtk.Settings migration (Martin Pitt)
+* pygi-convert.sh: Don't change the name of "glib" submodules (Martin Pitt)
+* Plug another memory leak (Paolo Borelli)
+* Plug a small memory leak. (Paolo Borelli)
+* Override Table.attach() to behave like pygtk (Paolo Borelli)
+* pygi-convert.sh: Convert Pango.WrapMode (Martin Pitt)
+* pygi-convert.sh: Don't change the name of "gtk" submodules (Martin Pitt)
+* Fix the __dir__() methods on DynamicModule and IntrospectionModule (Laszlo Pandy)
+* pygi-convert.sh: handle ReliefStyle (Paolo Borelli)
+* setup.py: fix the provides keyword argument (Dieter Verfaillie)
+* setup.py: use the same spaces-less format for all setup() parameters (Dieter Verfaillie)
+* Add a __repr__() method to DynamicModule. (Laszlo Pandy)
+* Go back to using getattr() in DynamicModule.__getattr__ (Tomeu Vizoso)
+* Change __dir__() to report all the attributes that __getattr__ supports (Laszlo Pandy)
+* Bump the minimum gio dependency (Emilio Pozuelo Monfort)
+* Add test for incorrect attributes in Gdk.Event (Tomeu Vizoso)
+* Don't call getattr again in gi.overrides.Gdk.Event.__getattr__ (Simon van der Linden)
+* Release allocated array of arguments when handling closures (Mike Gorse)
+* Release GIValueInfo when checking an enum argument (Mike Gorse)
+* Respect different type lengths when assigning out-argument pointers. (Eitan Isaacson)
+* Fix stupid name clash (Tomeu Vizoso)
+* Add /usr/share to XDG_DATA_DIRS when running the tests (Tomeu Vizoso)
+* Comment out tests that require SRV lookups (Tomeu Vizoso)
+* Use suppresion file when running valgrind (Tomeu Vizoso)
+* Fix warnings. (Ignacio Casal Quinteiro)
+* Allow comparing Gtk.TreePath to None (Jesse van den Kieboom)
+* handle unicode objects in properties (John (J5) Palmieri)
+* dsextras.py: check if gcc is there when platform is win32 and compiler is mingw32 (Dieter Verfaillie)
+* dsextras.py: be consistent in how distutils imports are done (Dieter Verfaillie)
+* dsextras.py: add have_gcc() function (Dieter Verfaillie)
+* dsextras.py: use distutils.spawn.find_executable for have_pkgconfig() (Dieter Verfaillie)
+* setup.py: fix another case of use True/False instead of 1/0 (Dieter Verfaillie)
+* pygi-convert.sh: improve GtkSourceView conversion (Paolo Borelli)
+* pygi-convert.sh: Gtk.DialogFlags conversion (Paolo Borelli)
+*       Doc Extractor: Print the gtk-doc blocks sorted by function name. (José Alburquerque)
+* pygi-convert.sh: add more Gtk conversions and sort (Paolo Borelli)
+* pygi-convert.sh: convert Atk (Paolo Borelli)
+* pygi-convert.sh: convert a few more Gio types (Paolo Borelli)
+* pygi-convert.sh: more GLib conversion (Paolo Borelli)
+* pygi-convert.sh: remove two cases handled by overrides (Paolo Borelli)
+* Override Gtk.ScrolledWindow constructor (Paolo Borelli)
+* pygi-convert.sh: Fix 'find' syntax (Paolo Borelli)
+* pygi-convert.sh: start handling Gio and GLib (Paolo Borelli)
+* pygi-convert.sh: convert Gdk.ScrollDirection. (Paolo Borelli)
+* Override Pango.Layout constructor. (Paolo Borelli)
+* Remove Pango.FontDescription() conversion. (Paolo Borelli)
+* Override GtkAction and GtkRadioAction constructors. (Paolo Borelli)
+* Override Adjustment constructor to behave like pygtk (Dmitrijs Ledkovs)
+* add secondary_text apis to MessageDialog (John (J5) Palmieri)
+* [gi] get rid of some debug prints and fix error messages (John (J5) Palmieri)
+* Fix demo for override changes. (Paolo Borelli)
+* Override Pango.FontDescription. (Paolo Borelli)
+* Stop checking that all vfuncs are implemented (Tomeu Vizoso)
+* Fix usage of TreeIter api that is now an override. (Paolo Borelli)
+* Fix Gtk.Label(label="Foo") (Paolo Borelli)
+* Fix typo when raising an exception (Paolo Borelli)
+* pygi-convert.sh: Added more conversions (Sebastian Pölsterl)
+* Override LinkButton constructor to make 'uri' mandatory (Paolo Borelli)
+* Container should be iterable. (Dmitry Morozov)
+* No need to import Gdk (Paolo Borelli)
+* Remove semicolumns (Paolo Borelli)
+* [gi] make sure Gtk.Button override passes all keywords to parent constructor (John (J5) Palmieri)
+* Fix cut&paste error in the Label override (Paolo Borelli)
+* pygi-convert.sh: handle TextWindowType (Paolo Borelli)
+* Override Label constructor to behave like pygtk (Paolo Borelli)
+* Override GtkTable constructor to behave like pygtk (Paolo Borelli)
+* pygi-convert.sh: convert MovementStep (Paolo Borelli)
+* Update Gdk overrides to work with latest Gtk+ 3 (Paolo Borelli)
+* Gtk: add an override for Gtk.main_quit (Johan Dahlin)
+* [gi] handle subtypes when inserting into tree models (John (J5) Palmieri)
+* Override TreeSelection.select_path and TreeView.scroll_to_cell (Paolo Borelli)
+* Override TreePath.__new__ (Paolo Borelli)
+* Override Container to behave like a sequence (Paolo Borelli)
+* refactor Jonathan Matthew recurse vfunc patch so it applys and clean up a bit (John (J5) Palmieri)
+* Recurse up through base classes when setting up vfuncs (Jonathan Matthew)
+* add a profiling torture test for when we fix up invoke (John (J5) Palmieri)
+* moved dynamic and base modules outside of gtk-2.0 directory (John (J5) Palmieri)
+* add test for inout argument count (John (J5) Palmieri)
+* [gi] add check for UNICHAR (John (J5) Palmieri)
+* Support gunichar (Paolo Borelli)
+* pygi-convert.sh: gtk.accel_map -> Gtk.AccelMap._ (Paolo Borelli)
+* pygi-convert.sh: handle "from gtk import gdk" (Paolo Borelli)
+* pygi-convert.sh: add some Pango special cases (Paolo Borelli)
+* Override TextIter (begins|ends|toggles)_tag() (Paolo Borelli)
+* Override TextBuffer.set_text() to make length optional (Paolo Borelli)
+* Override TextBuffer.create_mark() (Paolo Borelli)
+* Fix TextBuffer.get_selection_bounds() override (Paolo Borelli)
+* [gi] fix ActionGroup constructor to allow other keyword properties to be set (John (J5) Palmieri)
+* [gi] require the name parameter when creatin a Gtk.ActionGroup (John (J5) Palmieri)
+* Override UIManager.insert_action_group (Paolo Borelli)
+* Override TreeModel.get() to return a tuple (Paolo Borelli)
+* Make TreeSelection.get_selected_rows compatible with PyGtk (Paolo Borelli)
+* [gi] switch to using sequences/tuples when marshalling cairo_rectangle_int_t (John (J5) Palmieri)
+* [gi] overrides for treeview Drag and Drop (John (J5) Palmieri)
+* [gi] when encountering guint8 arrays treat them as byte arrays (John (J5) Palmieri)
+* pygi-convert.sh: Add pynotify -> Notify (Martin Pitt)
+* pygi-convert.sh: Remove sugar specifics, and allow command line file list (Martin Pitt)
+* pygi-convert.sh: Cover Message and Buttons types (Martin Pitt)
+* [gi] fix actiongroup test since actions are hashed (John (J5) Palmieri)
+* [gi] when converting to UTF-8 accept Python Unicode objects as input (Python 2) (John (J5) Palmieri)
+* Correct a bug in the freeing of memory in pygi-invoke.c. (Damien Caliste)
+* update news for release (John (J5) Palmieri)
+* Implement richcompare for GIBaseInfo (Jonathan Matthew)
+* [gi] add the rectangle_int_t forign cairo type (John (J5) Palmieri)
+* add a foreign type for cairo_rectangle_int_t and allow it to be caller-allocated (John (J5) Palmieri)
+* [gi] add overrides to Gtk.Editable (John (J5) Palmieri)
+* [gi] handle virtual invokers (John (J5) Palmieri)
+* add overrides for the insert* apis of list_store and tree_store (John (J5) Palmieri)
+* fix dialogs overrides which were relying on broken inheritance behavior (John (J5) Palmieri)
+* Add a overrides registry so we can refrence overrides inside the module (John (J5) Palmieri)
+* Merge remote branch 'dieterv/setup-fixes-for-merge' (John Stowers)
+* setup.py: ease maintenance burden for tests installation (Dieter Verfaillie)
+* fix inheritence issues in overrides (John (J5) Palmieri)
+* tests: add runtests-windows.py script (Dieter Verfaillie)
+* pygobject_postinstall.py: remove pygobject-2.0.pc treatment from postinstall as pkg-config on windows figures out the correct prefix at runtime (Dieter Verfaillie)
+* pygobject_postinstall.py: remove shortcut creation (Dieter Verfaillie)
+* setup.py: formatting cleanup, makes things readable (Dieter Verfaillie)
+* setup.py: build and install tests (Dieter Verfaillie)
+* setup.py: install documentation when available on build system (Dieter Verfaillie)
+* setup.py: install pygobject-codegen script (Dieter Verfaillie)
+* setup.py: install fixxref.py script (Dieter Verfaillie)
+* setup.py: rearrange constants (Dieter Verfaillie)
+* setup.py: check python version and pkgconig availability before anything else (Dieter Verfaillie)
+* setup.py: simplify sys.platform != 'win32' detection and error reporting (Dieter Verfaillie)
+* setup.py: rearrange imports (Dieter Verfaillie)
+* README.win32: update build instructions (Dieter Verfaillie)
+* dsextras.py: formatting cleanup, makes things readable (Dieter Verfaillie)
+* dsextras.py: add ggc4 to MSVC compatible struct packing comment (Dieter Verfaillie)
+* dsextras.py: use the ``pkgc_`` functions instead of repeating pgk-config incantations all over the place (Dieter Verfaillie)
+* dsextras.py: add pkgc_get_version and pkgc_get_defs_dir functions (Dieter Verfaillie)
+* dsextras.py: PEP8: Comparisons to singletons like None should always be done with 'is' or 'is not', never the equality operators. (Dieter Verfaillie)
+* dsextras.py: use True/False instead of 1/0 (Dieter Verfaillie)
+* dsextras.py: rearrange imports (Dieter Verfaillie)
+* Add distutils generated build/dist directories and eclipse configuration files to .gitignore (Dieter Verfaillie)
+* [gi] add tests for calling dir on a dynamic module (John (J5) Palmieri)
+* [gi] dir() now works for modules (Deepankar Sharma)
+* Don't check the inner type when comparing gpointers (Simón Pena)
+* Release GIL when calling into C functions (John (J5) Palmieri)
+* _gi.Repository : Implement missing info bindings. (José Aliste)
+* include Python.h so that PY_VERSION_HEX gets defined (John (J5) Palmieri)
+* [gi] make overrides work for python 3.x protocols and alias for python 2.x (John (J5) Palmieri)
+* Override Gtk.Widget.translate_coordinates to not return success value (Sebastian Pölsterl)
+* Override Gtk.TreeViewColumn.cell_get_position to not return success value (Sebastian Pölsterl)
+* Override get_path_at_pos and get_dest_row_at_pos of Gtk.TreeView to not return success value (Sebastian Pölsterl)
+* Override Gtk.TreeSortable.get_sort_column_id to not return success value (Sebastian Pölsterl)
+* Override forward_search and backward_search of Gtk.TextIter to not return success value (Sebastian Pölsterl)
+* Override Gtk.TextBuffer.get_selection_bounds to not return success value (Sebastian Pölsterl)
+* Override Gtk.RecentInfo.get_application_info to not return success value (Sebastian Pölsterl)
+* Override Gtk.IMContext.get_surrounding to not return success value (Sebastian Pölsterl)
+* Override get_item_at_pos, get_visible_range, get_dest_item_at_pos of Gtk.IconView to not return success value (Sebastian Pölsterl)
+* Override Gtk.Container.get_focus_chain to not return success value (Sebastian Pölsterl)
+* Override Gtk.ComboBox.get_active_iter to not return success value (Sebastian Pölsterl)
+* [gi] make parameter check less strict when dealing with GValue params (John (J5) Palmieri)
+* Shortcut removal is not needed on post-uninstall (John Stowers)
+* Disable shortcut creation in windows installer (John Stowers)
+* overrides for all subclasses of dialog (John (J5) Palmieri)
+* Make TreeModel behave like in GTK-2.x (Sebastian Pölsterl)
+* Correctly build GIO on windows (John Stowers)
+* Require Python >= 2.6.0 for Windows build (John Stowers)
+* Fix depreciation warning in dsextras.py (John Stowers)
+* Fix build on windows (John Stowers)
+* Support for GCC4 in Windows distutils build - bug 626548 (Michael Culbertson)
+* Remove obsolete comments in dsextras.py (John Stowers)
+* Broken dsextras.py pkg-config check error message (John Stowers)
+* add compat functions for the deprecated PyCObject api (John (J5) Palmieri)
+* Add __path__ attributes. (Damien Caliste)
+* Override Gtk.TreeSelection.get_selected to not return success value. (Sebastian Pölsterl)
+* Make row optional in Gtk.TreeStore/ListStore.append override (Vincent Untz)
+* Revert "add compat functions for the deprecated PyCObject api" (John (J5) Palmieri)
+* return NULL instead of -1 which fixes crash when introspection is turned off (John (J5) Palmieri)
+* add compat functions for the deprecated PyCObject api (John (J5) Palmieri)
+* fix commit 7fe83108 which didn't use the compat functions for string handling (John (J5) Palmieri)
+* Python 3 fixes for dsextras and the python.m4 distribution files (John (J5) Palmieri)
+
+2.27.0 - 2010-11-10
+-------------------
+
+* Implement richcompare for GIBaseInfo (Jonathan Matthew)
+* [gi] add the rectangle_int_t forign cairo type (John (J5) Palmieri)
+* add a foreign type for cairo_rectangle_int_t and allow it to be caller-allocated (John (J5) Palmieri)
+* [gi] add overrides to Gtk.Editable (John (J5) Palmieri)
+* [gi] handle virtual invokers (John (J5) Palmieri)
+* add overrides for the insert* apis of list_store and tree_store (John (J5) Palmieri)
+* fix dialogs overrides which were relying on broken inheritance behavior (John (J5) Palmieri)
+* Add a overrides registry so we can refrence overrides inside the module (John (J5) Palmieri)
+* Merge remote branch 'dieterv/setup-fixes-for-merge' (John Stowers)
+* setup.py: ease maintenance burden for tests installation (Dieter Verfaillie)
+* fix inheritence issues in overrides (John (J5) Palmieri)
+* tests: add runtests-windows.py script (Dieter Verfaillie)
+* pygobject_postinstall.py: remove pygobject-2.0.pc treatment from postinstall as pkg-config on windows figures out the correct prefix at runtime (Dieter Verfaillie)
+* pygobject_postinstall.py: remove shortcut creation (Dieter Verfaillie)
+* setup.py: formatting cleanup, makes things readable (Dieter Verfaillie)
+* setup.py: build and install tests (Dieter Verfaillie)
+* setup.py: install documentation when available on build system (Dieter Verfaillie)
+* setup.py: install pygobject-codegen script (Dieter Verfaillie)
+* setup.py: install fixxref.py script (Dieter Verfaillie)
+* setup.py: rearrange constants (Dieter Verfaillie)
+* setup.py: check python version and pkgconig availability before anything else (Dieter Verfaillie)
+* setup.py: simplify sys.platform != 'win32' detection and error reporting (Dieter Verfaillie)
+* setup.py: rearrange imports (Dieter Verfaillie)
+* README.win32: update build instructions (Dieter Verfaillie)
+* dsextras.py: formatting cleanup, makes things readable (Dieter Verfaillie)
+* dsextras.py: add ggc4 to MSVC compatible struct packing comment (Dieter Verfaillie)
+* dsextras.py: use the ``pkgc_`` functions instead of repeating pgk-config incantations all over the place (Dieter Verfaillie)
+* dsextras.py: add pkgc_get_version and pkgc_get_defs_dir functions (Dieter Verfaillie)
+* dsextras.py: PEP8: Comparisons to singletons like None should always be done with 'is' or 'is not', never the equality operators. (Dieter Verfaillie)
+* dsextras.py: use True/False instead of 1/0 (Dieter Verfaillie)
+* dsextras.py: rearrange imports (Dieter Verfaillie)
+* Add distutils generated build/dist directories and eclipse configuration files to .gitignore (Dieter Verfaillie)
+* [gi] add tests for calling dir on a dynamic module (John (J5) Palmieri)
+* [gi] dir() now works for modules (Deepankar Sharma)
+* Don't check the inner type when comparing gpointers (Simón Pena)
+* Release GIL when calling into C functions (John (J5) Palmieri)
+* _gi.Repository : Implement missing info bindings. (José Aliste)
+* include Python.h so that PY_VERSION_HEX gets defined (John (J5) Palmieri)
+* [gi] make overrides work for python 3.x protocols and alias for python 2.x (John (J5) Palmieri)
+* Override Gtk.Widget.translate_coordinates to not return success value (Sebastian Pölsterl)
+* Override Gtk.TreeViewColumn.cell_get_position to not return success value (Sebastian Pölsterl)
+* Override get_path_at_pos and get_dest_row_at_pos of Gtk.TreeView to not return success value (Sebastian Pölsterl)
+* Override Gtk.TreeSortable.get_sort_column_id to not return success value (Sebastian Pölsterl)
+* Override forward_search and backward_search of Gtk.TextIter to not return success value (Sebastian Pölsterl)
+* Override Gtk.TextBuffer.get_selection_bounds to not return success value (Sebastian Pölsterl)
+* Override Gtk.RecentInfo.get_application_info to not return success value (Sebastian Pölsterl)
+* Override Gtk.IMContext.get_surrounding to not return success value (Sebastian Pölsterl)
+* Override get_item_at_pos, get_visible_range, get_dest_item_at_pos of Gtk.IconView to not return success value (Sebastian Pölsterl)
+* Override Gtk.Container.get_focus_chain to not return success value (Sebastian Pölsterl)
+* Override Gtk.ComboBox.get_active_iter to not return success value (Sebastian Pölsterl)
+* [gi] make parameter check less strict when dealing with GValue params (John (J5) Palmieri)
+* Shortcut removal is not needed on post-uninstall (John Stowers)
+* Disable shortcut creation in windows installer (John Stowers)
+* overrides for all subclasses of dialog (John (J5) Palmieri)
+* Make TreeModel behave like in GTK-2.x (Sebastian Pölsterl)
+* Correctly build GIO on windows (John Stowers)
+* Require Python >= 2.6.0 for Windows build (John Stowers)
+* Fix depreciation warning in dsextras.py (John Stowers)
+* Fix build on windows (John Stowers)
+* Support for GCC4 in Windows distutils build - bug 626548 (Michael Culbertson)
+* Remove obsolete comments in dsextras.py (John Stowers)
+* Broken dsextras.py pkg-config check error message (John Stowers)
+* add compat functions for the deprecated PyCObject api (John (J5) Palmieri)
+* Add __path__ attributes. (Damien Caliste)
+* Override Gtk.TreeSelection.get_selected to not return success value. (Sebastian Pölsterl)
+* Make row optional in Gtk.TreeStore/ListStore.append override (Vincent Untz)
+* Revert "add compat functions for the deprecated PyCObject api" (John (J5) Palmieri)
+* return NULL instead of -1 which fixes crash when introspection is turned off (John (J5) Palmieri)
+* add compat functions for the deprecated PyCObject api (John (J5) Palmieri)
+* fix commit 7fe83108 which didn't use the compat functions for string handling (John (J5) Palmieri)
+* Python 3 fixes for dsextras and the python.m4 distribution files (John (J5) Palmieri)
+
+2.26.0 - 2010-09-24
+-------------------
+
+* Wrap g_get_system_{config,data}_dirs () (John Strowers)
+* fixed make check and make dist (John (J5) Palmieri)
+* Disable GI tests when introspection disabled (John Stowers)
+* Wrap g_uri_list_extract_uris. Fixes bug :bzbug:`584431` (Tomeu Vizoso)
+* Fix a few uses of TRUE and FALSE in the docs (Paul Bolle)
+* pygi: always free the invocation_state struct (Damien Caliste)
+* Start implementing something equivalent to g_variant_new (Tomeu Vizoso)
+* fixed typo - missing comma in glib.option module (John (J5) Palmieri)
+* add checks so we can compile under python 3 by setting PYTHON=python3 (John (J5) Palmieri)
+* Rename static methods as functions (Tomeu Vizoso)
+* fix a couple of compiler warnings (John (J5) Palmieri)
+* remove unused code (John (J5) Palmieri)
+* Check the type of the instance object (John (J5) Palmieri)
+* include the correct pycairo version (John (J5) Palmieri)
+* Use PyMapping_Keys to determine if an object is a dict (py3k fix) (John (J5) Palmieri)
+* fix handling of UINT64 and INT64 arguments in py3k (John (J5) Palmieri)
+* properly handle ulongs properties in py3k (John (J5) Palmieri)
+* Specify encoding of tests/test_gi.py (Tomeu Vizoso)
+* use actual unicode in the tests on py3k, not the byte representation (John (J5) Palmieri)
+* s/METH_KEYWORDS/METH_VARARGS|METH_KEYWORDS/ when defining object methods (John (J5) Palmieri)
+* fix subclassing PyLong by calling __new__ correctly (John (J5) Palmieri)
+* minor py3k fixups for python modules (John (J5) Palmieri)
+* minor fixes in tests for py3k compat (John (J5) Palmieri)
+* compilation: Fix syntax error (Colin Walters)
+* Add missing file (Tomeu Vizoso)
+* Add override for GLib.Variant.new_tuple (Tomeu Vizoso)
+* fix for changes in the gi test libraries (John (J5) Palmieri)
+* Gtk.DialogFlags.NO_SEPARATOR has been removed in Gtk 3.0 (John (J5) Palmieri)
+* no need to offset arg positions when is_method is true (John (J5) Palmieri)
+* gi: Add support for more property types (Tomeu Vizoso)
+* use PyObject_SetAttrString, not PyDict_SetItemString when setting __gtype__ (John (J5) Palmieri)
+* Rename GArgument to GIArgument (Tomeu Vizoso)
+* fix up tests so they run in py3k (John (J5) Palmieri)
+* tests: Port to new introspection tests (Colin Walters)
+* we need to specify tp_hash since we overide tp_richcompare (John (J5) Palmieri)
+* working enum/flags/pid subclasses of long (John Ehresman)
+* make vfuncs work in py3k (John (J5) Palmieri)
+* make cairo module compile in py3k (John (J5) Palmieri)
+* fix exceptions so they work in python 3.x (John (J5) Palmieri)
+* make the gi module compile under 3.x (John (J5) Palmieri)
+* fix up testshelper module so it compiles in python 3.x (John (J5) Palmieri)
+* convert to using PYGLIB_DEFINE_TYPE for module objects (John (J5) Palmieri)
+* some more p3k PyString and PyInt eradication in GI (John (J5) Palmieri)
+* pyglib: Fix typo (Leo Singer) (Tomeu Vizoso)
+* Add defines for size_t and ssize_t conversion functions (Gustavo Noronha Silva)
+* pyglib: Fix a compiler warning (Colin Walters)
+* Don't force gtk 2.0 (Tomeu Vizoso)
+* Fix some ref leaks in hook_up_vfunc_implementation() (Steve Frécinaux)
+* handle strings correctly in gio (John (J5) Palmieri)
+* make giomodule compile under py3k (John (J5) Palmieri)
+* for py3k we need to do some more processing to get bytes from a unicode string (John (J5) Palmieri)
+* use Bytes instead of Unicode when reading io (John (J5) Palmieri)
+* prefix compat macros with PYGLIB (John (J5) Palmieri)
+* Gtk.Button unit tests (John (J5) Palmieri)
+* [Gtk] Add overrides for Button (Johan Dahlin)
+* Make Cairo an optional dependency (Simon van der Linden)
+* Don't import again PyGObject (John Ralls) (Tomeu Vizoso)
+* move to using richcompare slot instead of compare (John (J5) Palmieri)
+* Replace autogen.sh by a newer version (Simon van der Linden)
+* Fix some warnings (Simon van der Linden)
+* Fix caller-allocates emergency free. (Simon van der Linden)
+* Remove useless checks. (Simon van der Linden)
+* Call valgrind with G_SLICE=always-malloc G_DEBUG=gc-friendly (Tomeu Vizoso)
+* Fix some warnings. (Ignacio Casal Quinteiro)
+* Add myself as a maintainer (Simon van der Linden)
+* Properly allocate boxed structs that are (caller-allocates) (Tomeu Vizoso)
+* override gdk.Event to return attribute from the proper event object (Toms Baugis)
+* check if z# needs an int or Py_ssize_t (John (J5) Palmieri)
+* make sure we parse parameters to python object vars not glib vars (John (J5) Palmieri)
+* Make an example and a demo work out of the box (Paul Bolle)
+* make sure caller allocated structs are freed when they go out of scope (John (J5) Palmieri)
+* Revert "override gdk.Event to return attribute from the proper event object." (Tomeu Vizoso)
+* PyGI: properly quit cairo-demo (Paul Bolle)
+* override gdk.Event to return attribute from the proper event object. (Toms Baugis)
+* Clean and improve the test infrastructure (Simon van der Linden)
+* Add some more transformations to pygi-convert.sh (Tomeu Vizoso)
+* Adapt to API changes: g_irepository_enumerate_versions (Tomeu Vizoso)
+* Add GValue<->GArgument marshalling for some more types (Tomeu Vizoso)
+* Chain up with the non-introspection implementation for properties if needed (Tomeu Vizoso)
+* Improve error reporting for missing attributes in introspection modules (Tomeu Vizoso)
+* Implement getting and setting properties using introspection information. (Tomeu Vizoso)
+* Readd Gdk.Rectangle override for Gtk-2.0 (Tomeu Vizoso)
+* Allow specifying a version when loading a typelib (Tomeu Vizoso)
+* treat GFreeFunc as equivalent to GDestroyNotify when scanning callbacks (Jonathan Matthew)
+* Don't use == to compare doubles, use <= and =>. (Simon van der Linden)
+* Allow passing ints as enum args (Tomeu Vizoso)
+* Make error message less ambiguous (Tomeu Vizoso)
+* fix passing in type names as a GType and add gtype unit tests (John (J5) Palmieri)
+* Increase a bit verbosity of tests so people know which test failed (Tomeu Vizoso)
+* Actually add the files for GVariant foreign structs (Tomeu Vizoso)
+* Add foreign struct support for GVariant (Tomeu Vizoso)
+
+2.21.5 - 2010-07-12
+-------------------
+
+* Shut up some compiler warnings (Florian Müllner)
+* Adjust to API break in GObject-Introspection (Florian Müllner)
+* pass in the demo app so demos can use utility methods like requesting file paths (John (J5) Palmieri)
+* demo fixes to keep up with Gtk+ (John (J5) Palmieri)
+* override test fixes for new GTK+ annotations (John (J5) Palmieri)
+* Fix warning. (Ignacio Casal Quinteiro)
+* fix up treeiter usage due to caller-allocates annotations in gtk+ (John (J5) Palmieri)
+* add entry completion demo (John (J5) Palmieri)
+* string changes (John (J5) Palmieri)
+* add the Entry demo directory and the entry_buffer demo (John (J5) Palmieri)
+* fix loading of demo modules to support sub modules (John (J5) Palmieri)
+* add the ability to have demos in sub catagories (John (J5) Palmieri)
+* Add  __name__ to DynamicModule class. (Jose Aliste)
+* Do not override GdkRectangle. (Ignacio Casal Quinteiro)
+* Add override for TreeModel implementing __len__() (Philip Withnall)
+
+2.21.4 - 2010-06-29
+-------------------
+
+* Build the cairo shim as a python module so the _gi module stops linking to it (Tomeu Vizoso)
+* add drawing area demo (John (J5) Palmieri)
+* sort the demo list (John (J5) Palmieri)
+* rename iter to treeiter so we aren't using a python reserved word (John (J5) Palmieri)
+* Fixup for change in buffer API (John (J5) Palmieri)
+* add ListStore, TreeStore and TreeViewColumn APIs (John (J5) Palmieri)
+* Add unit test for add_actions user data. (Ignacio Casal Quinteiro)
+* Pass user_data param when adding actions (Paolo Borelli)
+* add an exception type to the try/except block (John (J5) Palmieri)
+* return PyList instead of PyTuple for array, return empty list for NULL arrays (John (J5) Palmieri)
+* Fix 'make distcheck' (Tomeu Vizoso)
+* Allow building pygobject without introspection support by providing --disable-introspection to configure. (Tomeu Vizoso)
+* Make sure that sys.argv is a list and not a sequence. (Tomeu Vizoso)
+* Force loading the GObject typelib so we have available the wrappers for base classes such as GInitiallyUnowned. (Tomeu Vizoso)
+* we shouldn't g_array_free NULL pointers (John (J5) Palmieri)
+* remove unneeded TextIter creation in the tests (John (J5) Palmieri)
+* add override for TextBuffer (John (J5) Palmieri)
+* fix up some build issues (John (J5) Palmieri)
+* make the overrides file git friendly by appending to __all__ after each override (John (J5) Palmieri)
+* Override Dialog constructor and add_buttons method (Paolo Borelli)
+* Merge PyGI (Johan Dahlin)
+
+2.21.3 - 2010-06-21
+-------------------
+
+* Proper handling of null-ok in virtual methods (Ludovic L'Hours)
+* Fall back to use the floating references API in glib if there isn't a sinkfunc defined. (Tomeu Vizoso)
+* Revert "Drop sinkfuncs." (Tomeu Vizoso)
+* [giounix] Make it possible to compile on glib 2.20 (Johan Dahlin)
+* Release the lock when potentially invoking Python code. (Sjoerd Simons)
+
+2.21.2 - 2010-06-10
+-------------------
+
+* Drop sinkfuncs. (Tomeu Vizoso)
+* Clear error if we failed the import (Colin Walters)
+* Added missing , to keyword list of gio.GFile.set_attribute (John Ehresman)
+* Fix arg conversion in gio.GFile.set_attribute (John Ehresman)
+* Set constants under python 2.5 or before (John Ehresman)
+* Doc Extractor: Use replacements that make sense for &...; expressions. (José Alburquerque)
+* Add build docs for windows (John Stowers)
+* Setup.py cosmetic tidy (John Stowers)
+* Fix crash when importing gio (John Stowers)
+* Bug 589671 - Dont use generate-constants (John Stowers)
+* Bug 589671 - Fix setup.py for windows build (John Stowers)
+* Include pygsource.h (John Stowers)
+* codegen/docextract_to_xml.py: One more &...; replacement (&nbsp;). (José Alburquerque)
+* codegen/docextract_to_xml.py: Replace some &..; that cause errors. (José Alburquerque)
+* codegen/docextract_to_xml.py: Handle C++ multi-line comments. (José Alburquerque)
+* codegen/docextract.py: Stop final section processing on first match. (José Alburquerque)
+* Update doc extraction tool to handle GObjectIntrospection annotations. (José Alburquerque)
+* Docs: replace gio.IO_ERROR_* with gio.ERROR_* (Paul Bolle)
+* Bug 613341 - pygobject tests seem to require pygtk causing a circular (Gian Mario)
+* Don't raise an error in _pygi_import if pygi support is disabled (Simon van der Linden)
+* Initialize PyGPollFD_Type.fd_obj to NULL (Tomeu Vizoso)
+* Bug 605937 - pygobject: Makefile.am sets $TMPDIR, disrupting distcc (Gian Mario)
+* Wrap gio.Cancellable.make_pollfd() and add a test (Gian Mario)
+* Make cancellable an optional parameter in many methods (Gian Mario)
+
+2.21.1 - 2010-01-02
+-------------------
+
+* Wrap gio.Volume.eject_with_operation (Gian Mario)
+* Wrap gio.Mount.eject_with_operation (Gian Mario)
+* Wrap gio.Mount.unmount_mountable_with_operation (Gian Mario)
+* Wrap File.unmount_mountable_with_operation (Gian Mario)
+* Wrap gio.File.stop_mountable (Gian Mario)
+* Wrap gio.File.start_mountable (Gian Mario)
+* Wrap gio.File.replace_readwrite_async (Gian Mario)
+* Wrap gio.File.poll_mountable (Gian Mario)
+* Wrap gio.File.open_readwrite_async (Gian Mario)
+* Wrap gio.File.eject_mountable_with_operation (Gian Mario)
+* Wrap gio.File.create_readwrite_async (Gian Mario)
+* Wrap gio.Drive.stop (Gian Mario)
+* Wrap gio.Drive.start (Gian Mario)
+* Wrap gio.SocketListener.accept_socket_async|finish (Gian Mario)
+* Wrap gio.SocketListener.accept_finish (Gian Mario)
+* Wrap gio.SocketListener.accept_async (Gian Mario)
+* Wrap gio.SocketListener.accept_socket (Gian Mario)
+* Wrap gio.SocketListener.accept (Gian Mario)
+* Make cancellable optional in gio.SocketClient.connect_to_host
+  (Gian Mario)
+* Wrap gio.SocketListener.add_address (Gian Mario)
+* Wrap gio.SocketClient.connect_to_service_async (Gian Mario)
+* Wrap gio.SocketClient.connect_to_host_async (Gian Mario)
+* Wrap gio.SocketClient.connect_async (Gian Mario)
+* Wrap gio.SocketAddressEnumerator.next_async (Gian Mario)
+* Add a missing object gio.InetSocketAddress new in GIO 2.22
+  (Gian Mario)
+* Make cancellable optional for gio.SocketAddressEnumerator.next
+  (Gian Mario)
+* Wrap gio.Socket.condition_wait (Gian Mario)
+* Wrap gio.Socket.condition_check (Gian Mario)
+* Wrap gio.Resolver.lookup_service_finish (Gian Mario)
+* Wrap gio.Resolver.lookup_service_async (Gian Mario)
+* Wrap gio.Resolver.lookup_service (Gian Mario)
+* Wrap gio.Resolver.lookup_by_address_async (Gian Mario)
+* Wrap gio.Resolver.lookup_by_name_finish (Gian Mario)
+* Wrap gio.Drive.eject_with_data (Gian Mario)
+* Deprecate old gio.Drive methods (Gian Mario)
+* Wrap gio.Resolver.lookup_by_name (Gian Mario)
+* Make cancellable optional in gio.Resolver.lookup_by_address
+  (Gian Mario)
+* Strip ``g_`` prefix for many other functions (Gian Mario)
+* Strip ``g_`` prefix from InetAddress functions (Gian Mario)
+* Fix function name gio.resolver_get_default (Gian Mario)
+* Wrap gio.FileIOStream.query_info_async (Gian Mario)
+* Register enums and flags in PyGI if needed (Tomeu Vizoso, :bzbug:`603534`)
+* Wrap gio.IOStream.close_async (Gian Mario)
+* Make cancellable optional in GFile.create_readwrite (Gian Mario)
+* Remove a duplicate entry in gio.defs (Gian Mario)
+* Wrap gio.FileInfo.set_modification_time (Gian Mario)
+* Wrap gio.EmblemedIcon.get_emblems (Gian Mario)
+* Update Enums and Flags with new API (Gian Mario)
+* Fix handling of uchar in pyg_value_from_pyobject (Bastian Winkler)
+
+2.21.0 - 2009-12-18
+-------------------
+
+* pygmainloop: fix use of PySignal_WakeUpFD API for nested loops
+  (Philippe Normad, :bzbug:`481569`)
+* Add capabilities to import wrappers from pygi (Simon van der Linden)
+* Move threads_init() function from 'gobject' to 'glib' (Paul)
+* Fix wrong minimum checking in float properties (Paul, :bzbug:`587637`)
+* Wrap new API added in GIO 2.22 (Gian Mario)
+* Fix bad name when rebuilding the unix source module (Gian Mario)
+* Add the missing limit constants from glibconfig.h
+  (Tomeu Vizoso, :bzbug:`603244`)
+* Suppress warnings about format conversion
+  (Simon van der Linden, :bzbug:`603355`)
+* Properly define Connectable as interface type and not object type
+  (Gian Mario)
+* Wrap new API added in GIO-UNIX 2.22 (Gian Mario)
+* Wrap g_find_program_in_path (Gian Mario, :bzbug:`598435`)
+* Add pygi-external.h into Makefile SOURCES (Gian Mario)
+
+2.20.0 - 2009-09-23
+-------------------
+
+* Allow to use automake 1.11 (Paolo Borelli)
+* Specify programming language in .devhelp file (Frédéric Péters)
+* Plug reference leak of GSource in pyg_main_loop_init (Paul)
+* Updated uninstalled.pc file (Brian Cameron)
+
+2.19.0 - 2009-08-10
+-------------------
+
+* Add macros to help with Python list to/from GList/GSList conversions.
+  (John Finlay)
+* GIO docs practically completed (Gian)
+* GFileInfo.list_attributes should accept None/NULL (Gian)
+* Strip out Windows DLL API macros (John Finlay)
+* Document that many functions got moved gobject -> glib (Paul)
+* Allow h2def.py to work when there are tabs or multiple spaces after
+  the struct keyword. (Murray Cumming)
+* Fix build when builddir is not the same as srcdir
+  (Theppitak Karoonboonyanan)
+* Make gio.Emblem constructor new-style (Paul)
+* Cleanup GIO overrides to use Python function/method names (Paul)
+* Make codegen report errors using Python function/method names (Paul)
+* Fix object type in gio.BufferedInputStream_fill_async (Gian)
+* Wrap gio.BufferedInputStream.fill_async (Gian)
+* Add gio.BufferedOutputStream which was forgotten in the types (Gian)
+* Split overrides for gio.MemoryOutputStream (Gian)
+* Wrap gio.memory_input_stream_new_from_data (Gian)
+* Introduces the girepository module from the former PyBank
+  (Simon van der Linden)
+* Add API appeared in 2.20 but not marked as such in gio docs (Gian)
+* Wrap gio.FileOutputStream.query_info_async (Gian)
+* Wrap gio.FileInputStream.query_async (Gian)
+* Install executable codegen parts with executing permissions (Paul)
+* Wrap gio.DataInputStream.read_line_async and read_until_async (Paul)
+* Fix gio.OutputStream.splice_async (Paul)
+* Add GIO 2.20 API and update docs (Gian)
+
+2.18.0 - 2009-05-24
+-------------------
+
+* Improve gio docs with some more classes (Gian)
+* Wrap gio.OutputStream.splice_async() (Gian)
+* Add Python ver into installed libpyglib name (Emilio Pozuelo Monfort)
+* Wrap gio.OutputStream.flush_async() (Gian)
+* Use 'Requires.private' for libffi in '.pc' files (Josselin Mouette)
+* Add wrapper for gio.FileAttributeMatcher (Gian)
+* Mark relevant glib.IOChannel methods as METH_NOARGS (Paul)
+* Retire hand-written ChangeLog; autocreate from Git history (Paul)
+* Wrap gio.InputStream.skip_async() (Gian)
+* Add in codegen -n --namespace option and the code to remove dll
+  API in headers, added documentation (Siavash Safi)
+* Properly mark glib.get_user_special_dir() as a keywords method (Paul)
+
+2.17.0 - 2009-04-30
+-------------------
+
+* Write a good part of the docs for gio (Gian)
+* Wrap g_mount_guess_content_type g_mount_guess_content_type_finish
+  g_mount_guess_content_type_sync (Gian, :bzbug:`580802`)
+* Swap first two arguments of gio.File.query_info_async (Paul, :bzbug:`580490`)
+* Fix a crash in pyg_type_add_interfaces (Paul, :bzbug:`566571`)
+* Remove an empty structure, use sizeof(PyObject)
+  instead (Paul, :bzbug:`560591`)
+* Wrap four g_get_user_*_dir() functions (Paul, :bzbug:`575999`)
+* Remove 'ltihooks.py' as using deprecated Python module (Paul)
+* Code maintenance: add .gitignore files (Paul)
+* CellRendererPixbuf stock-size property has wrong type (Paul, :bzbug:`568499`)
+* Add a doap file after git migration (Johan Dahlin)
+* missing dep on libffi in pygobject-2.0.pc (Götz Waschk, :bzbug:`550231`)
+* g_volume_monitor_tp_new new function, return the singleton object.
+  (Paul, :bzbug:`555613`)
+* Remove a DeprecationWarning under python 2.6 (James Westby, :bzbug:`573753`)
+* several scripts from codegen directory are not distributed
+  (Krzesimir Nowak)
+* g_file_copy_async change argument order to keep it consistent with the
+  other methods (Gian)
+* memory leak in gio.File.copy_async (Paul Pogonyshev, :bzbug:`578870`)
+* g_file_monitor should accept None for cancellable and set the default
+  flag to G_FILE_MONITOR_NONE (Gian)
+* pyg_notify_free needs to ensure it has GIL before calling Py_XDECREF
+  (Jonathan Matthew)
+* Wrap g_file_set_display_name_async (Gian)
+* Add a semi-private method to return the option context C object from
+  an option context wrapper (Tristan Hill)
+* Converting a negative long Python value to a GUINT64 GValue doesn't
+  error out as it should (Gustavo J. A. M. Carneiro, :bzbug:`577999`)
+* Wrap g_file_set_attributes_async and
+  g_file_set_attributes_finish (Gian)
+* g_file_query_filesystem_info_async fix a typo (Gian)
+* Wrap g_file_query_filesystem_info_async (Gian)
+* Add missing g_file_query_filesystem_info_async and
+  g_file_query_filesystem_info_finish (Gian)
+* Wrap g_file_eject_mountable (Gian)
+* g_file_copy callback cannot be optional (Gian)
+* Swap various kwargs names to reflect the code (Gian)
+* Update the address of the FSF (Tobias Mueller, :bzbug:`577134`)
+* Add g_volume_should_automount (Gian)
+* Wrap g_drive_enumerate_identifiers and g_volume_enumerate_identifiers
+  (Gian)
+* Add a couple of convinence functions to convert from/to a python list
+  and an array of strings (Gian)
+* Allow setting pytype wrapper class (Mark Lee, John Ehresman, :bzbug:`559001`)
+* Wrap g_file_enumerator_close_async (Gian Mario Tagliaretti)
+
+2.16.1 - 2009-02-22
+-------------------
+
+* Apply the patch provided by Cygwin Ports maintainer
+  (Paul Pogonyshev, :bzbug:`564018`)
+* Bad -I ordering can break build, patch from [dmacks netspace org]
+  (Gian Mario Tagliaretti, :bzbug:`566737`)
+* Fix keyword list to be in sync with positional arguments
+  (Paul, :bzbug:`566744`)
+* Add a comment explaining why the two for loops for registering
+  interfaces (Gustavo Carneiro)
+* Huge cleanup of GIO overrides (Paul, :bzbug:`566706`)
+* gtk.Buildable interface method override is not recognized
+  (Paul, :bzbug:`566571`)
+* Do not escape the ampersand "&" in entity references. Replace some
+  unusual entity references in the output with their literal values.
+  (Daniel Elstner, :bzbug:`568485`)
+* gio.InputStream.read_async can cause memory corruption.
+  (Paul, :bzbug:`567792`)
+* Inconsistent use of tabs and spaces in pygtk.py (Paul, :bzbug:`569350`)
+* Huge fix of memory leaks in GIO (Paul, Paolo Borelli, Gian, :bzbug:`568427`)
+* non-async functions don't release python locks before calling
+  blocking C functions (Gian, Gustavo, :bzbug:`556250`)
+* Change comment to avoid false positives when grep'ing for deprecated
+  gtk functions (Andre Klapper)
+* ltihooks.py updating license header from GPL to LGPL
+  (James Henstridge)
+
+2.16.0 - 2009-01-04
+-------------------
+
+* gobject.timeout_add_seconds() not found in docs
+  (Paul Pogonyshev, :bzbug:`547119`)
+* _wrap_g_output_stream_write_async not adding a reference to the
+  buffer passed (Paul, :bzbug:`564102`)
+* gio.VolumeMonitor segfaults (Gian Mario Tagliaretti, :bzbug:`555613`)
+* Test if `domain` is not-null before using it to avoids segfaults
+  (Paul, :bzbug:`561826`)
+* g_output_stream_write_all use gsize instead of gssize (Gian)
+* add __repr__ to gio.Drive, gio.Mount and gio.Volume
+  (Paul, :bzbug:`530935`)
+* Missing AC_CONFIG_MACRO_DIR([m4]) (Loïc Minier, :bzbug:`551227`)
+* Make codegen not import when corresponding argument types are not
+  registered (Paul, :bzbug:`551056`)
+* Fix typos breaking compilation (Frederic Peters :bzbug:`551212`)
+* GFile load_contents methods chop data at first \0
+  (Jonathan Matthew, :bzbug:`551059`) 
+
+2.15.4 - 2008-09-03
+-------------------
+
+* Fix typo in GPointer type registration (Loïc Minier,:bzbug:`550463`)
+* support G_TYPE_CLOSURE in codegen (Gian)
+
+2.15.3 - 2008-08-31
+-------------------
+
+* Beginning of porting to 3.0. glib & gobject module ported.
+* Wrap g_app_info_* functions (Gian)
+* Wrap gio.FileAttributeInfo (Gian)
+* Wrap g_vfs_get_supported_uri_schemes (Johan, :bzbug:`545846`)
+* Wrap g_file_info_get_modification_time (Johan, :bzbug:`545861`)
+* Wrap gio.Volume.mount/eject (Johan)
+* Wrap gio.File.move (Johan)
+* Wrap gio.query_writable_namespaces (Gian, :bzbug:`545920`)
+* Separate glib & gobject documentation
+* Wrap GFile.append_to_async (Gian, :bzbug:`545959`)
+* Wrap GFile.create_async (Gian, :bzbug:`546020`)
+* Change return value from 'gboolean' to 'int' and changed semantics
+  to Pythonic (Paul, :bzbug:`544946`)
+* Wrap GFile.replace_async and query_info_async (Gian, :bzbug:`546046`)
+* GIcon and implementations improvements (Paul, :bzbug:`546135`)
+* Improve __repr__ and richcompare for gio classes (Paul)
+* Missing Py_INCREFs for some file async methods (Jonathan Matthew, :bzbug:`546734`)
+* File.copy progress_callback does not work (Paul, :bzbug:`546591`)
+* add File.replace_contents, replace_contents_async, replace_contents_finish.
+  (Jonathan Matthew, :bzbug:`547067`)
+* Add GFile.query_default_handler (Gian)
+* fix docstring line length (Jonathan Matthew, :bzbug:`547134`)
+* improve runtime type wrapper creation (Paul, :bzbug:`547104`)
+* make gio.File more Pythonic (Paul, :bzbug:`546120`)
+* No TypeError raised when type is None (Paul, :bzbug:`540376`)
+* wrap a few memory stream methods (Paul, :bzbug:`547354`)
+* wrap gio.DataInputStream.read_line and ...read_until (Paul, :bzbug:`547484`)
+* wrap four important asynchronous methods in gio.Drive and gio.Mount
+  (Paul, :bzbug:`547495`)
+* gio.InputStream.read() looks broken (Paul, :bzbug:`547494`)
+* wrap g_content_types_get_registered() (Paul, :bzbug:`547088`)
+* cannot create new threads when pygtk is used (Paul, :bzbug:`547633`)
+* an unitialized variable in PyGLib (Paul, :bzbug:`549351`)
+* Constructor of gtk.TreeView raises TypeError when model is None
+  (Paul, :bzbug:`549191`)
+* Fix memory problems reported by valgrind due to invalid tp_basicsize in
+  PyGPropsDescr_Type. (Gustavo, :bzbug:`549945`)
+
+2.15.2 - 2008-07-26
+-------------------
+
+* New module: glib, which contains the parts of the old
+  gobject bindings which are in the glib library.
+  MainLoop/MainContext/Sources/GOption and a few others has now moved.
+* Add a new installed library libpyglib-2.0, which contains the extension
+  API for third-part modules instead of relying on macros which accesses
+  struct fields.
+* Add bindings for gio.File.enumerate_children_async,
+  gio.FileEnumerator.next_files_async, gio.Mount.mount,
+  gio.File.mount_mountable, gio.File.mount_enclosing_volume,
+  gio.File.unmount_mountable, gio.File.copy.
+* Add a new api for mapping a GError domain to an exception and register
+  an exception for GIOError.
+* Remove leading IO_* prefix for the gio flags and register a quark
+  for the domain.
+* Use GSlice in the glib module and bump required version to 2.14.
+
+2.15.1 - 2008-07-15
+-------------------
+
+* Rename pygtk-codegen-2.0 to pygobject-codegen-2.0 to avoid
+  conflicting with PyGTK (Paul Pogonyshev)
+
+2.15.0 - 2008-07-15
+-------------------
+
+* Add GIO bindings (Johan, Mario Tagliaretti, Thomas Leonard)
+* Move codegen from PyGTK (Johan, Paul Pogonyshev, :bzbug:`542821`)
+* Add more variables to the .pc files (Damien Carbery, Paul, 
+  Dan Winship, :bzbug:`486876`)
+* Add pyg_option_group_new to the public API (Johan)
+* Add g_get_application_anme and g_get_progname (Sebastian Rittau)
+* Avoid making wakeups when using Python 2.6 (Johan, Gustavo,
+  Adam Olsen, Josselin Mouette, Philippe Normand, Guido Van Rossum)
+* Only link against libffi when found (Ed Catmur, :bzbug:`496006`)
+* Improve gobject.property (Tomeu Vizoso, :bzbug:`523352`)
+* Improve enum comparision and warnings (Paul, Phil Dumont, :bzbug:`428732`)
+* Many gobject.Source improvements (Bryan Silverthorn)
+* Apply some fixes to make pylint happier (Johan, Simon Schampijer,
+  :bzbug:`523821`)
+* Fix error message in pyg_io_add_watch (Juha Sahkangas)
+* Improve h2def.py (Oliver Crete, Murray Cumming, Lauro Moura)
+
+2.14.2 - 2008-05-23
+-------------------
+
+* Allow gobject.property work with subclasses. (:bzbug:`523352`, Tomeu Vizoso)
+* Unbreak Source.prepare (:bzbug:`523075`, Bryan Silverthorn)
+* Never override customly set 'tp_new' and 'tp_alloc' (Paul Pogonyshev)
+* Don't link against libffi if we cannot find libffi
+  on the system. (:bzbug:`496006`, Ed Catmur)
+* Dist .m4 files. (:bzbug:`496011`, Ed Catmur)
+* Don't return NULL after warning of enum comparsion
+  (:bzbug:`519631`, Paul Pogonyshev)
+
+2.14.1 - 2008-01-03
+-------------------
+
+* Avoid wakeups when using Python trunk (Johan Dahlin, :bzbug:`481569`)
+* Add an uninstalled.pc (Damien Carbery, :bzbug:`486876`)
+
+2.14.0 - 2007-09-16
+-------------------
+
+* Fix a Python 2.6 deprecation warning (Johannes Hölzl, :bzbug:`342948`)
+* Wrap g_timeout_add_seconds, when compiling with glib 2.14 (Gustavo)
+* Always fully initialize the PyGObject (Ed Catmur, :bzbug:`466082`)
+* Fix compilation in Solaris, again (:bzbug:`339924`, Gustavo)
+* Fix check for default value in boolean type (Marco Giusti, :bzbug:`470230`)
+* Fix new style properties with subclasses (Johan Dahlin, :bzbug:`470718`)
+* Docs generation fixes (John Finlay)
+
+2.13.2 - 2007-07-07
+-------------------
+
+* Fix build on opensolaris (Damien Carbery, :bzbug:`339924`)
+* Proxy GOption exceptions from Python to C (Johannes Hölzl, :bzbug:`342948`)
+* Support G_TYPE_VALUE boxed args/signals
+  (Ed Catmur, Carlos Martin, :bzbug:`351072`)
+* pyg_error_exception_check bug fix (Sebastian Granjoux, :bzbug:`449879`)
+* Toggle references bug fix (:bzbug:`447271`, Gustavo Carneiro)
+* use python-config to get python includes (:bzbug:`448173`, Sebastien Bacher)
+* Support GObject properties in new properties API (Gustavo)
+* generate-constants fixes (Muntyan)
+* Allow running autogen.sh from outside $srcdir (Muntyan)
+
+2.13.1 - 2007-05-02
+-------------------
+
+* Generic CClosure marshaller using libffi (Johan, :bzbug:`353816`)
+* Uninstalled .pc file (Damien Carbery, :bzbug:`385129`)
+* Fix leak in GFlags handling (Daniel Berrange, :bzbug:`428726`)
+* Use dirname in autogen (Loïc Minier, :bzbug:`409234`)
+* Treat None in a GValueArray as pointer/NULL (Ed Catmur, :bzbug:`352209`)
+* Toggle reference bug fix in tp_setattro (Gustavo, :bzbug:`434659`)
+* Add a simplified helper for creating properties (Johan, Gustavo, :bzbug:`338089`)
+* Avoid throwing an exception in GValue converter (James Livingstone,
+  Ed Catmur, :bzbug:`374653`)
+* Build fix in .pc file (Luca Ferretti, :bzbug:`435132`)
+
+2.13.0 - 2007-04-23
+-------------------
+
+* Release the GIL in g_object_set_property (Edward Hervey, :bzbug:`395048`)
+* Break PyGObject<->GObject reference cycle (Gustavo Carneiro, :bzbug:`320428`)
+* use static inline functions for init_pygobject (Gustavo, :bzbug:`419379`)
+* Wrap g_set_application_name, g_set_prgname
+  (Havoc Pennington, :bzbug:`415853`)
+* New pyg_gerror_exception_check API (Gustavo, :bzbug:`425242`)
+* New API to get/set multiple properties
+  (Gian Mario Tagliaretti, :bzbug:`403212`)
+* Misc. bug fixes.
+
+2.12.3 - 2006-11-18
+-------------------
+
+* distutils build fixes (Cedric)
+* documentation updates (John)
+* gobject.handler_block_by_func and friends now accept methods 
+  (Johan, Dima, :bzbug:`375589`)
+* avoid truncating of gparamspec (Yevgen Muntyan, :bzbug:`353943`)
+* set __module__ on gobject derived types (Johan, 
+  Osmo Salomaa, :bzbug:`376099`)
+* Ensure exceptions are raised on errors in gobject.OptionGroup
+  (Johan, Laszlo Pandy, :bzbug:`364576`
+
+2.12.2 - 2006-10-03
+-------------------
+
+* Make PyGObject 64-bit safe for Python 2.5 (Gustavo)
+* All headers are now LGPL and not GPL (Johan)
+* Remove a couple of GCC warnings (Gustavo)
+* Revive distutils support (Cedric Gustin)
+* Emission hook reference count bugfix (Gustavo)
+* MSVC/ANSI C compilation fix (John Ehresman)
+* Bump Ctrl-C timeout handler from 100ms to 1000 (Johan)
+
+2.12.1 - 2006-09-04
+-------------------
+
+* Corrected version check (Sebastian Dröge, :bzbug:`354364`)
+
+2.12.0 - 2006-09-04
+-------------------
+
+* Install the html files even when using --disable-docs (:bzbug:`353159`, Johan,
+  Matthias Clasen)
+
+2.11.4 - 2006-08-27
+-------------------
+
+* Include pre-generated html docs in the tarball  (Johan)
+* Fix bug in do_set_property called from constructor (Gustavo, :bzbug:`353039`)
+* Fix type registration involving interfaces with signals and/or
+  properties (Gustavo)
+
+2.11.3 - 2006-08-21
+-------------------
+
+* Documentation updates (John)
+* Documentation build fixes (Johan, John, Gian Mario Tagliaretti)
+* PyGObject can now be compiled using a C++ compiler (Murray Cumming)
+* Type registration bug fix (Gustavo)
+
+2.11.2 - 2006-08-08
+-------------------
+
+* Add fixxref.py from PyGTK (Johan)
+* Fix parallel build (:bzbug:`350225`, Ed Catmur)
+
+2.11.1 - 2006-08-04
+-------------------
+
+* Add John Finlay's reference manual (Johan, John)
+* Fix GOption mem leak (Gustavo)
+* Infrastructure for lazy type registration (Johan)
+* Enum/Flags fixes (Gustavo, John)
+* Eliminate some GCC warnings (Johan)
+
+2.11.0 - 2006-07-12
+-------------------
+
+* Add GOption support (:bzbug:`163645`, Johannes Hölzl)
+* GObject metaclass converted to Python code (Gustavo)
+* Register GType constants from Python-land (Johan)
+* Distutils updates (John Ehresman, Cedric Gustin)
+* Add support for signal emission hooks (:bzbug:`154845`, Johan)
+* g_spawn_close_pid support (Gustavo)
+* Add new APIs do add or disable log redirections (Muntyan, :bzbug:`323786`)
+* "sub-sub-type" bug fixed (Gustavo)
+* Coverity report code fixes (Johan)
+* Support retrieving signal and property info from interfaces (Finlay)
+* Support parameters of type G_TYPE_GSTRING in signals (Gustavo)
+* Wrap a few g_filename_* APIs (Gustavo)
+
+2.10.1 - 2006-04-11
+-------------------
+
+* uint64 property bug fix (Andy Wingo)
+* Hard code path to 2.0 (Gustavo)
+* Allow only tuples and lists in strv to value (Gustavo)
+* Include dsextras.py in the dist (Johan)
+
+2.10.0 - 2006-03-13
+-------------------
+
+* enum/leak fix (Michael Smith)
+
+2.9.1 - 2006-01-16
+------------------
+
+2.9.0 - 2006-01-16
+------------------
+
+* Signal accumulator support (Gustavo)
+* GObject doc string generation improvements (Gustavo)
+* Discover GInterfaces in runtime (Gustavo)
+* Warn if return value in iowatch callback is not bool (Gustavo)
+* Convert string arrays properly (Christopher Aillon)
+
+2.8.0 - 2006-01-09
+------------------
+
+* Initial release, split of from PyGTK.
+* Updates for Python 2.5's richcompare (Manish Yosh)
+* PyFlags bug fixes (Gustavo)
+* Fix leak in pygobject_new_with_interfaces (Johan)
+* Undeprecate gobject.type_register (Johan)
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644 (file)
index 0000000..8c50c5e
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,42 @@
+Metadata-Version: 1.2
+Name: PyGObject
+Version: 3.30.1
+Summary: Python bindings for GObject Introspection
+Home-page: https://pygobject.readthedocs.io
+Author: James Henstridge
+Author-email: james@daa.com.au
+Maintainer: Simon Feltman
+Maintainer-email: sfeltman@src.gnome.org
+License: GNU LGPL
+Description: .. image:: https://pygobject.readthedocs.io/en/latest/_images/pygobject.svg
+           :align: center
+           :width: 400px
+           :height: 98px
+        
+        |
+        
+        **PyGObject** is a Python package which provides bindings for `GObject
+        <https://developer.gnome.org/gobject/stable/>`__ based libraries such as `GTK+
+        <https://www.gtk.org/>`__, `GStreamer <https://gstreamer.freedesktop.org/>`__,
+        `WebKitGTK+ <https://webkitgtk.org/>`__, `GLib
+        <https://developer.gnome.org/glib/stable/>`__, `GIO
+        <https://developer.gnome.org/gio/stable/>`__ and many more.
+        
+        It supports Linux, Windows and macOS and works with **Python 2.7+**, **Python
+        3.5+**, **PyPy** and **PyPy3**. PyGObject, including this documentation, is
+        licensed under the **LGPLv2.1+**.
+        
+        
+        ----
+        
+        For more information visit https://pygobject.readthedocs.io
+        
+Platform: POSIX, Windows
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/PKG-INFO.in b/PKG-INFO.in
new file mode 100644 (file)
index 0000000..c357cc3
--- /dev/null
@@ -0,0 +1,20 @@
+Metadata-Version: 1.0
+Name: PyGObject
+Version: @VERSION@
+Summary: Python bindings for GObject Introspection
+Home-page: https://pygobject.readthedocs.io
+Author: James Henstridge
+Author-email: james@daa.com.au
+Maintainer: Simon Feltman
+Maintainer-email: sfeltman@src.gnome.org
+License: GNU LGPL
+Description: Python bindings for GObject Introspection
+Platform: POSIX, Windows
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/PyGObject.egg-info/PKG-INFO b/PyGObject.egg-info/PKG-INFO
new file mode 100644 (file)
index 0000000..8c50c5e
--- /dev/null
@@ -0,0 +1,42 @@
+Metadata-Version: 1.2
+Name: PyGObject
+Version: 3.30.1
+Summary: Python bindings for GObject Introspection
+Home-page: https://pygobject.readthedocs.io
+Author: James Henstridge
+Author-email: james@daa.com.au
+Maintainer: Simon Feltman
+Maintainer-email: sfeltman@src.gnome.org
+License: GNU LGPL
+Description: .. image:: https://pygobject.readthedocs.io/en/latest/_images/pygobject.svg
+           :align: center
+           :width: 400px
+           :height: 98px
+        
+        |
+        
+        **PyGObject** is a Python package which provides bindings for `GObject
+        <https://developer.gnome.org/gobject/stable/>`__ based libraries such as `GTK+
+        <https://www.gtk.org/>`__, `GStreamer <https://gstreamer.freedesktop.org/>`__,
+        `WebKitGTK+ <https://webkitgtk.org/>`__, `GLib
+        <https://developer.gnome.org/glib/stable/>`__, `GIO
+        <https://developer.gnome.org/gio/stable/>`__ and many more.
+        
+        It supports Linux, Windows and macOS and works with **Python 2.7+**, **Python
+        3.5+**, **PyPy** and **PyPy3**. PyGObject, including this documentation, is
+        licensed under the **LGPLv2.1+**.
+        
+        
+        ----
+        
+        For more information visit https://pygobject.readthedocs.io
+        
+Platform: POSIX, Windows
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/PyGObject.egg-info/SOURCES.txt b/PyGObject.egg-info/SOURCES.txt
new file mode 100644 (file)
index 0000000..ae9b52d
--- /dev/null
@@ -0,0 +1,317 @@
+.gitlab-ci.yml
+COPYING
+MANIFEST.in
+NEWS
+PKG-INFO.in
+README.rst
+meson.build
+meson_options.txt
+pygobject-3.0.pc.in
+pygobject.doap
+setup.cfg
+setup.py
+.gitlab-ci/Dockerfile
+.gitlab-ci/Dockerfile.old
+.gitlab-ci/README.rst
+.gitlab-ci/coverage-docker.sh
+.gitlab-ci/fixup-cov-paths.py
+.gitlab-ci/run-docker-old.sh
+.gitlab-ci/run-docker.sh
+.gitlab-ci/test-docker-old.sh
+.gitlab-ci/test-docker.sh
+.gitlab-ci/test-flatpak.sh
+.gitlab-ci/test-msys2.sh
+PyGObject.egg-info/PKG-INFO
+PyGObject.egg-info/SOURCES.txt
+PyGObject.egg-info/dependency_links.txt
+PyGObject.egg-info/not-zip-safe
+PyGObject.egg-info/requires.txt
+PyGObject.egg-info/top_level.txt
+docs/Makefile
+docs/bugs_repo.rst
+docs/changelog.rst
+docs/conf.py
+docs/contact.rst
+docs/extra.css
+docs/further.rst
+docs/getting_started.rst
+docs/icons.rst
+docs/index.rst
+docs/maintguide.rst
+docs/packagingguide.rst
+docs/devguide/building_testing.rst
+docs/devguide/dev_environ.rst
+docs/devguide/index.rst
+docs/devguide/override_guidelines.rst
+docs/devguide/overview.rst
+docs/devguide/style_guide.rst
+docs/guide/cairo_integration.rst
+docs/guide/debug_profile.rst
+docs/guide/deploy.rst
+docs/guide/faq.rst
+docs/guide/index.rst
+docs/guide/porting.rst
+docs/guide/testing.rst
+docs/guide/threading.rst
+docs/guide/api/api.rst
+docs/guide/api/basic_types.rst
+docs/guide/api/error_handling.rst
+docs/guide/api/flags_enums.rst
+docs/guide/api/gobject.rst
+docs/guide/api/index.rst
+docs/guide/api/properties.rst
+docs/guide/api/signals.rst
+docs/guide/code/cairo-demo.py
+docs/guide/images/cairo_integration.png
+docs/images/LICENSE
+docs/images/favicon.ico
+docs/images/logo.svg
+docs/images/overview.dia
+docs/images/overview.svg
+docs/images/pygobject-small.svg
+docs/images/pygobject.svg
+docs/images/start_linux.png
+docs/images/start_macos.png
+docs/images/start_windows.png
+examples/cairo-demo.py
+examples/option.py
+examples/properties.py
+examples/signal.py
+examples/demo/demo.py
+examples/demo/demos/__init__.py
+examples/demo/demos/appwindow.py
+examples/demo/demos/assistant.py
+examples/demo/demos/builder.py
+examples/demo/demos/button_box.py
+examples/demo/demos/clipboard.py
+examples/demo/demos/colorselector.py
+examples/demo/demos/combobox.py
+examples/demo/demos/dialogs.py
+examples/demo/demos/drawingarea.py
+examples/demo/demos/expander.py
+examples/demo/demos/flowbox.py
+examples/demo/demos/images.py
+examples/demo/demos/infobars.py
+examples/demo/demos/links.py
+examples/demo/demos/menus.py
+examples/demo/demos/pickers.py
+examples/demo/demos/pixbuf.py
+examples/demo/demos/printing.py
+examples/demo/demos/rotatedtext.py
+examples/demo/demos/test.py
+examples/demo/demos/Css/__init__.py
+examples/demo/demos/Css/css_accordion.py
+examples/demo/demos/Css/css_basics.py
+examples/demo/demos/Css/css_multiplebgs.py
+examples/demo/demos/Entry/__init__.py
+examples/demo/demos/Entry/entry_buffer.py
+examples/demo/demos/Entry/entry_completion.py
+examples/demo/demos/Entry/search_entry.py
+examples/demo/demos/IconView/__init__.py
+examples/demo/demos/IconView/iconviewbasics.py
+examples/demo/demos/IconView/iconviewedit.py
+examples/demo/demos/TreeView/__init__.py
+examples/demo/demos/TreeView/liststore.py
+examples/demo/demos/TreeView/treemodel_filelist.py
+examples/demo/demos/TreeView/treemodel_filetree.py
+examples/demo/demos/TreeView/treemodel_large.py
+examples/demo/demos/data/alphatest.png
+examples/demo/demos/data/apple-red.png
+examples/demo/demos/data/background.jpg
+examples/demo/demos/data/brick.png
+examples/demo/demos/data/brick2.png
+examples/demo/demos/data/css_accordion.css
+examples/demo/demos/data/css_basics.css
+examples/demo/demos/data/css_multiplebgs.css
+examples/demo/demos/data/cssview.css
+examples/demo/demos/data/demo.gresource
+examples/demo/demos/data/demo.gresource.xml
+examples/demo/demos/data/demo.ui
+examples/demo/demos/data/floppybuddy.gif
+examples/demo/demos/data/gnome-applets.png
+examples/demo/demos/data/gnome-calendar.png
+examples/demo/demos/data/gnome-foot.png
+examples/demo/demos/data/gnome-fs-directory.png
+examples/demo/demos/data/gnome-fs-regular.png
+examples/demo/demos/data/gnome-gimp.png
+examples/demo/demos/data/gnome-gmush.png
+examples/demo/demos/data/gnome-gsame.png
+examples/demo/demos/data/gnu-keys.png
+examples/demo/demos/data/gtk-logo-rgb.gif
+examples/demo/demos/data/reset.css
+gi/__init__.py
+gi/_compat.py
+gi/_constants.py
+gi/_error.py
+gi/_gtktemplate.py
+gi/_option.py
+gi/_ossighelper.py
+gi/_propertyhelper.py
+gi/_signalhelper.py
+gi/docstring.py
+gi/gimodule.c
+gi/gimodule.h
+gi/importer.py
+gi/meson.build
+gi/module.py
+gi/pygboxed.c
+gi/pygboxed.h
+gi/pygenum.c
+gi/pygenum.h
+gi/pygflags.c
+gi/pygflags.h
+gi/pygi-argument.c
+gi/pygi-argument.h
+gi/pygi-array.c
+gi/pygi-array.h
+gi/pygi-basictype.c
+gi/pygi-basictype.h
+gi/pygi-boxed.c
+gi/pygi-boxed.h
+gi/pygi-cache.c
+gi/pygi-cache.h
+gi/pygi-ccallback.c
+gi/pygi-ccallback.h
+gi/pygi-closure.c
+gi/pygi-closure.h
+gi/pygi-enum-marshal.c
+gi/pygi-enum-marshal.h
+gi/pygi-error.c
+gi/pygi-error.h
+gi/pygi-foreign-api.h
+gi/pygi-foreign-cairo.c
+gi/pygi-foreign.c
+gi/pygi-foreign.h
+gi/pygi-hashtable.c
+gi/pygi-hashtable.h
+gi/pygi-info.c
+gi/pygi-info.h
+gi/pygi-invoke-state-struct.h
+gi/pygi-invoke.c
+gi/pygi-invoke.h
+gi/pygi-list.c
+gi/pygi-list.h
+gi/pygi-marshal-cleanup.c
+gi/pygi-marshal-cleanup.h
+gi/pygi-object.c
+gi/pygi-object.h
+gi/pygi-property.c
+gi/pygi-property.h
+gi/pygi-python-compat.h
+gi/pygi-repository.c
+gi/pygi-repository.h
+gi/pygi-resulttuple.c
+gi/pygi-resulttuple.h
+gi/pygi-signal-closure.c
+gi/pygi-signal-closure.h
+gi/pygi-source.c
+gi/pygi-source.h
+gi/pygi-struct-marshal.c
+gi/pygi-struct-marshal.h
+gi/pygi-struct.c
+gi/pygi-struct.h
+gi/pygi-type.c
+gi/pygi-type.h
+gi/pygi-util.c
+gi/pygi-util.h
+gi/pygi-value.c
+gi/pygi-value.h
+gi/pyginterface.c
+gi/pyginterface.h
+gi/pygobject-internal.h
+gi/pygobject-object.c
+gi/pygobject-object.h
+gi/pygobject.h
+gi/pygoptioncontext.c
+gi/pygoptioncontext.h
+gi/pygoptiongroup.c
+gi/pygoptiongroup.h
+gi/pygparamspec.c
+gi/pygparamspec.h
+gi/pygpointer.c
+gi/pygpointer.h
+gi/pygspawn.c
+gi/pygspawn.h
+gi/pygtkcompat.py
+gi/types.py
+gi/overrides/GIMarshallingTests.py
+gi/overrides/GLib.py
+gi/overrides/GObject.py
+gi/overrides/Gdk.py
+gi/overrides/GdkPixbuf.py
+gi/overrides/Gio.py
+gi/overrides/Gtk.py
+gi/overrides/Pango.py
+gi/overrides/__init__.py
+gi/overrides/keysyms.py
+gi/overrides/meson.build
+gi/repository/__init__.py
+gi/repository/meson.build
+pygtkcompat/__init__.py
+pygtkcompat/generictreemodel.py
+pygtkcompat/meson.build
+pygtkcompat/pygtkcompat.py
+subprojects/glib.wrap
+subprojects/gobject-introspection.wrap
+subprojects/libffi.wrap
+subprojects/pycairo.wrap
+tests/__init__.py
+tests/conftest.py
+tests/gimarshallingtestsextra.c
+tests/gimarshallingtestsextra.h
+tests/helper.py
+tests/meson.build
+tests/org.gnome.test.gschema.xml
+tests/regressextra.c
+tests/regressextra.h
+tests/runtests.py
+tests/test-floating.c
+tests/test-floating.h
+tests/test-thread.c
+tests/test-thread.h
+tests/test-unknown.c
+tests/test-unknown.h
+tests/test_atoms.py
+tests/test_cairo.py
+tests/test_docstring.py
+tests/test_error.py
+tests/test_everything.py
+tests/test_fields.py
+tests/test_gdbus.py
+tests/test_generictreemodel.py
+tests/test_gi.py
+tests/test_gio.py
+tests/test_glib.py
+tests/test_gobject.py
+tests/test_gtk_template.py
+tests/test_gtype.py
+tests/test_import_machinery.py
+tests/test_interface.py
+tests/test_internal_api.py
+tests/test_iochannel.py
+tests/test_mainloop.py
+tests/test_object_marshaling.py
+tests/test_option.py
+tests/test_ossig.py
+tests/test_overrides_gdk.py
+tests/test_overrides_gdkpixbuf.py
+tests/test_overrides_gio.py
+tests/test_overrides_glib.py
+tests/test_overrides_gtk.py
+tests/test_overrides_pango.py
+tests/test_properties.py
+tests/test_pycapi.py
+tests/test_pygtkcompat.py
+tests/test_repository.py
+tests/test_resulttuple.py
+tests/test_signal.py
+tests/test_source.py
+tests/test_subprocess.py
+tests/test_thread.py
+tests/test_typeclass.py
+tests/test_unknown.py
+tests/testhelpermodule.c
+tests/valgrind.supp
+tests/gi/overrides/Regress.py
+tests/gi/overrides/__init__.py
+tools/pygi-convert.sh
\ No newline at end of file
diff --git a/PyGObject.egg-info/dependency_links.txt b/PyGObject.egg-info/dependency_links.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/PyGObject.egg-info/not-zip-safe b/PyGObject.egg-info/not-zip-safe
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/PyGObject.egg-info/requires.txt b/PyGObject.egg-info/requires.txt
new file mode 100644 (file)
index 0000000..2d8fd4a
--- /dev/null
@@ -0,0 +1 @@
+pycairo>=1.11.1
diff --git a/PyGObject.egg-info/top_level.txt b/PyGObject.egg-info/top_level.txt
new file mode 100644 (file)
index 0000000..25cbcf7
--- /dev/null
@@ -0,0 +1,2 @@
+gi
+pygtkcompat
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..b08d664
--- /dev/null
@@ -0,0 +1,22 @@
+.. image:: https://pygobject.readthedocs.io/en/latest/_images/pygobject.svg
+   :align: center
+   :width: 400px
+   :height: 98px
+
+|
+
+**PyGObject** is a Python package which provides bindings for `GObject
+<https://developer.gnome.org/gobject/stable/>`__ based libraries such as `GTK+
+<https://www.gtk.org/>`__, `GStreamer <https://gstreamer.freedesktop.org/>`__,
+`WebKitGTK+ <https://webkitgtk.org/>`__, `GLib
+<https://developer.gnome.org/glib/stable/>`__, `GIO
+<https://developer.gnome.org/gio/stable/>`__ and many more.
+
+It supports Linux, Windows and macOS and works with **Python 2.7+**, **Python
+3.5+**, **PyPy** and **PyPy3**. PyGObject, including this documentation, is
+licensed under the **LGPLv2.1+**.
+
+
+----
+
+For more information visit https://pygobject.readthedocs.io
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644 (file)
index 0000000..91dc99c
--- /dev/null
@@ -0,0 +1,16 @@
+DIAS = $(wildcard images/*.dia)
+DIA_SVGS = $(patsubst %.dia,%.svg,$(DIAS))
+
+all: _build
+
+images/%.svg: images/%.dia
+       dia $< --export=$@ --filter=dia-svg
+
+_build: Makefile *.rst devguide/*.rst guide/*.rst conf.py images/*.png $(DIA_SVGS) ../README.rst ../NEWS
+       sphinx-build -b html . _build
+
+linkcheck:
+       sphinx-build -b linkcheck -n . _build
+
+clean:
+       rm -R _build
diff --git a/docs/bugs_repo.rst b/docs/bugs_repo.rst
new file mode 100644 (file)
index 0000000..830c23f
--- /dev/null
@@ -0,0 +1,31 @@
+==========================
+Bug Tracker / Git / Source
+==========================
+
+.. include:: icons.rst
+
+|bug-logo| Bug Tracker
+----------------------
+
+We use the GNOME GitLab issue tracker:
+
+* List of existing issues: https://gitlab.gnome.org/GNOME/pygobject/issues
+* Create a new issue: https://gitlab.gnome.org/GNOME/pygobject/issues/new
+
+
+|git-logo| Git Repo
+-------------------
+
+PyGObject uses `Git <https://git-scm.com/>`_ for source control and the git
+repo is hosted on the `GNOME Gitlab instance <https://gitlab.gnome.org/>`__:
+
+* https://gitlab.gnome.org/GNOME/pygobject
+* ``git clone https://gitlab.gnome.org/GNOME/pygobject.git``
+
+
+|source-logo| Source Tarballs
+-----------------------------
+
+Release tarballs of all releases can be found on the GNOME server:
+
+https://ftp.gnome.org/pub/GNOME/sources/pygobject
diff --git a/docs/changelog.rst b/docs/changelog.rst
new file mode 100644 (file)
index 0000000..0e78f5e
--- /dev/null
@@ -0,0 +1,11 @@
+Changelog
+=========
+
+Versions with an odd minor version are unstable releases (e.g. 3.27.x) while
+versions with even minor version are stable releases (e.g. 3.28.x). This list
+is sorted by release date.
+
+For more details see the GIT log:
+https://gitlab.gnome.org/GNOME/pygobject/commits/master
+
+.. include:: ../NEWS
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644 (file)
index 0000000..9609974
--- /dev/null
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+
+extensions = [
+    'sphinx.ext.todo',
+    'sphinx.ext.intersphinx',
+    'sphinx.ext.extlinks',
+]
+
+intersphinx_mapping = {
+    'gtk': ('https://lazka.github.io/pgi-docs/Gtk-3.0', None),
+    'gobject': ('https://lazka.github.io/pgi-docs/GObject-2.0', None),
+    'glib': ('https://lazka.github.io/pgi-docs/GLib-2.0', None),
+    'gdk': ('https://lazka.github.io/pgi-docs/Gdk-3.0', None),
+    'gio': ('https://lazka.github.io/pgi-docs/Gio-2.0', None),
+    'python2': ('https://docs.python.org/2.7', None),
+    'python3': ('https://docs.python.org/3', None),
+    'cairo': ('https://pycairo.readthedocs.io/en/latest', None),
+}
+
+source_suffix = '.rst'
+master_doc = 'index'
+exclude_patterns = ['_build', 'README.rst']
+
+pygments_style = 'tango'
+html_theme = 'sphinx_rtd_theme'
+html_show_copyright = False
+html_favicon = "images/favicon.ico"
+project = "PyGObject"
+html_title = project
+
+html_context = {
+    'extra_css_files': [
+        'https://quodlibet.github.io/fonts/font-mfizz.css',
+        '_static/extra.css',
+    ],
+    "display_gitlab": True,
+    "gitlab_user": "GNOME",
+    "gitlab_repo": "pygobject",
+    "gitlab_version": "master",
+    "conf_py_path": "/docs/",
+    "gitlab_host": "gitlab.gnome.org",
+}
+
+html_static_path = [
+    "extra.css",
+    "images/pygobject-small.svg",
+]
+
+html_theme_options = {
+    "display_version": False,
+}
+
+extlinks = {
+    'bzbug': ('https://bugzilla.gnome.org/show_bug.cgi?id=%s', 'bz#'),
+    'issue': ('https://gitlab.gnome.org/GNOME/pygobject/issues/%s', '#'),
+    'commit': ('https://gitlab.gnome.org/GNOME/pygobject/commit/%s', ''),
+    'mr': (
+        'https://gitlab.gnome.org/GNOME/pygobject/merge_requests/%s', '!'),
+    'user': ('https://gitlab.gnome.org/%s', ''),
+}
+
+suppress_warnings = ["image.nonlocal_uri"]
diff --git a/docs/contact.rst b/docs/contact.rst
new file mode 100644 (file)
index 0000000..211de81
--- /dev/null
@@ -0,0 +1,26 @@
+=======
+Contact
+=======
+
+Issue Tracker
+    If you have any questions, problems or want to give feedback please file
+    an issue in our `issue tracker
+    <https://gitlab.gnome.org/GNOME/pygobject/issues>`__.
+
+IRC
+    For chatting with the community we have an IRC channel named
+    ``#python`` on ``irc.gnome.org``. Logs for the channel are available at
+    https://quodlibet.duckdns.org/irc/pygobject.
+
+Mailing List
+    If you want to start a discussion with the Python community that is part
+    of the GNOME project use the mailing list at
+    https://mail.gnome.org/mailman/listinfo/python-hackers-list.
+
+StackOverflow / StackExchange
+    If you have technical questions about PyGObject you can find answers on
+    `Stack Overflow <https://stackoverflow.com/questions/tagged/pygobject>`__.
+    When asking there please use the tag `PyGObject`.
+
+If you are unsure which communication channel to use **please use the issue
+tracker**.
diff --git a/docs/devguide/building_testing.rst b/docs/devguide/building_testing.rst
new file mode 100644 (file)
index 0000000..4f4c075
--- /dev/null
@@ -0,0 +1,54 @@
+==================
+Building & Testing
+==================
+
+To pass extra arguments to pytest you can set "PYTEST_ADDOPTS":
+
+.. code:: shell
+
+    # don't hide stdout
+    export PYTEST_ADDOPTS="-s"
+    python3 setup.py test
+
+
+Using Setuptools
+----------------
+
+.. code:: shell
+
+    # Build in-tree
+    python3 setup.py build_ext --inplace
+
+    # Build in-tree including tests
+    python3 setup.py build_tests
+
+    # Executing some code after the build
+    PYTHONPATH=. python3 foo.py
+
+    # Running tests
+    python3 setup.py test
+
+    # To test only a specific file/class/function::
+    TEST_NAMES=test_gi python3 python3 setup.py test
+    TEST_NAMES=test_gi.TestUtf8 python3 setup.py test
+    TEST_NAMES=test_gi.TestUtf8.test_utf8_full_return python3 setup.py test
+
+    # To display stdout and pytest verbose output:
+    PYGI_TEST_VERBOSE=yes python3 setup.py test
+    # or:
+    python3 setup.py test -s
+
+    # using pytest directly
+    py.test-3 tests/test_gi.py
+
+    # Running flake8 tests
+    python3 setup.py quality
+
+    # Run under gdb
+    python3 setup.py test --gdb
+
+    # Run under valgrind
+    python3 setup.py test --valgrind --valgrind-log-file=valgrind.log
+
+    # Create a release tarball for GNOME
+    python3 setup.py sdist_gnome
diff --git a/docs/devguide/dev_environ.rst b/docs/devguide/dev_environ.rst
new file mode 100644 (file)
index 0000000..db31678
--- /dev/null
@@ -0,0 +1,267 @@
+.. include:: ../icons.rst
+
+.. _devenv:
+
+##################################
+Creating a Development Environment
+##################################
+
+This describes how to setup a development environment for working on a project
+that uses PyGObject, or for working on PyGObject itself. Please follow the
+instructions on ":ref:`gettingstarted`" first, as they are a pre-requirement.
+
+.. _pipenv-setup:
+
+************
+Pipenv Setup
+************
+
+.. _install-dependencies:
+
+Install Dependencies
+====================
+In order to compile Python and pip install pygobject, dependencies are need for
+your operating system.
+
+=========================================== ======================================== ==============================================
+|ubuntu-logo| :ref:`Ubuntu <ubuntu-dep>`    |fedora-logo| :ref:`Fedora <fedora-dep>` |arch-logo| :ref:`Arch Linux <arch-dep>`
+|windows-logo| :ref:`Windows <windows-dep>` |macosx-logo| :ref:`macOS <macosx-dep>`  |opensuse-logo| :ref:`openSUSE <opensuse-dep>`
+=========================================== ======================================== ==============================================
+
+.. _ubuntu-dep:
+
+|ubuntu-logo| Ubuntu / |debian-logo| Debian
+-------------------------------------------
+
+.. code:: console
+
+    sudo apt-get install -y python3-venv python3-wheel python3-dev
+    sudo apt-get install -y libgirepository1.0-dev build-essential \
+      libbz2-dev libreadline-dev libssl-dev zlib1g-dev libsqlite3-dev wget \
+      curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libcairo2-dev
+
+
+.. _fedora-dep:
+
+|fedora-logo| Fedora
+--------------------
+
+.. code:: console
+
+    sudo dnf install -y python3-venv python3-wheel
+    sudo dnf install -y gcc zlib-devel bzip2 bzip2-devel readline-devel \
+      sqlite sqlite-devel openssl-devel tk-devel git python3-cairo-devel \
+      cairo-gobject-devel gobject-introspection-devel
+
+
+.. _arch-dep:
+
+|arch-logo| Arch Linux
+----------------------
+
+.. code:: console
+
+    sudo pacman -S --noconfirm python-virtualenv python-wheel
+    sudo pacman -S --noconfirm base-devel openssl zlib git gobject-introspection
+
+
+.. _opensuse-dep:
+
+|opensuse-logo| openSUSE
+------------------------
+
+.. code:: console
+
+    sudo zypper install -y python3-venv python3-wheel gobject-introspection \
+      python3-cairo-devel openssl zlib git
+    sudo zypper install --type pattern devel_basis
+
+
+.. _windows-dep:
+
+|windows-logo| Windows
+----------------------
+
+.. code:: console
+
+    pacman -S --needed --noconfirm base-devel mingw-w64-i686-toolchain git \
+       mingw-w64-i686-python3 mingw-w64-i686-python3-cairo \
+       mingw-w64-i686-gobject-introspection mingw-w64-i686-libffi
+
+.. _macosx-dep:
+
+|macosx-logo| macOS
+-------------------
+
+No extra dependencies needed.
+
+
+.. _install-pyenv:
+
+Install `pyenv`_
+================
+
+`pyenv`_ lets you easily switch between multiple versions of Python.
+
+============================================= =========================================
+|linux-logo| :ref:`Linux <linux-pyenv>`       |macosx-logo| :ref:`macOS <macosx-pyenv>`
+|windows-logo| :ref:`Windows <windows-pyenv>`
+============================================= =========================================
+
+.. _linux-pyenv:
+
+|linux-logo| Linux
+------------------
+
+.. code:: console
+
+    git clone https://github.com/pyenv/pyenv.git ~/.pyenv
+    echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
+    echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
+    echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.bashrc
+    source ~/.bashrc
+    pyenv install 3.6.4
+
+
+.. _windows-pyenv:
+
+|windows-logo| Windows
+----------------------
+
+TODO: currently no way to install `pyenv`_ in Windows. So we'll use a normal
+`virtualenv`_ instead.
+
+.. code:: console
+
+    virtualenv --python 3 myvenv
+    source myvenv/bin/activate
+
+
+.. _macosx-pyenv:
+
+|macosx-logo| macOS
+-------------------
+
+.. code:: console
+
+    brew install pyenv
+    pyenv install 3.6.4
+
+
+.. _install-pipsi:
+
+Install `pipsi`_
+================
+
+`pipsi`_ is a wrapper around virtualenv and pip which installs
+scripts provided by python packages into separate virtualenvs to shield them
+from your system and each other. We'll use this to install pipenv.
+
+============================================= =========================================
+|linux-logo| :ref:`Linux <linux-pipsi>`       |macosx-logo| :ref:`macOS <macosx-pipsi>`
+|windows-logo| :ref:`Windows <windows-pipsi>`
+============================================= =========================================
+
+.. _linux-pipsi:
+
+|linux-logo| Linux
+------------------
+
+.. code:: console
+
+    curl https://raw.githubusercontent.com/mitsuhiko/pipsi/master/get-pipsi.py | python3 - --src=git+https://github.com/mitsuhiko/pipsi.git\#egg=pipsi
+    echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
+    source ~/.bashrc
+    pipsi install pew
+    pipsi install pipenv
+
+
+.. _windows-pipsi:
+
+|windows-logo| Windows
+----------------------
+
+.. code:: console
+
+    curl https://raw.githubusercontent.com/mitsuhiko/pipsi/master/get-pipsi.py | python3 - --src=git+https://github.com/mitsuhiko/pipsi.git\#egg=pipsi
+
+Add C:/\Users/\.local/\bin to your path via Control Panel->All Control Panel
+Items->System->Advanced System Setttings->Environment Variables
+
+.. code:: console
+
+    pipsi install pew
+    pipsi install pipenv
+
+
+.. _macosx-pipsi:
+
+|macosx-logo| macOS
+-------------------
+
+With homebrew:
+
+.. code:: console
+
+    brew install pipenv
+
+With pipsi:
+
+.. code:: console
+
+    curl https://raw.githubusercontent.com/mitsuhiko/pipsi/master/get-pipsi.py | python3 - --src=git+https://github.com/mitsuhiko/pipsi.git\#egg=pipsi
+    echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
+    source ~/.bashrc
+    pipsi install pew
+    pipsi install pipenv
+
+
+.. _projects-pygobject-dependencies:
+
+************************************
+Projects with PyGObject Dependencies
+************************************
+
+If you are going to work on a project that has PyGObject as a dependency, then
+do the following additional steps:
+
+.. code:: console
+
+    git clone <url/projectname.git>
+    cd projectname
+    pipenv --python 3
+    pipenv install pycairo
+    pipenv install pygobject
+    pipenv shell
+
+
+.. _work-on-pygobject:
+
+*****************
+Work on PyGObject
+*****************
+
+.. _platform-ind-steps:
+
+Platform Independent Steps
+==========================
+
+
+If you are going to work on developing PyGObject itself, then do the following
+additional steps:
+
+.. code:: console
+
+    git clone https://gitlab.gnome.org/GNOME/pygobject.git
+    cd pygobject
+    pipenv --python 3
+    pipenv install pytest
+    pipenv install flake8
+    pipenv install pycairo
+    pipenv shell
+
+
+.. _pyenv: https://github.com/pyenv/pyenv
+.. _pipsi: https://github.com/mitsuhiko/pipsi
+.. _pipenv: https://github.com/pypa/pipenv
+.. _virtualenv: https://www.virtualenv.org
diff --git a/docs/devguide/index.rst b/docs/devguide/index.rst
new file mode 100644 (file)
index 0000000..0458eda
--- /dev/null
@@ -0,0 +1,13 @@
+=================
+Development Guide
+=================
+
+.. toctree::
+    :titlesonly:
+    :maxdepth: 1
+
+    overview
+    dev_environ
+    building_testing
+    style_guide
+    override_guidelines
diff --git a/docs/devguide/override_guidelines.rst b/docs/devguide/override_guidelines.rst
new file mode 100644 (file)
index 0000000..35f3f4b
--- /dev/null
@@ -0,0 +1,90 @@
+==========================
+Python Override Guidelines
+==========================
+
+This document serves as a guide for developers creating new PyGObject
+overrides or modifying existing ones. This document is not intended as hard
+rules as there may always be pragmatic exceptions to what is listed here. It
+is also a good idea to study the `Zen of Python by Tim Peters
+<https://www.python.org/dev/peps/pep-0020/>`__.
+
+In general, overrides should be minimized and preference should always be
+placed on updating the underlying API to be more bindable, adding features to
+GI to support the requirement, or adding mechanical features to PyGObject
+which can apply generically to all overrides (:bzbug:`721226` and
+:bzbug:`640812`).
+
+If a GI feature or more bindable API for a library is in the works, it is a
+good idea to avoid the temptation to add temporary short term workarounds in
+overrides. The reason is this can creaste unnecessary conflicts when the
+bindable API becomes a reality (:bzbug:`707280`).
+
+* Minimize class overrides when possible.
+
+  *Reason*: Class overrides incur a load time performance penalty because
+  they require the classes GType and all of the Python method bindings to be
+  created. See :bzbug:`705810`
+
+* Prefer monkey patching methods on repository classes over inheritance.
+
+  *Reason*: Class overrides add an additional level to the method
+  resolution order (mro) which has a performance penalty. Since overrides are
+  designed for specific repository library APIs, monkey patching is
+  reasonable because it is utilized in a controlled manner by the API
+  designer (as opposed to monkey patching a third-party library which is more
+  fragile).
+
+* Avoid overriding ``__init__``
+  *Reason*: Sub-classing the overridden class then becomes challenging and
+  has the potential to cause bugs (see :bzbug:`711487` and reasoning
+  listed in https://wiki.gnome.org/Projects/PyGObject/InitializerDeprecations).
+
+* Unbindable functions which take variadic arguments are generally ok to add
+  Python implementations, but keep in mind the prior noted guidelines. A lot
+  of times adding bindable versions of the functions to the underlying library
+  which take a list is acceptable. For example: :bzbug:`706119`. Another
+  problem here is if an override is added, then later a bindable version of
+  the API is added which takes a list, there is a good chance we have to live
+  with the override forever which masks a working version implemented by GI.
+
+* Avoid side effects beyond the intended repositories API in function/method
+  overrides.
+
+  *Reason*: This conflates the original API and adds a documentation burden
+  on the override maintainer.
+
+* Don't change function signatures from the original API and don't add default
+  values.
+
+  *Reason*: This turns into a documentation discrepancy between the libraries
+  API and the Python version of the API. Default value work should focus on
+  bug :bzbug:`558620`, not cherry-picking individual Python functions and
+  adding defaults.
+
+* Avoid implicit side effects to the Python standard library (or anywhere).
+
+  * Don't modify or use sys.argv
+
+    *Reason*: sys.argv should only be explicitly controlled by application
+    developers. Otherwise it requires hacks to work around a module modifying
+    or using the developers command line args which they rightfully own.
+
+    .. code:: python
+
+        saved_argv = sys.argv.copy()
+        sys.argv = []
+        from gi.repository import Gtk
+        sys.argv = saved_argv
+
+  * Never set Pythons default encoding.
+
+    *Reason*: Read or watch Ned Batchelders "`Pragmatic Unicode
+    <https://nedbatchelder.com/text/unipain.html>`__"
+
+* For PyGTK compatibility APIs, add them to PyGTKCompat not overrides.
+* Prefer adapter patterns over of inheritance and overrides.
+
+  *Reason*: An adapter allows more flexibility and less dependency on
+  overrides. It allows application developers to use the raw GI API without
+  having to think about if a particular typelibs overrides have been installed
+  or not.
diff --git a/docs/devguide/overview.rst b/docs/devguide/overview.rst
new file mode 100644 (file)
index 0000000..a6b4e6d
--- /dev/null
@@ -0,0 +1,16 @@
+========
+Overview
+========
+
+
+See :doc:`/bugs_repo` for information on where to find the bug tracker and the
+source code.
+
+Continuous Testing
+------------------
+
+The test suite gets regularly run on all supported platforms using
+https://github.com/pygobject/pygobject-ci
+
+There is currently no integration with the git repo for this and the status
+has to be checked manually.
diff --git a/docs/devguide/style_guide.rst b/docs/devguide/style_guide.rst
new file mode 100644 (file)
index 0000000..0cd01fd
--- /dev/null
@@ -0,0 +1,101 @@
+================
+Style Guidelines
+================
+
+Python Code
+-----------
+
+* Generally follow Python's `PEP8
+  <https://www.python.org/dev/peps/pep-0008/>`__ style guidelines. We run the
+  pep8 command to verify this during unittest runs.
+
+* Break up logical blocks of related code with a newline. Specifically add a
+  blank newline after conditional or looping blocks.
+* Don't comment what is obvious. Instead prefer meaningful names of functions
+  and variables:
+
+  .. code:: python
+
+        # Get the functions signal annotations <-- this comment is unnecessary
+        return_type, arg_types = get_signal_annotations(func)
+
+* Use comments to explain non-obvious blocks and conditionals, magic,
+  workarounds (with bug references), or generally complex pieces of code.
+  Good examples:
+
+  .. code:: python
+
+        # If a property was defined with a decorator, it may already have
+        # a name; if it was defined with an assignment (prop = Property(...))
+        # we set the property's name to the member name
+        if not prop.name:
+            prop.name = name
+
+  .. code:: python
+
+        # Python causes MRO's to be calculated starting with the lowest
+        # base class and working towards the descendant, storing the result
+        # in __mro__ at each point. Therefore at this point we know that
+        # we already have our base class MRO's available to us, there is
+        # no need for us to (re)calculate them.
+        if hasattr(base, '__mro__'):
+            bases_of_subclasses += [list(base.__mro__)]
+
+
+Python Doc Strings
+------------------
+
+* Doc strings should generally follow
+  `PEP257 <https://www.python.org/dev/peps/pep-0257/>`__ unless noted here.
+* Use `reStructuredText (resST) <http://sphinx-doc.org/rest.html>`__
+  annotations.
+* Use three double quotes for doc strings (``"""``).
+* Use a brief description on the same line as the triple quote.
+* Include function parameter documentation (including types, returns, and
+  raises) between the brief description and the full description. Use a
+  newline with indentation for the parameters descriptions.
+
+  .. code:: python
+
+        def spam(amount):
+            """Creates a Spam object with the given amount.
+
+            :param int amount:
+                The amount of spam.
+            :returns:
+                A new Spam instance with the given amount set.
+            :rtype: Spam
+            :raises ValueError:
+                If amount is not a numeric type.
+
+            More complete description.
+            """
+
+* For class documentation, use the classes doc string for an explanation of
+  what the class is used for and how it works, including Python examples.
+  Include ``__init__`` argument documentation after the brief description in
+  the classes doc string. The class ``__init__`` should generally be the first
+  method defined in a class putting it as close as possible (location wise) to
+  the class documentation.
+
+  .. code:: python
+
+        class Bacon(CookedFood):
+            """Bacon is a breakfast food.
+
+            :param CookingType cooking_type:
+                Enum for the type of cooking to use.
+            :param float cooking_time:
+                Amount of time used to cook the Bacon in minutes.
+
+            Use Bacon in combination with other breakfast foods for
+            a complete breakfast. For example, combine Bacon with
+            other items in a list to make a breakfast:
+
+            .. code-block:: python
+
+                breakfast = [Bacon(), Spam(), Spam(), Eggs()]
+
+            """
+            def __init__(self, cooking_type=CookingType.BAKE, cooking_time=15.0):
+                super(Bacon, self).__init__(cooking_type, cooking_time)
diff --git a/docs/extra.css b/docs/extra.css
new file mode 100644 (file)
index 0000000..3ae7bdb
--- /dev/null
@@ -0,0 +1,57 @@
+.wy-side-nav-search {
+    background-color: initial;
+}
+
+.wy-nav-top {
+    background-color: #171A2F;
+}
+
+.wy-side-nav-search input[type="text"] {
+    border-color: transparent;
+}
+
+.wy-nav-content {
+    margin: initial;
+}
+
+.wy-nav-side {
+    background-color: #171A2F;
+}
+
+.rst-content div[role=navigation], footer {
+   font-size: 0.85em;
+   color: #999;
+}
+
+.rst-content div[role=navigation] hr {
+    margin-top: 6px;
+}
+
+footer hr {
+    margin-bottom: 6px;
+}
+
+.rst-footer-buttons {
+    display: none;
+}
+
+a.icon-home, a.icon-home:hover {
+    display: inline-block;
+    padding: 4px 4px 4px 21px;
+    background: transparent url(pygobject-small.svg) center left no-repeat;
+    background-size: 1.2em;
+    margin-top: 0.2em;
+    margin-bottom: 1em;
+}
+
+
+.fa-home::before, .icon-home::before {
+    content: "";
+}
+
+.wy-nav-top a {
+    margin: -2em;
+    background: transparent url(pygobject-small.svg) center left no-repeat;
+    background-size: 1.2em;
+    padding: 4px 4px 4px 24px;
+}
diff --git a/docs/further.rst b/docs/further.rst
new file mode 100644 (file)
index 0000000..832cf45
--- /dev/null
@@ -0,0 +1,10 @@
+=================
+Further Resources
+=================
+
+`Python GTK+ 3 Tutorial <https://python-gtk-3-tutorial.readthedocs.io>`__
+    Many examples showing how to build an application using PyGObject and GTK+.
+
+`Python GI API Reference <https://lazka.github.io/pgi-docs>`__
+    Auto generated API documentation for many libraries accessible through
+    PyGObject.
diff --git a/docs/getting_started.rst b/docs/getting_started.rst
new file mode 100644 (file)
index 0000000..eabde65
--- /dev/null
@@ -0,0 +1,133 @@
+.. include:: icons.rst
+
+.. _gettingstarted:
+
+===============
+Getting Started
+===============
+
+To get things started we will try to run a very simple `GTK+
+<https://www.gtk.org/>`_ based GUI application using the :doc:`PyGObject <index>` provided
+Python bindings. First create a small Python script called ``hello.py`` with
+the following content and save it somewhere:
+
+.. code:: python
+
+    import gi
+    gi.require_version("Gtk", "3.0")
+    from gi.repository import Gtk
+
+    window = Gtk.Window(title="Hello World")
+    window.show()
+    window.connect("destroy", Gtk.main_quit)
+    Gtk.main()
+
+Before we can run the example application we need to install PyGObject, GTK+
+and their dependencies. Follow the instructions for your platform below.
+
+======================================================= ==================================================== ==================================================== ==========================================================
+|ubuntu-logo| :ref:`Ubuntu <ubuntu-getting-started>`    |fedora-logo| :ref:`Fedora <fedora-getting-started>` |arch-logo| :ref:`Arch Linux <arch-getting-started>` |opensuse-logo| :ref:`openSUSE <opensuse-getting-started>`
+|windows-logo| :ref:`Windows <windows-getting-started>` |macosx-logo| :ref:`macOS <macosx-getting-started>`  |python-logo| :ref:`PyPI <pypi-getting-started>`
+======================================================= ==================================================== ==================================================== ==========================================================
+
+
+.. _windows-getting-started:
+
+|windows-logo| Windows
+----------------------
+
+#) Go to http://www.msys2.org/ and download the x86_64 installer
+#) Follow the instructions on the page for setting up the basic environment
+#) Run ``C:\msys64\mingw32.exe`` - a terminal window should pop up
+#) Execute ``pacman -Suy``
+#) Execute ``pacman -S mingw-w64-i686-gtk3 mingw-w64-i686-python2-gobject mingw-w64-i686-python3-gobject``
+#) To test that GTK+3 is working you can run ``gtk3-demo``
+#) Copy the ``hello.py`` script you created to ``C:\msys64\home\<username>``
+#) In the mingw32 terminal execute ``python3 hello.py`` - a window should appear.
+
+.. figure:: images/start_windows.png
+    :scale: 60%
+
+
+.. _ubuntu-getting-started:
+
+|ubuntu-logo| Ubuntu / |debian-logo| Debian
+-------------------------------------------
+
+1) Open a terminal
+2) Execute ``sudo apt install python-gi python-gi-cairo python3-gi python3-gi-cairo gir1.2-gtk-3.0``
+3) Change the directory to where your ``hello.py`` script can be found (e.g. ``cd Desktop``)
+4) Run ``python3 hello.py``
+
+.. figure:: images/start_linux.png
+    :scale: 60%
+
+
+.. _fedora-getting-started:
+
+|fedora-logo| Fedora
+--------------------
+
+1) Open a terminal
+2) Execute ``sudo dnf install pygobject3 python3-gobject gtk3``
+3) Change the directory to where your ``hello.py`` script can be found (e.g. ``cd Desktop``)
+4) Run ``python3 hello.py``
+
+
+.. _arch-getting-started:
+
+|arch-logo| Arch Linux
+----------------------
+
+1) Open a terminal
+2) Execute ``sudo pacman -S python-gobject python2-gobject gtk3``
+3) Change the directory to where your ``hello.py`` script can be found (e.g. ``cd Desktop``)
+4) Run ``python3 hello.py``
+
+
+.. _opensuse-getting-started:
+
+|opensuse-logo| openSUSE
+------------------------
+
+1) Open a terminal
+2) Execute ``sudo zypper install python-gobject python3-gobject gtk3``
+3) Change the directory to where your ``hello.py`` script can be found (e.g. ``cd Desktop``)
+4) Run ``python3 hello.py``
+
+
+.. _macosx-getting-started:
+
+|macosx-logo| macOS
+-------------------
+
+1) Go to https://brew.sh/ and install homebrew
+2) Open a terminal
+3) Execute ``brew install pygobject3 --with-python@2 gtk+3`` to install for both python2 and python3
+4) Change the directory to where your ``hello.py`` script can be found (e.g. ``cd Desktop``)
+5) Run ``python3 hello.py``
+
+.. figure:: images/start_macos.png
+    :scale: 70%
+
+
+.. _pypi-getting-started:
+
+|python-logo| From PyPI
+-----------------------
+
+PyGObject is also available on PyPI: https://pypi.org/project/PyGObject
+
+For this approach you have to make sure that all runtime and build
+dependencies are present yourself as pip will only take care of pycairo.
+
+.. code::
+
+    virtualenv --python=python3 myvenv
+    source myvenv/bin/activate
+    pip install pygobject
+    python hello.py
+
+
+For more details on how to use a virtualenv with PyGObject, see the
+":ref:`devenv`" page.
diff --git a/docs/guide/api/api.rst b/docs/guide/api/api.rst
new file mode 100644 (file)
index 0000000..c5e019f
--- /dev/null
@@ -0,0 +1,75 @@
+================
+GI Documentation
+================
+
+This is the API provided by the toplevel "gi" package.
+
+
+.. function:: gi.require_version(namespace, version)
+
+    :param str namespace: The namespace
+    :param str version: The version of the namespace which should be loaded
+    :raises: :obj:`ValueError <exceptions.ValueError>`
+
+    Ensures the namespace gets loaded with the given version. If the namespace
+    was already loaded with a different version or a different version was
+    required previously raises ValueError.
+
+    ::
+
+        import gi
+        gi.require_version('Gtk', '3.0')
+
+
+.. function:: gi.require_foreign(namespace, symbol=None)
+
+    :param str namespace:
+        Introspection namespace of the foreign module (e.g. "cairo")
+    :param symbol:
+        Optional symbol typename to ensure a converter exists.
+    :type symbol: :obj:`str` or :obj:`None`
+    :raises: :obj:`ImportError <exceptions.ImportError>`
+
+    Ensure the given foreign marshaling module is available and loaded.
+
+    Example:
+
+    .. code-block:: python
+
+        import gi
+        import cairo
+        gi.require_foreign('cairo')
+        gi.require_foreign('cairo', 'Surface')
+
+
+.. function:: gi.check_version(version)
+
+    :param tuple version: A version tuple
+    :raises: :obj:`ValueError <exceptions.ValueError>`
+
+    Compares the passed in version tuple with the gi version and does nothing
+    if gi version is the same or newer. Otherwise raises ValueError.
+
+
+.. function:: gi.get_required_version(namespace)
+
+    :returns: The version successfully required previously by :func:`gi.require_version` or :obj:`None`
+    :rtype: str or :obj:`None`
+
+
+.. data:: gi.version_info
+    :annotation: = (3, 18, 1)
+
+    The version of PyGObject
+
+
+.. class:: gi.PyGIDeprecationWarning
+
+    The warning class used for deprecations in PyGObject and the included
+    Python overrides. It inherits from DeprecationWarning and is hidden
+    by default.
+
+
+.. class:: gi.PyGIWarning
+
+    Like :class:`gi.PyGIDeprecationWarning` but visible by default.
diff --git a/docs/guide/api/basic_types.rst b/docs/guide/api/basic_types.rst
new file mode 100644 (file)
index 0000000..b41fee9
--- /dev/null
@@ -0,0 +1,53 @@
+===========
+Basic Types
+===========
+
+PyGObject will automatically convert between C types and Python types. In
+cases where it's appropriate it will use default Python types like :obj:`int`,
+:obj:`list`, and :obj:`dict`.
+
+
+Number Types
+------------
+
+All glib integer types get mapped to :obj:`int`, :obj:`long` and :obj:`float`.
+Since the glib integer types are always range limited, conversions from Python
+int/long can fail with :class:`OverflowError`:
+
+.. code:: pycon
+
+    >>> GLib.random_int_range(0, 2**31-1)
+    1684142898
+    >>> GLib.random_int_range(0, 2**31)
+    Traceback (most recent call last):
+      File "<stdin>", line 1, in <module>
+    OverflowError: 2147483648 not in range -2147483648 to 2147483647
+    >>> 
+
+
+Text Types
+----------
+
+In case you use Python 2 then text is utf-8 encoded :obj:`str`, in case of
+Python 3 :obj:`str` is used.
+
+
+Platform String Types
+---------------------
+
+* Windows + Python 2: utf-8 encoded :obj:`str`
+* Windows + Python 3: :obj:`str`
+* Unix + Python 2: :obj:`str`
+* Unix + Python 3: :obj:`str`
+
+On Python 3 there is currently no support for :obj:`bytes`, see `bug 746564
+<https://bugzilla.gnome.org/show_bug.cgi?id=746564>`__ for more details.
+
+
+Other Types
+-----------
+
+* GList <-> :obj:`list`
+* GSList <-> :obj:`list`
+* GHashTable <-> :obj:`dict`
+* arrays <-> :obj:`list`
diff --git a/docs/guide/api/error_handling.rst b/docs/guide/api/error_handling.rst
new file mode 100644 (file)
index 0000000..e392cca
--- /dev/null
@@ -0,0 +1,48 @@
+==============
+Error Handling
+==============
+
+GLib has its own method of handling errors using :obj:`GLib.Error`. These are
+raised as Python exceptions, but with a few small differences.
+
+It's common in Python for exception subclasses to be used (e.g.,
+:obj:`ValueError` versus :obj:`IOError`) to distinguish different types of
+errors. Libraries often define their own :obj:`Exception` subclasses, and
+library users will handle these cases explicitly.
+
+In GLib-using libraries, errors are all :obj:`GLib.Error` instances, with no
+subclassing for different error types. Instead, every :obj:`GLib.Error`
+instance has attributes that distinguish types of error:
+
+* :attr:`GLib.Error.domain` is the error domain, usually a string that you can
+  convert to a ``GLib`` quark with :func:`GLib.quark_from_string`
+* :attr:`GLib.Error.code` identifies a specific error within the domain
+* :attr:`GLib.Error.message` is a human-readable description of the error
+
+Error domains are defined per-module, and you can get an error domain from
+``*_error_quark`` functions on the relevant module. For example, IO errors
+from ``Gio`` are in the domain returned by :func:`Gio.io_error_quark`, and
+possible error code values are enumerated in :obj:`Gio.IOErrorEnum`.
+
+Once you've caught a :obj:`GLib.Error`, you can call
+:meth:`GLib.Error.matches` to see whether it matches the specific error you
+want to handle.
+
+
+Examples
+--------
+
+Catching a specific error:
+
+.. code:: pycon
+
+    >>> from gi.repository import GLib, Gio
+    >>> f = Gio.File.new_for_path('missing-path')
+    >>> try:
+    ...     f.read()
+    ... except GLib.Error as err:
+    ...     if err.matches(Gio.io_error_quark(), Gio.IOErrorEnum.NOT_FOUND):
+    ...         print('File not found')
+    ...     else:
+    ...         raise
+    File not found
diff --git a/docs/guide/api/flags_enums.rst b/docs/guide/api/flags_enums.rst
new file mode 100644 (file)
index 0000000..0a90735
--- /dev/null
@@ -0,0 +1,39 @@
+=============
+Flags & Enums
+=============
+
+Flags are subclasses of :class:`GObject.GFlags` and represent bit fields where
+some bits also have names:
+
+.. code:: pycon
+
+    >>> Gtk.DialogFlags.MODAL
+    <flags GTK_DIALOG_MODAL of type Gtk.DialogFlags>
+    >>> Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT
+    <flags GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT of type Gtk.DialogFlags>
+    >>> int(_)
+    3
+    >>> Gtk.DialogFlags(3)
+    <flags GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT of type Gtk.DialogFlags>
+    >>> isinstance(Gtk.DialogFlags.MODAL, Gtk.DialogFlags)
+    True
+    >>>
+
+Bitwise operations on them will produce a value of the same type.
+
+
+Enums are subclasses of :class:`GObject.GEnum` and represent a list of named
+constants:
+
+.. code:: pycon
+
+    >>> Gtk.Align.CENTER
+    <enum GTK_ALIGN_CENTER of type Gtk.Align>
+    >>> int(Gtk.Align.CENTER)
+    3
+    >>> int(Gtk.Align.END)
+    2
+    >>> Gtk.Align(1)
+    <enum GTK_ALIGN_START of type Gtk.Align>
+    >>> isinstance(Gtk.Align.CENTER, Gtk.Align)
+    True
diff --git a/docs/guide/api/gobject.rst b/docs/guide/api/gobject.rst
new file mode 100644 (file)
index 0000000..08c124b
--- /dev/null
@@ -0,0 +1,91 @@
+==============
+GObject.Object
+==============
+
+Compare to other types, :obj:`GObject.Object` has the best integration between
+the GObject and Python type system.
+
+1) It is possible to subclass a :obj:`GObject.Object`. Subclassing
+   creates a new :obj:`GObject.GType` which is connected to the new Python
+   type. This means you can use it with API which takes :obj:`GObject.GType`.
+2) The Python wrapper instance for a :obj:`GObject.Object` is always the same.
+   For the same C instance you will always get the same Python instance.
+
+
+In addition :obj:`GObject.Object` has support for :any:`signals <signals>` and
+:any:`properties <properties>`
+
+.. toctree::
+    :titlesonly:
+    :maxdepth: 1
+    :hidden:
+
+    signals
+    properties
+
+
+Examples
+--------
+
+Subclassing:
+
+.. code:: pycon
+
+    >>> from gi.repository import GObject
+    >>> class A(GObject.Object):
+    ...     pass
+    ... 
+    >>> A()
+    <__main__.A object at 0x7f9113fc3280 (__main__+A at 0x559d9861acc0)>
+    >>> A.__gtype__
+    <GType __main__+A (94135355573712)>
+    >>> A.__gtype__.name
+    '__main__+A'
+    >>> 
+
+In case you want to specify the GType name we have to provide a
+``__gtype_name__``:
+
+.. code:: pycon
+
+    >>> from gi.repository import GObject
+    >>> class B(GObject.Object):
+    ...     __gtype_name__ = "MyName"
+    ... 
+    >>> B.__gtype__
+    <GType MyName (94830143629776)>
+    >>> 
+
+:obj:`GObject.Object` only supports single inheritance, this means you can
+only subclass one :obj:`GObject.Object`, but multiple Python classes:
+
+.. code:: pycon
+
+    >>> from gi.repository import GObject
+    >>> class MixinA(object):
+    ...     pass
+    ... 
+    >>> class MixinB(object):
+    ...     pass
+    ... 
+    >>> class MyClass(GObject.Object, MixinA, MixinB):
+    ...     pass
+    ... 
+    >>> instance = MyClass()
+
+
+Here we can see how we create a :obj:`Gio.ListStore` for our new subclass and
+that we get back the same Python instance we put into it:
+
+.. code:: pycon
+
+    >>> from gi.repository import GObject, Gio
+    >>> class A(GObject.Object):
+    ...     pass
+    ... 
+    >>> store = Gio.ListStore.new(A)
+    >>> instance = A()
+    >>> store.append(instance)
+    >>> store.get_item(0) is instance
+    True
+    >>> 
diff --git a/docs/guide/api/index.rst b/docs/guide/api/index.rst
new file mode 100644 (file)
index 0000000..41f17ab
--- /dev/null
@@ -0,0 +1,13 @@
+=============
+API Reference
+=============
+
+.. toctree::
+    :titlesonly:
+    :maxdepth: 1
+
+    api
+    basic_types
+    flags_enums
+    gobject
+    error_handling
diff --git a/docs/guide/api/properties.rst b/docs/guide/api/properties.rst
new file mode 100644 (file)
index 0000000..0241109
--- /dev/null
@@ -0,0 +1,119 @@
+==========
+Properties
+==========
+
+Properties are part of a class and are defined through a
+:obj:`GObject.ParamSpec`, which contains the type, name, value range and so
+on.
+
+To find all the registered properties of a class you can use the
+:meth:`GObject.Object.list_properties` class method.
+
+.. code:: pycon
+
+    >>> Gio.Application.list_properties()
+    [<GParamString 'application-id'>, <GParamFlags 'flags'>, <GParamString
+    'resource-base-path'>, <GParamBoolean 'is-registered'>, <GParamBoolean
+    'is-remote'>, <GParamUInt 'inactivity-timeout'>, <GParamObject
+    'action-group'>, <GParamBoolean 'is-busy'>]
+    >>> param = Gio.Application.list_properties()[0]
+    >>> param.name
+    'application-id'
+    >>> param.owner_type
+    <GType GApplication (94881584893168)>
+    >>> param.value_type
+    <GType gchararray (64)>
+    >>> 
+
+The :obj:`GObject.Object` contructor takes multiple properties as keyword
+arguments. Property names usually contain "-" for seperating words. In Python
+you can either use "-" or "_". In this case variable names don't allow "-", so
+we use "_".
+
+.. code:: pycon
+
+    >>> app = Gio.Application(application_id="foo.bar")
+
+To get and set the property value see :meth:`GObject.Object.get_property` and
+:meth:`GObject.Object.set_property`.
+
+.. code:: pycon
+
+    >>> app = Gio.Application(application_id="foo.bar")
+    >>> app
+    <Gio.Application object at 0x7f7499284fa0 (GApplication at 0x564b571e7c00)>
+    >>> app.get_property("application_id")
+    'foo.bar'
+    >>> app.set_property("application_id", "a.b")
+    >>> app.get_property("application-id")
+    'a.b'
+    >>> 
+
+
+Each instance also has a ``props`` attribute which exposes all properties
+as instance attributes:
+
+.. code:: pycon
+
+    >>> from gi.repository import Gtk
+    >>> button = Gtk.Button(label="foo")
+    >>> button.props.label
+    'foo'
+    >>> button.props.label = "bar"
+    >>> button.get_label()
+    'bar'
+    >>> 
+
+
+To track changes of properties, :obj:`GObject.Object` has a special ``notify``
+signal with the property name as the detail string. Note that in this case you
+have to give the real property name and replacing "-" with "_" wont work.
+
+.. code:: pycon
+
+    >>> app = Gio.Application(application_id="foo.bar")
+    >>> def my_func(instance, param):
+    ...     print("New value %r" % instance.get_property(param.name))
+    ... 
+    >>> app.connect("notify::application-id", my_func)
+    11L
+    >>> app.set_property("application-id", "something.different")
+    New value 'something.different'
+    >>> 
+
+You can define your own properties using the :obj:`GObject.Property` decorator,
+which can be used similarly to the builtin Python :any:`property` decorator:
+
+.. function:: GObject.Property(type=None, default=None, nick='', blurb='', \
+    flags=GObject.ParamFlags.READWRITE, minimum=None, maximum=None)
+
+    :param GObject.GType type: Either a GType, a type with a GType or a
+        Python type which maps to a default GType
+    :param object default: A default value
+    :param str nick: Property nickname
+    :param str block: Short description
+    :param GObject.ParamFlags flags: Property configuration flags
+    :param object minimum: Minimum value, depends on the type
+    :param object maximum: Maximum value, depends on the type
+
+
+.. code:: python
+
+    class AnotherObject(GObject.Object):
+        value = 0
+
+        @GObject.Property
+        def prop_pyobj(self):
+            """Read only property."""
+
+            return object()
+
+        @GObject.Property(type=int)
+        def prop_gint(self):
+            """Read-write integer property."""
+
+            return self.value
+
+        @prop_gint.setter
+        def prop_gint(self, value):
+            self.value = value
diff --git a/docs/guide/api/signals.rst b/docs/guide/api/signals.rst
new file mode 100644 (file)
index 0000000..4353b21
--- /dev/null
@@ -0,0 +1,94 @@
+=======
+Signals
+=======
+
+GObject signals are a system for registering callbacks for specific events.
+
+To find all signals of a class you can use the
+:func:`GObject.signal_list_names` function:
+
+
+.. code:: pycon
+
+    >>> GObject.signal_list_names(Gio.Application)
+    ('activate', 'startup', 'shutdown', 'open', 'command-line', 'handle-local-options')
+    >>> 
+
+
+To connect to a signal, use :meth:`GObject.Object.connect`:
+
+.. code:: pycon
+
+    >>> app = Gio.Application()
+    >>> def on_activate(instance):
+    ...     print("Activated:", instance)
+    ... 
+    >>> app.connect("activate", on_activate)
+    17L
+    >>> app.run()
+    ('Activated:', <Gio.Application object at 0x7f1bbb304320 (GApplication at 0x5630f1faf200)>)
+    0
+    >>> 
+
+It returns number which identifies the connection during its lifetime and which
+can be used to modify the connection.
+
+For example it can be used to temporarily ignore signal emissions using
+:meth:`GObject.Object.handler_block`:
+
+.. code:: pycon
+
+    >>> app = Gio.Application(application_id="foo.bar")
+    >>> def on_change(*args):
+    ...     print(args)
+    ... 
+    >>> c = app.connect("notify::application-id", on_change)
+    >>> app.props.application_id = "foo.bar"
+    (<Gio.Application object at 0x7f1bbb304550 (GApplication at 0x5630f1faf2b0)>, <GParamString 'application-id'>)
+    >>> with app.handler_block(c):
+    ...     app.props.application_id = "no.change"
+    ... 
+    >>> app.props.application_id = "change.again"
+    (<Gio.Application object at 0x7f1bbb304550 (GApplication at 0x5630f1faf2b0)>, <GParamString 'application-id'>)
+    >>> 
+
+
+You can define your own signals using the :obj:`GObject.Signal` decorator:
+
+
+.. function:: GObject.Signal(name='', flags=GObject.SignalFlags.RUN_FIRST, \
+    return_type=None, arg_types=None, accumulator=None, accu_data=None)
+
+    :param str name: The signal name
+    :param GObject.SignalFlags flags: Signal flags
+    :param GObject.GType return_type: Return type
+    :param list arg_types: List of :class:`GObject.GType` argument types
+    :param accumulator: Accumulator function
+    :type accumulator: :obj:`GObject.SignalAccumulator`
+    :param object accu_data: User data for the accumulator
+
+
+.. code:: python
+
+    class MyClass(GObject.Object):
+
+        @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, return_type=bool,
+                        arg_types=(object,),
+                        accumulator=GObject.signal_accumulator_true_handled)
+        def test(self, *args):
+            print("Handler", args)
+
+        @GObject.Signal
+        def noarg_signal(self):
+            print("noarg_signal")
+
+    instance = MyClass()
+
+    def test_callback(inst, obj):
+        print "Handled", inst, obj
+        return True
+
+    instance.connect("test", test_callback)
+    instance.emit("test", object())
+
+    instance.emit("noarg_signal")
diff --git a/docs/guide/cairo_integration.rst b/docs/guide/cairo_integration.rst
new file mode 100644 (file)
index 0000000..84ea3f3
--- /dev/null
@@ -0,0 +1,39 @@
+=================
+Cairo Integration
+=================
+
+Despite `cairo <https://cairographics.org/>`__ not being a GObject based
+library, PyGObject provides special cairo integration through `pycairo
+<https://pycairo.readthedocs.io>`__. Functions returning and taking cairo data
+types get automatically converted to pycairo objects and vice versa.
+
+Some distros ship the PyGObject cairo support in a separate package. If you've
+followed the instructions on ":ref:`gettingstarted`" you should have everything
+installed.
+
+If your application requires the cairo integration you can use
+:func:`gi.require_foreign`:
+
+.. code:: python
+
+    try:
+        gi.require_foreign("cairo")
+    except ImportError:
+        print("No pycairo integration :(")
+
+Note that PyGObject currently does not support `cairocffi
+<https://pypi.python.org/pypi/cairocffi>`__, only pycairo.
+
+
+Demo
+----
+
+The following example shows a :obj:`Gtk.Window` with a custom drawing in Python
+using pycairo.
+
+.. figure:: images/cairo_integration.png
+    :scale: 75%
+    :align: center
+
+.. literalinclude:: code/cairo-demo.py
+    :linenos:
diff --git a/docs/guide/code/cairo-demo.py b/docs/guide/code/cairo-demo.py
new file mode 100755 (executable)
index 0000000..f5ac112
--- /dev/null
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+"""
+Based on cairo-demo/X11/cairo-demo.c
+"""
+
+import cairo
+import gi
+gi.require_version("Gtk", "3.0")
+from gi.repository import Gtk
+
+SIZE = 30
+
+
+def triangle(ctx):
+    ctx.move_to(SIZE, 0)
+    ctx.rel_line_to(SIZE, 2 * SIZE)
+    ctx.rel_line_to(-2 * SIZE, 0)
+    ctx.close_path()
+
+
+def square(ctx):
+    ctx.move_to(0, 0)
+    ctx.rel_line_to(2 * SIZE, 0)
+    ctx.rel_line_to(0, 2 * SIZE)
+    ctx.rel_line_to(-2 * SIZE, 0)
+    ctx.close_path()
+
+
+def bowtie(ctx):
+    ctx.move_to(0, 0)
+    ctx.rel_line_to(2 * SIZE, 2 * SIZE)
+    ctx.rel_line_to(-2 * SIZE, 0)
+    ctx.rel_line_to(2 * SIZE, -2 * SIZE)
+    ctx.close_path()
+
+
+def inf(ctx):
+    ctx.move_to(0, SIZE)
+    ctx.rel_curve_to(0, SIZE, SIZE, SIZE, 2 * SIZE, 0)
+    ctx.rel_curve_to(SIZE, -SIZE, 2 * SIZE, -SIZE, 2 * SIZE, 0)
+    ctx.rel_curve_to(0, SIZE, -SIZE, SIZE, - 2 * SIZE, 0)
+    ctx.rel_curve_to(-SIZE, -SIZE, - 2 * SIZE, -SIZE, - 2 * SIZE, 0)
+    ctx.close_path()
+
+
+def draw_shapes(ctx, x, y, fill):
+    ctx.save()
+
+    ctx.new_path()
+    ctx.translate(x + SIZE, y + SIZE)
+    bowtie(ctx)
+    if fill:
+        ctx.fill()
+    else:
+        ctx.stroke()
+
+    ctx.new_path()
+    ctx.translate(3 * SIZE, 0)
+    square(ctx)
+    if fill:
+        ctx.fill()
+    else:
+        ctx.stroke()
+
+    ctx.new_path()
+    ctx.translate(3 * SIZE, 0)
+    triangle(ctx)
+    if fill:
+        ctx.fill()
+    else:
+        ctx.stroke()
+
+    ctx.new_path()
+    ctx.translate(3 * SIZE, 0)
+    inf(ctx)
+    if fill:
+        ctx.fill()
+    else:
+        ctx.stroke()
+
+    ctx.restore()
+
+
+def fill_shapes(ctx, x, y):
+    draw_shapes(ctx, x, y, True)
+
+
+def stroke_shapes(ctx, x, y):
+    draw_shapes(ctx, x, y, False)
+
+
+def draw(da, ctx):
+    ctx.set_source_rgb(0, 0, 0)
+
+    ctx.set_line_width(SIZE / 4)
+    ctx.set_tolerance(0.1)
+
+    ctx.set_line_join(cairo.LINE_JOIN_ROUND)
+    ctx.set_dash([SIZE / 4.0, SIZE / 4.0], 0)
+    stroke_shapes(ctx, 0, 0)
+
+    ctx.set_dash([], 0)
+    stroke_shapes(ctx, 0, 3 * SIZE)
+
+    ctx.set_line_join(cairo.LINE_JOIN_BEVEL)
+    stroke_shapes(ctx, 0, 6 * SIZE)
+
+    ctx.set_line_join(cairo.LINE_JOIN_MITER)
+    stroke_shapes(ctx, 0, 9 * SIZE)
+
+    fill_shapes(ctx, 0, 12 * SIZE)
+
+    ctx.set_line_join(cairo.LINE_JOIN_BEVEL)
+    fill_shapes(ctx, 0, 15 * SIZE)
+    ctx.set_source_rgb(1, 0, 0)
+    stroke_shapes(ctx, 0, 15 * SIZE)
+
+
+def main():
+    win = Gtk.Window()
+    win.connect('destroy', lambda w: Gtk.main_quit())
+    win.set_default_size(450, 550)
+
+    drawingarea = Gtk.DrawingArea()
+    win.add(drawingarea)
+    drawingarea.connect('draw', draw)
+
+    win.show_all()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/docs/guide/debug_profile.rst b/docs/guide/debug_profile.rst
new file mode 100644 (file)
index 0000000..628bab1
--- /dev/null
@@ -0,0 +1,112 @@
+=====================
+Debugging & Profiling
+=====================
+
+Things can go wrong, these tools may help you find the cause. If you know any
+more tricks please share them.
+
+
+GObject Instance Count Leak Check
+---------------------------------
+
+Requires a development (only available in debug mode) version of glib. Jhbuild
+recommended.
+
+::
+
+    jhbuild shell
+    GOBJECT_DEBUG=instance-count GTK_DEBUG=interactive ./quodlibet.py
+
+* In the GTK+ Inspector switch to the "Statistics" tab
+* Sort by "Cumulative" and do the action which you suspect does leak or where
+  you want to make sure it doesn't repeatedly. Like for example opening
+  and closing a window or switching between media files to present.
+* If something in the "Cumulative" column steadily increases there probably
+  is a leak.
+
+cProfile Performance Profiling
+------------------------------
+
+* https://docs.python.org/2/library/profile.html
+* bundled with python
+
+::
+
+    python -m cProfile -s [sort_order] quodlibet.py > cprof.txt
+
+
+where ``sort_order`` can one of the following:
+calls, cumulative, file, line, module, name, nfl, pcalls, stdname, time
+
+Example output::
+
+             885311 function calls (866204 primitive calls) in 12.110 seconds
+
+       Ordered by: cumulative time
+
+       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
+            1    0.002    0.002   12.112   12.112 quodlibet.py:11(<module>)
+            1    0.007    0.007   12.026   12.026 quodlibet.py:25(main)
+    19392/13067    0.151    0.000    4.342    0.000 __init__.py:639(__get__)
+            1    0.003    0.003    4.232    4.232 quodlibetwindow.py:121(__init__)
+            1    0.000    0.000    4.029    4.029 quodlibetwindow.py:549(select_browser)
+            1    0.002    0.002    4.022    4.022 albums.py:346(__init__)
+            ...
+            ...
+
+SnakeViz - cProfile Based Visualization
+---------------------------------------
+
+* https://jiffyclub.github.io/snakeviz/
+* ``pip install snakeviz``
+
+::
+
+    python -m cProfile -o prof.out quodlibet.py
+    snakeviz prof.out
+
+
+Sysprof - System-wide Performance Profiler for Linux
+----------------------------------------------------
+
+* http://sysprof.com/
+
+::
+
+    sysprof-cli -c "python quodlibet/quodlibet.py"
+    sysprof capture.syscap
+
+GDB
+---
+
+::
+
+    gdb --args python quodlibet/quodlibet.py
+    # type "run" and hit enter
+
+
+Debugging Wayland Issues
+------------------------
+
+::
+
+    mutter --nested --wayland
+    # start your app, it should show up in the nested mutter
+
+::
+
+    weston
+    # start your app, it should show up in the nested weston
+
+
+Debugging HiDPI Issue
+---------------------
+
+::
+
+    GDK_SCALE=2 ./quodlibet/quodlibet.py
+
+::
+
+    MUTTER_DEBUG_NUM_DUMMY_MONITORS=2 MUTTER_DEBUG_DUMMY_MONITOR_SCALES=1,2 mutter --nested --wayland
+    # start your app, it should show up in the nested mutter
diff --git a/docs/guide/deploy.rst b/docs/guide/deploy.rst
new file mode 100644 (file)
index 0000000..efee20c
--- /dev/null
@@ -0,0 +1,52 @@
+.. include:: ../icons.rst
+
+======================
+Application Deployment
+======================
+
+There is currently no nice deployment story, but it's not impossible. This is
+a list of random notes and examples.
+
+|linux-logo| Linux
+------------------
+
+On Linux there is no single strategy. Quod Libet uses distutils, MyPaint uses
+SCons. Gramps uses distutils.
+
+|macosx-logo| macOS
+-------------------
+
+On OSX you can use `gtk-osx <https://git.gnome.org/browse/gtk-osx>`__ which is
+based on jhbuild and then `gtk-mac-bundler
+<https://git.gnome.org/browse/gtk-mac-bundler>`__ for packaging things up and
+making libraries relocatable. With macOS bundles you generally have a startup
+shell script which sets all the various env vars relative to the bundle,
+similar to jhbuild.
+
+|windows-logo| Windows
+----------------------
+
+On Windows things are usually build to be relocatable by default, so no env
+vars are needed. You can build/install through MSYS2, copy the bits you need
+and you are done. For GUI application you'll also need an exe launcher that
+links against the python dll.
+
+Example Deployments
+-------------------
+
+* `Quod Libet <https://quodlibet.readthedocs.io/>`__ provides a Windows
+  installer based on MSYS2 and NSIS3. On macOS, jhbuild is used for building,
+  gtk.mac-bundler for packing things up and `dmgbuild
+  <https://pypi.python.org/pypi/dmgbuild>`__ for creating a dmg. distutis is
+  used for building/installing the application into the final environment.
+  Most of this is automated and scripts can be found in the git repo.
+
+* `MyPaint <http://mypaint.org/>`__ provides a Windows installer based on
+  MSYS2 and Inno Setup. It uses SCons for building/installing the application.
+
+* ...?
+
+Other options
+-------------
+
+* `PyInstaller <http://www.pyinstaller.org/>`_ is a program that freezes (packages) Python programs into stand-alone executables, under Windows, Linux, Mac OS X, and more. PyInstaller's packager has built-in support for automatically including PyGObject dependencies with your application without requiring additional configuration.
diff --git a/docs/guide/faq.rst b/docs/guide/faq.rst
new file mode 100644 (file)
index 0000000..48031b0
--- /dev/null
@@ -0,0 +1,11 @@
+==========================
+Frequently Asked Questions
+==========================
+
+How can I use PyGObject with the official CPython builds on Windows?
+--------------------------------------------------------------------
+
+https://sourceforge.net/projects/pygobjectwin32 provides binaries which should
+be ABI compatible with the official CPython binaries. I'd recommend using
+msys2 if at all possible, since there are more people involved and it's easier
+to fix/patch things yourself.
diff --git a/docs/guide/images/cairo_integration.png b/docs/guide/images/cairo_integration.png
new file mode 100644 (file)
index 0000000..4726875
Binary files /dev/null and b/docs/guide/images/cairo_integration.png differ
diff --git a/docs/guide/index.rst b/docs/guide/index.rst
new file mode 100644 (file)
index 0000000..ac966d7
--- /dev/null
@@ -0,0 +1,17 @@
+==========
+User Guide
+==========
+
+
+.. toctree::
+    :titlesonly:
+    :maxdepth: 1
+
+    api/index
+    cairo_integration
+    threading
+    debug_profile
+    deploy
+    testing
+    porting
+    faq
diff --git a/docs/guide/porting.rst b/docs/guide/porting.rst
new file mode 100644 (file)
index 0000000..462ed85
--- /dev/null
@@ -0,0 +1,109 @@
+============================
+Porting from Static Bindings
+============================
+
+Before PyGObject 3, bindings where not generated automatically through gobject
+introspection and where provided as separate Python libraries like pygobject,
+pygtk, pygst etc. We call them static bindings.
+
+If your code contains imports like ``import gtk``, ``import gst``, ``import
+glib`` or ``import gobject`` you are using the old bindings and you should
+upgrade.
+
+Note that using old and new bindings in the same process is not supported, you
+have to switch everything at once.
+
+
+Static Bindings Library Differences
+-----------------------------------
+
+**pygtk** supported GTK+ 2.0 and Python 2 only. PyGObject supports GTK+ >=3.0
+and Python 2/3. If you port away from pygtk you also have to move to GTK+ 3.0
+at the same time. **pygtkcompat** described below can help you with that
+transition.
+
+**pygst** supports GStreamer 0.10 and Python 2 only. Like with GTK+ you have
+to move to PyGObject and GStreamer 1.0 at the same time.
+
+**pygobject 2** supports glib 2.0 and Python 2. The new bindings also support
+glib 2.0 and Python 2/3.
+
+
+General Porting Tips
+--------------------
+
+PyGObject contains a shell script which can help you with the many naming
+differences between static and dynamic bindings:
+
+https://gitlab.gnome.org/GNOME/pygobject/raw/master/tools/pygi-convert.sh
+
+::
+
+    ./pygi-convert.sh mymodule.py
+
+It just does basic text replacement. It reduces the amount of naming changes
+you have to make in the beginning, but nothing more.
+
+1) Run on a Python module
+2) Check/Verify the changes made (e.g. using ``git diff``)
+3) Finish porting the module by hand
+4) Continue to the next module...
+
+
+Porting Tips for GTK+
+---------------------
+
+While PyGObject theoretically supports GTK+ 2.0 it is not really usable. It
+will be easier to port to GTK+ 3.0 right away.
+
+For some general advice regarding the migration from GTK+ 2.0 to 3.0 see the
+`offical migration guide
+<https://developer.gnome.org/gtk3/stable/gtk-migrating-2-to-3.html>`__. If you
+need to know how a C symbol is exposed in Python have a look at the `symbol
+mapping listing <https://lazka.github.io/pgi-docs/#Gtk-3.0/mapping.html>`__.
+
+
+Using the pygtkcompat Compatibility Layer
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+PyGObject ships a compatibility layer for pygtk which partially emulates the
+old interfaces:
+
+::
+
+    from gi import pygtkcompat
+    pygtkcompat.enable()
+    pygtkcompat.enable_gtk(version='3.0')
+
+    import gtk
+
+``enable()`` has to be called once before the first ``gtk`` import.
+
+Note that pygtkcompat is just for helping you through the transition by
+allowing you to port one module at a time. Only a limited subset of the
+interfaces are emulated correctly and you should try to get rid of it in the
+end.
+
+
+Default Encoding Changes
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+Importing ``gtk`` had the side effect of changing the default Python encoding
+from ASCII to UTF-8 (check ``sys.getdefaultencoding()``) and that no longer
+happens with PyGObject. Since text with pygtk is returned as utf-8 encoded
+str, your code is likely depending auto-decoding in many places and you can
+change it manually by doing:
+
+::
+
+    # Python 2 only
+    import sys
+    reload(sys)
+    sys.setdefaultencoding("utf-8")
+    # see if auto decoding works:
+    assert '\xc3\xb6' + u'' ==  u'\xf6'
+
+While this is not officially supported by Python I don't know of any
+downsides. Once you are sure that you explicitly decode in all places or you
+move to Python 3 where things are unicode by default you can remove this
+again.
diff --git a/docs/guide/testing.rst b/docs/guide/testing.rst
new file mode 100644 (file)
index 0000000..6a7c663
--- /dev/null
@@ -0,0 +1,39 @@
+.. include:: ../icons.rst
+
+==================================
+Testing and Continuous Integration
+==================================
+
+To get automated tests of GTK+ code running on a headless server use Xvfb
+(virtual framebuffer X server). It provides the ``xvfb-run -a`` command which
+creates a temporary X server without the need for any real display hardware.
+
+::
+
+    xvfb-run -a python my_script.py
+
+
+Continuous Integration using Travis CI / CircleCI
+-------------------------------------------------
+
+Travis CI uses a rather old Ubuntu and thus the supported GTK+ is at 3.10 and
+PyGObject is at 3.12. If that's enough for you then have a look at our Travis
+CI example project:
+
+    |github-logo| https://github.com/pygobject/pygobject-travis-ci-examples
+
+    .. image:: https://travis-ci.org/pygobject/pygobject-travis-ci-examples.svg?branch=master
+        :target: https://travis-ci.org/pygobject/pygobject-travis-ci-examples
+
+To get newer PyGObject, GTK+, etc. working on `Travis CI
+<https://travis-ci.org>`__ or `CircleCI <https://circleci.com>`__ you can use
+Docker with an image of your choosing. Have a look at our Docker example
+project which runs tests on various Debian, Ubuntu and Fedora versions:
+
+    |github-logo| https://github.com/pygobject/pygobject-travis-ci-docker-examples
+
+    .. image:: https://travis-ci.org/pygobject/pygobject-travis-ci-docker-examples.svg?branch=master
+        :target: https://travis-ci.org/pygobject/pygobject-travis-ci-docker-examples
+
+    .. image:: https://circleci.com/gh/pygobject/pygobject-travis-ci-docker-examples.svg?style=shield
+        :target: https://circleci.com/gh/pygobject/pygobject-travis-ci-docker-examples
diff --git a/docs/guide/threading.rst b/docs/guide/threading.rst
new file mode 100644 (file)
index 0000000..c1bac32
--- /dev/null
@@ -0,0 +1,290 @@
+=====================
+Threads & Concurrency
+=====================
+
+Operations which could potentially block should not be executed in the main 
+loop. The main loop is in charge of input processing and drawing and 
+blocking it results in the user interface freezing. For the user this means 
+not getting any feedback and not being able to pause or abort the operation 
+which causes the problem.
+
+Such an operation might be:
+
+* Loading external resources like an image file on the web
+* Searching the local file system
+* Writing, reading and copying files
+* Calculations where the runtime depends on some external factor
+
+The following examples show
+
+* how Python threads, running in parallel to GTK+, can interact with the UI
+* how to use and control asynchronous I/O operations in glib
+
+
+Threads
+-------
+
+The first example uses a Python thread to execute code in the background 
+while still showing feedback on the progress in a window.
+
+.. code:: python
+
+    import threading
+    import time
+
+    from gi.repository import GLib, Gtk, GObject
+
+
+    def app_main():
+        win = Gtk.Window(default_height=50, default_width=300)
+        win.connect("destroy", Gtk.main_quit)
+
+        progress = Gtk.ProgressBar(show_text=True)
+        win.add(progress)
+
+        def update_progess(i):
+            progress.pulse()
+            progress.set_text(str(i))
+            return False
+
+        def example_target():
+            for i in range(50):
+                GLib.idle_add(update_progess, i)
+                time.sleep(0.2)
+
+        win.show_all()
+
+        thread = threading.Thread(target=example_target)
+        thread.daemon = True
+        thread.start()
+
+
+    if __name__ == "__main__":
+        app_main()
+        Gtk.main()
+
+
+The example shows a simple window containing a progress bar. After everything
+is set up it constructs a Python thread, passes it a function to execute,
+starts the thread and the GTK+ main loop. After the main loop is started it is
+possible to see the window and interact with it.
+
+In the background ``example_target()`` gets executed and calls
+:func:`GLib.idle_add` and :func:`time.sleep` in a loop. In this example
+:func:`time.sleep` represents the blocking operation. :func:`GLib.idle_add`
+takes the ``update_progess()`` function and arguments that will get passed to
+the function and asks the main loop to schedule its execution in the main
+thread. This is needed because GTK+ isn't thread safe; only one thread, the
+main thread, is allowed to call GTK+ code at all times.
+
+
+Threads: FAQ
+------------
+
+* I'm porting code from pygtk (GTK+ 2) to PyGObject (GTK+ 3). Has anything 
+  changed regarding threads?
+
+  Short answer: No.
+
+  Long answer: ``gtk.gdk.threads_init()``, ``gtk.gdk.threads_enter()`` and
+  ``gtk.gdk.threads_leave()`` are now :func:`Gdk.threads_init`,
+  :func:`Gdk.threads_enter` and :func:`Gdk.threads_leave`.
+  ``gobject.threads_init()`` can be removed.
+
+* I'm using :func:`Gdk.threads_init` and want to get rid of it. What do I 
+  need to do?
+
+  * Remove any :func:`Gdk.threads_init()`, :func:`Gdk.threads_enter` and  
+    :func:`Gdk.threads_leave` calls. In case they get executed in a thread,
+    move the GTK+ code into its own function and schedule it using
+    :func:`GLib.idle_add`. Be aware that the newly created function will be
+    executed some time later, so other stuff can happen in between.
+
+  * Replace any call to ``Gdk.threads_add_*()`` with their GLib counterpart.
+    For example :func:`GLib.idle_add` instead of :func:`Gdk.threads_add_idle`.
+
+* What about signals and threads?
+
+  Signals get executed in the context they are emitted from. In which context
+  the object is created or where ``connect()`` is called from doesn't matter.
+  In GStreamer, for example, some signals can be called from a different
+  thread, see the respective signal documentation for when this is the case.
+  In case you connect to such a signal you have to make sure to not call any
+  GTK+ code or use :func:`GLib.idle_add` accordingly.
+
+* What if I need to call GTK+ code in signal handlers emitted from a thread?
+
+  In case you have a signal that is emitted from another thread and you need
+  to call GTK+ code during and not after signal handling, you can push the
+  operation with an :class:`threading.Event` object to the main loop and wait
+  in the signal handler until the operation gets scheduled and the result is
+  available. Be aware that if the signal is emitted from the main loop this
+  will deadlock. See the following example
+
+  .. code:: python
+
+        # [...]
+
+        toggle_button = Gtk.ToggleButton()
+
+        def signal_handler_in_thread():
+
+            def function_calling_gtk(event, result):
+                result.append(toggle_button.get_active())
+                event.set()
+
+            event = threading.Event()
+            result = []
+            GLib.idle_add(function_calling_gtk, event, result)
+            event.wait()
+            toggle_button_is_active = result[0]
+            print(toggle_button_is_active)
+
+        # [...]
+
+* What about the Python `GIL
+  <https://en.wikipedia.org/wiki/Global_Interpreter_Lock>`__ ?
+
+  Similar to I/O operations in Python, all PyGObject calls release the 
+  GIL during their execution and other Python threads can be executed 
+  during that time.
+
+
+Asynchronous Operations
+-----------------------
+
+In addition to functions for blocking I/O glib also provides corresponding
+asynchronous versions, usually with the same name plus a ``_async`` suffix.
+These functions do the same operation as the synchronous ones but don't block
+during their execution. Instead of blocking they execute the operation in the
+background and call a callback once the operation is finished or got canceled.
+
+The following example shows how to download a web page and display the 
+source in a text field. In addition it's possible to abort the running 
+operation.
+
+
+.. code:: python
+
+    import time
+
+    from gi.repository import Gio, GLib, Gtk
+
+
+    class DownloadWindow(Gtk.Window):
+
+        def __init__(self):
+            super(DownloadWindow, self).__init__(
+                default_width=500, default_height=400, title="Async I/O Example")
+
+            self.cancellable = Gio.Cancellable()
+
+            self.cancel_button = Gtk.Button(label="Cancel")
+            self.cancel_button.connect("clicked", self.on_cancel_clicked)
+            self.cancel_button.set_sensitive(False)
+
+            self.start_button = Gtk.Button(label="Load")
+            self.start_button.connect("clicked", self.on_start_clicked)
+
+            textview = Gtk.TextView()
+            self.textbuffer = textview.get_buffer()
+            scrolled = Gtk.ScrolledWindow()
+            scrolled.add(textview)
+
+            box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6,
+                          border_width=12)
+            box.pack_start(self.start_button, False, True, 0)
+            box.pack_start(self.cancel_button, False, True, 0)
+            box.pack_start(scrolled, True, True, 0)
+
+            self.add(box)
+
+        def append_text(self, text):
+            iter_ = self.textbuffer.get_end_iter()
+            self.textbuffer.insert(iter_, "[%s] %s\n" % (str(time.time()), text))
+
+        def on_start_clicked(self, button):
+            button.set_sensitive(False)
+            self.cancel_button.set_sensitive(True)
+            self.append_text("Start clicked...")
+
+            file_ = Gio.File.new_for_uri(
+                "http://python-gtk-3-tutorial.readthedocs.org/")
+            file_.load_contents_async(
+                self.cancellable, self.on_ready_callback, None)
+
+        def on_cancel_clicked(self, button):
+            self.append_text("Cancel clicked...")
+            self.cancellable.cancel()
+
+        def on_ready_callback(self, source_object, result, user_data):
+            try:
+                succes, content, etag = source_object.load_contents_finish(result)
+            except GLib.GError as e:
+                self.append_text("Error: " + e.message)
+            else:
+                content_text = content[:100].decode("utf-8")
+                self.append_text("Got content: " + content_text + "...")
+            finally:
+                self.cancellable.reset()
+                self.cancel_button.set_sensitive(False)
+                self.start_button.set_sensitive(True)
+
+
+    if __name__ == "__main__":
+        win = DownloadWindow()
+        win.show_all()
+        win.connect("destroy", Gtk.main_quit)
+
+        Gtk.main()
+
+
+The example uses the asynchronous version of :meth:`Gio.File.load_contents` to
+load the content of an URI pointing to a web page, but first we look at the
+simpler blocking alternative:
+
+We create a :class:`Gio.File` instance for our URI and call
+:meth:`Gio.File.load_contents`, which, if it doesn't raise an error, returns
+the content of the web page we wanted.
+
+.. code:: python
+
+    file = Gio.File.new_for_uri("http://python-gtk-3-tutorial.readthedocs.org/")
+    try:
+        status, contents, etag_out = file.load_contents(None)
+    except GLib.GError:
+        print("Error!")
+    else:
+        print(contents)
+
+In the asynchronous variant we need two more things:
+
+* A :class:`Gio.Cancellable`, which we can use during the operation to 
+  abort or cancel it.
+* And a :func:`Gio.AsyncReadyCallback` callback function, which gets called
+  once the operation is finished and we can collect the result.
+
+The window contains two buttons for which we register ``clicked`` signal
+handlers:
+
+* The ``on_start_clicked()`` signal handler calls 
+  :meth:`Gio.File.load_contents_async` with a :class:`Gio.Cancellable` 
+  and ``on_ready_callback()`` as :func:`Gio.AsyncReadyCallback`.
+* The ``on_cancel_clicked()`` signal handler calls 
+  :meth:`Gio.Cancellable.cancel` to cancel the running operation.
+
+Once the operation is finished, either because the result is available, an
+error occurred or the operation was canceled, ``on_ready_callback()`` will be
+called with the :class:`Gio.File` instance and a :class:`Gio.AsyncResult`
+instance which holds the result.
+
+To get the result we now have to call :meth:`Gio.File.load_contents_finish` 
+which returns the same things as :meth:`Gio.File.load_contents` except in 
+this case the result is already there and it will return immediately 
+without blocking.
+
+After all this is done we call :meth:`Gio.Cancellable.reset` so the 
+:class:`Gio.Cancellable` can be re-used for new operations and we can click 
+the "Load" button again. This works since we made sure that only one 
+operation can be active at any time by deactivating the "Load" button using 
+:meth:`Gtk.Widget.set_sensitive`.
diff --git a/docs/icons.rst b/docs/icons.rst
new file mode 100644 (file)
index 0000000..6ccf5ce
--- /dev/null
@@ -0,0 +1,52 @@
+.. |python-logo| raw:: html
+
+    <i class="icon-python"></i>
+
+
+.. |ubuntu-logo| raw:: html
+
+    <i class="icon-ubuntu"></i>
+
+.. |debian-logo| raw:: html
+
+    <i class="icon-debian"></i>
+
+.. |fedora-logo| raw:: html
+
+    <i class="icon-fedora"></i>
+
+.. |opensuse-logo| raw:: html
+
+    <i class="icon-suse"></i>
+
+.. |windows-logo| raw:: html
+
+    <i class="fa fa-windows"></i>
+
+.. |source-logo| raw:: html
+
+    <i class="fa fa-file"></i>
+
+.. |arch-logo| raw:: html
+
+    <i class="icon-archlinux"></i>
+
+.. |macosx-logo| raw:: html
+
+    <i class="fa fa-apple"></i>
+
+.. |github-logo| raw:: html
+
+    <i class="fa fa-github"></i>
+
+.. |git-logo| raw:: html
+
+    <i class="fa fa-git-square"></i>
+
+.. |bug-logo| raw:: html
+
+    <i class="fa fa-bug"></i>
+
+.. |linux-logo| raw:: html
+
+    <i class="fa fa-linux"></i>
diff --git a/docs/images/LICENSE b/docs/images/LICENSE
new file mode 100644 (file)
index 0000000..0fbbdbf
--- /dev/null
@@ -0,0 +1,3 @@
+pygobject.svg and pygobject-small.svg are based on the GTK+ logo, created by
+Andreas Nilsson, licensed under CC BY-SA 3.0. For more info see
+https://commons.wikimedia.org/wiki/File:GTK%2B_logo.svg
diff --git a/docs/images/favicon.ico b/docs/images/favicon.ico
new file mode 100644 (file)
index 0000000..905b200
Binary files /dev/null and b/docs/images/favicon.ico differ
diff --git a/docs/images/logo.svg b/docs/images/logo.svg
new file mode 100644 (file)
index 0000000..3f4ebde
--- /dev/null
@@ -0,0 +1,266 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="135"
+   height="135"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.91 r13725"
+   version="1.0"
+   sodipodi:docname="logo.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective2897" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective2450" />
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2474">
+      <path
+         d="m 0,450.7086 121.89,0 0,153.071 -121.89,0 0,-153.071 z"
+         id="path2476"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2484">
+      <path
+         d="m 1701.323,36.99957 283.465,0 0,595.276 -283.465,0 0,-595.276 z"
+         id="path2486"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2494">
+      <path
+         d="m 0,1802.8342 487.5601,0 0,578.1658 L 0,2381 0,1802.8342 z"
+         id="path2496"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2504">
+      <path
+         d="m -1.5e-6,-2.94433 2.3255315,0 0,3.88873 -2.3255315,0 0,-3.88873 z"
+         id="path2506"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2514">
+      <path
+         d="m 0,1802.8342 487.5601,0 0,578.1658 L 0,2381 0,1802.8342 z"
+         id="path2516"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2532">
+      <path
+         d="m 9.806,537.3086 104.874,0 0,28.203 -104.874,0 0,-28.203 z"
+         id="path2534"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2542">
+      <path
+         d="m 1701.323,36.99957 283.465,0 0,595.276 -283.465,0 0,-595.276 z"
+         id="path2544"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2552">
+      <path
+         d="m 39.2241,2149.2344 419.4961,0 0,112.812 -419.4961,0 0,-112.812 z"
+         id="path2554"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2562">
+      <path
+         d="m -0.0935124,-19.05134 2.7028824,0 0,21.10663 -2.7028824,0 0,-21.10663 z"
+         id="path2564"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2572">
+      <path
+         d="m 39.2241,2149.2344 419.4961,0 0,112.812 -419.4961,0 0,-112.812 z"
+         id="path2574"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2612">
+      <path
+         d="m 0,-9.1245 291.968,0 0,462.668 -291.968,0 0,-462.668 z"
+         id="path2614"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2618">
+      <path
+         d="m 0,0 283.5,0 0,453.5436 -283.5,0 L 0,0 z"
+         id="path2620"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2628">
+      <path
+         d="m 1701.323,36.99957 283.465,0 0,595.276 -283.465,0 0,-595.276 z"
+         id="path2630"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2638">
+      <path
+         d="m 0,0 1134,0 0,1814.1743 -1134,0 L 0,0 z"
+         id="path2640"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2644">
+      <path
+         d="m 0,0 1134,0 0,1814.1743 -1134,0 L 0,0 z"
+         id="path2646"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2654">
+      <path
+         d="m 8.716e-4,0.0203769 0.9688584,0 0,1.2848631 -0.9688584,0 0,-1.2848631 z"
+         id="path2656"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath2664">
+      <path
+         d="m 0,0 1134,0 0,1814.1743 -1134,0 L 0,0 z"
+         id="path2666"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <inkscape:perspective
+       id="perspective2777"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <filter
+       inkscape:collect="always"
+       style="color-interpolation-filters:sRGB"
+       id="filter4210"
+       x="-0.013603581"
+       width="1.0272072"
+       y="-0.010734612"
+       height="1.0214692">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="0.17314453"
+         id="feGaussianBlur4212" />
+    </filter>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.979899"
+     inkscape:cx="18.317077"
+     inkscape:cy="97.877634"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1280"
+     inkscape:window-height="674"
+     inkscape:window-x="0"
+     inkscape:window-y="26"
+     inkscape:window-maximized="0"
+     showborder="true"
+     fit-margin-top="9"
+     fit-margin-left="9"
+     fit-margin-right="9"
+     fit-margin-bottom="9" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Ebene 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-74.24472,-374.35257)">
+    <rect
+       style="fill:#000000;fill-opacity:1"
+       id="rect3079"
+       width="117"
+       height="117"
+       x="83.24472"
+       y="383.35257"
+       ry="23.031063" />
+    <g
+       id="g3855"
+       transform="translate(0.7466894,0.04879762)"
+       style="fill:#4e9a06" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter4210)"
+       x="106.41864"
+       y="461.19006"
+       id="text4178"
+       sodipodi:linespacing="125%"
+       transform="matrix(1.1069586,0,0,1.1069586,-15.44004,-48.180085)"><tspan
+         sodipodi:role="line"
+         id="tspan4180"
+         x="106.41864"
+         y="461.19006"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:87.5px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';text-align:start;text-anchor:start;opacity:1;fill:#ffffff;fill-opacity:1">gi</tspan></text>
+  </g>
+</svg>
diff --git a/docs/images/overview.dia b/docs/images/overview.dia
new file mode 100644 (file)
index 0000000..08b0243
Binary files /dev/null and b/docs/images/overview.dia differ
diff --git a/docs/images/overview.svg b/docs/images/overview.svg
new file mode 100644 (file)
index 0000000..37d6e2c
--- /dev/null
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
+<svg width="27cm" height="10cm" viewBox="220 135 524 199" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <defs/>
+  <g id="Background">
+    <rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke-linejoin: round; stroke: #646464" x="456.332" y="136.698" width="134.923" height="196.302" rx="8" ry="8"/>
+    <g>
+      <rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke-linejoin: round; stroke: #5a9fd4" x="221.09" y="214.954" width="80" height="36" rx="10" ry="10"/>
+      <text font-size="12.8" style="fill: #646464; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:Lato;font-style:normal;font-weight:500" x="261.09" y="237.604">
+        <tspan x="261.09" y="237.604">Python</tspan>
+      </text>
+    </g>
+    <g>
+      <rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke-linejoin: round; stroke: #5a9fd4" x="338" y="216.562" width="81.6" height="36" rx="10" ry="10"/>
+      <text font-size="12.8" style="fill: #646464; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:Lato;font-style:normal;font-weight:500" x="378.8" y="239.212">
+        <tspan x="378.8" y="239.212">PyGObject</tspan>
+      </text>
+    </g>
+    <g>
+      <rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke-linejoin: round; stroke: #5a9fd4" x="473.126" y="147.249" width="100.6" height="36" rx="10" ry="10"/>
+      <text font-size="12.8" style="fill: #646464; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:Lato;font-style:normal;font-weight:500" x="523.426" y="169.899">
+        <tspan x="523.426" y="169.899">libgirepository</tspan>
+      </text>
+    </g>
+    <g>
+      <rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke-linejoin: round; stroke: #5a9fd4" x="483.752" y="192.25" width="80" height="36" rx="10" ry="10"/>
+      <text font-size="12.8" style="fill: #646464; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:Lato;font-style:normal;font-weight:500" x="523.752" y="214.9">
+        <tspan x="523.752" y="214.9">libglib</tspan>
+      </text>
+    </g>
+    <g>
+      <rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke-linejoin: round; stroke: #5a9fd4" x="483.834" y="238.25" width="80" height="36" rx="10" ry="10"/>
+      <text font-size="12.8" style="fill: #646464; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:Lato;font-style:normal;font-weight:500" x="523.834" y="260.9">
+        <tspan x="523.834" y="260.9">libgobject</tspan>
+      </text>
+    </g>
+    <g>
+      <line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke-linejoin: round; stroke: #646464" x1="301.09" y1="232.954" x2="330.289" y2="233.558"/>
+      <polygon style="fill: #646464; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #646464" fill-rule="evenodd" points="334.788,233.651 328.727,236.527 330.289,233.558 328.851,230.528 "/>
+    </g>
+    <g>
+      <line style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke: #646464" x1="419.6" y1="234.562" x2="449.596" y2="234.796"/>
+      <polygon style="fill: #646464; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #646464" fill-rule="evenodd" points="454.096,234.831 448.073,237.784 449.596,234.796 448.12,231.784 "/>
+    </g>
+    <g>
+      <path style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke: #646464" d="M 591.256 234.848 C 615.096,234.848 615.096,200.938 632.302,200.938"/>
+      <polygon style="fill: #646464; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #646464" fill-rule="evenodd" points="636.802,200.938 630.802,203.938 632.302,200.938 630.802,197.938 "/>
+    </g>
+    <g>
+      <path style="fill: none; stroke-opacity: 1; stroke-width: 2; stroke: #646464" d="M 591.256 234.848 C 615.096,234.848 615.096,270.234 631.594,270.234"/>
+      <polygon style="fill: #646464; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #646464" fill-rule="evenodd" points="636.094,270.234 630.094,273.234 631.594,270.234 630.094,267.234 "/>
+    </g>
+    <g>
+      <rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #5a9fd4" x="484" y="285.9" width="80" height="36" rx="10" ry="10"/>
+      <text font-size="12.8" style="fill: #646464; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:Lato;font-style:normal;font-weight:500" x="524" y="308.55">
+        <tspan x="524" y="308.55">libffi</tspan>
+      </text>
+    </g>
+    <g>
+      <rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #5a9fd4" x="639.038" y="182.938" width="104.05" height="36" rx="10" ry="10"/>
+      <text font-size="12.8" style="fill: #646464; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:Lato;font-style:normal;font-weight:500" x="691.063" y="205.588">
+        <tspan x="691.063" y="205.588">Gtk-3.0.typelib</tspan>
+      </text>
+    </g>
+    <g>
+      <rect style="fill: #ffffff; fill-opacity: 1; stroke-opacity: 1; stroke-width: 2; stroke: #5a9fd4" x="638.33" y="252.234" width="80" height="36" rx="10" ry="10"/>
+      <text font-size="12.8" style="fill: #646464; fill-opacity: 1; stroke: none;text-anchor:middle;font-family:Lato;font-style:normal;font-weight:500" x="678.33" y="274.884">
+        <tspan x="678.33" y="274.884">libgtk-3.so</tspan>
+      </text>
+    </g>
+  </g>
+</svg>
diff --git a/docs/images/pygobject-small.svg b/docs/images/pygobject-small.svg
new file mode 100644 (file)
index 0000000..e6576e8
--- /dev/null
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.0"
+   width="88.572403"
+   height="96.050858"
+   id="svg6843"
+   sodipodi:docname="pygobject-small.svg"
+   inkscape:version="0.92.1 r15371">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1366"
+     inkscape:window-height="674"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="1.2285173"
+     inkscape:cx="135.96584"
+     inkscape:cy="-1.8615718"
+     inkscape:window-x="0"
+     inkscape:window-y="26"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer1" />
+  <defs
+     id="defs6845">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4534">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop4530" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop4532" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4522">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop4518" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop4520" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4660">
+      <stop
+         style="stop-color:#646464;stop-opacity:1;"
+         offset="0"
+         id="stop4656" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="1"
+         id="stop4658" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4652">
+      <stop
+         style="stop-color:#5a9fd4;stop-opacity:1;"
+         offset="0"
+         id="stop4648" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0.94650203"
+         offset="1"
+         id="stop4650" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4644">
+      <stop
+         style="stop-color:#edd400;stop-opacity:1;"
+         offset="0"
+         id="stop4640" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="1"
+         id="stop4642" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4644"
+       id="linearGradient4646"
+       x1="53.816978"
+       y1="111.10486"
+       x2="20.88413"
+       y2="30.82696"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4652"
+       id="linearGradient4654"
+       x1="70.43454"
+       y1="17.875593"
+       x2="53.816978"
+       y2="111.10486"
+       gradientUnits="userSpaceOnUse"
+       spreadMethod="pad" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4660"
+       id="linearGradient4662"
+       x1="23.216625"
+       y1="81.319481"
+       x2="107.33282"
+       y2="39.060543"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4522"
+       id="linearGradient4524"
+       x1="70.587303"
+       y1="17.177763"
+       x2="70.485733"
+       y2="67.361443"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4534"
+       id="linearGradient4536"
+       x1="69.029457"
+       y1="87.363297"
+       x2="70.373047"
+       y2="68.046875"
+       gradientUnits="userSpaceOnUse" />
+  </defs>
+  <g
+     transform="translate(-19.822261,-15.90723)"
+     id="layer1">
+    <g
+       id="g4527"
+       transform="translate(0,-0.20854102)">
+      <path
+         inkscape:connector-curvature="0"
+         style="display:inline;opacity:1;fill:url(#linearGradient4654);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.12400007;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="path6976"
+         d="M 20.88413,30.82696 53.816977,55.527708 107.33282,39.060543 70.587303,17.177763 Z" />
+      <path
+         inkscape:connector-curvature="0"
+         style="display:inline;fill:url(#linearGradient4662);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.12364459;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="path6978"
+         d="M 22.94243,82.287118 20.88413,30.82696 53.816977,55.527708 v 55.577152 z" />
+      <path
+         inkscape:connector-curvature="0"
+         style="display:inline;opacity:1;fill:url(#linearGradient4646);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.12364459;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="path6980"
+         d="M 53.816977,111.10486 103.21619,90.5207 107.33282,39.060543 53.816977,55.527708 Z" />
+      <path
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#linearGradient4536);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         d="m 70.597656,66.675781 -47.558594,14.044922 0.355469,1.197266 46.978516,-13.871094 32.652343,22.910156 0.71875,-1.023437 z"
+         id="path6982"
+         inkscape:connector-curvature="0" />
+      <path
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#linearGradient4524);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         d="m 69.808594,17.875 v 49.109375 h 1.25 V 17.875 Z"
+         id="path6984"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/docs/images/pygobject.svg b/docs/images/pygobject.svg
new file mode 100644 (file)
index 0000000..fbf88e1
--- /dev/null
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.0"
+   width="392.86777"
+   height="96.050858"
+   id="svg6843"
+   sodipodi:docname="pygobject.svg"
+   inkscape:version="0.92.1 r15371">
+  <metadata
+     id="metadata12">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1366"
+     inkscape:window-height="674"
+     id="namedview10"
+     showgrid="false"
+     inkscape:zoom="1.2285173"
+     inkscape:cx="135.96584"
+     inkscape:cy="-1.8615724"
+     inkscape:window-x="0"
+     inkscape:window-y="26"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g4527" />
+  <defs
+     id="defs6845">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4534">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop4530" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop4532" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4522">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop4518" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop4520" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4660">
+      <stop
+         style="stop-color:#646464;stop-opacity:1;"
+         offset="0"
+         id="stop4656" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="1"
+         id="stop4658" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4652">
+      <stop
+         style="stop-color:#5a9fd4;stop-opacity:1;"
+         offset="0"
+         id="stop4648" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0.94650203"
+         offset="1"
+         id="stop4650" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4644">
+      <stop
+         style="stop-color:#edd400;stop-opacity:1;"
+         offset="0"
+         id="stop4640" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="1"
+         id="stop4642" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4644"
+       id="linearGradient4646"
+       x1="53.816978"
+       y1="111.10486"
+       x2="20.88413"
+       y2="30.82696"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4652"
+       id="linearGradient4654"
+       x1="70.43454"
+       y1="17.875593"
+       x2="53.816978"
+       y2="111.10486"
+       gradientUnits="userSpaceOnUse"
+       spreadMethod="pad" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4660"
+       id="linearGradient4662"
+       x1="23.216625"
+       y1="81.319481"
+       x2="107.33282"
+       y2="39.060543"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4522"
+       id="linearGradient4524"
+       x1="70.587303"
+       y1="17.177763"
+       x2="70.485733"
+       y2="67.361443"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4534"
+       id="linearGradient4536"
+       x1="69.029457"
+       y1="87.363297"
+       x2="70.373047"
+       y2="68.046875"
+       gradientUnits="userSpaceOnUse" />
+  </defs>
+  <g
+     transform="translate(-19.822261,-15.90723)"
+     id="layer1">
+    <g
+       id="g4527"
+       transform="translate(0,-0.20854102)">
+      <path
+         inkscape:connector-curvature="0"
+         style="display:inline;opacity:1;fill:url(#linearGradient4654);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.12400007;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="path6976"
+         d="M 20.88413,30.82696 53.816977,55.527708 107.33282,39.060543 70.587303,17.177763 Z" />
+      <path
+         inkscape:connector-curvature="0"
+         style="display:inline;fill:url(#linearGradient4662);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.12364459;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="path6978"
+         d="M 22.94243,82.287118 20.88413,30.82696 53.816977,55.527708 v 55.577152 z" />
+      <path
+         inkscape:connector-curvature="0"
+         style="display:inline;opacity:1;fill:url(#linearGradient4646);fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.12364459;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="path6980"
+         d="M 53.816977,111.10486 103.21619,90.5207 107.33282,39.060543 53.816977,55.527708 Z" />
+      <path
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#linearGradient4536);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         d="m 70.597656,66.675781 -47.558594,14.044922 0.355469,1.197266 46.978516,-13.871094 32.652343,22.910156 0.71875,-1.023437 z"
+         id="path6982"
+         inkscape:connector-curvature="0" />
+      <path
+         style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#linearGradient4524);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+         d="m 69.808594,17.875 v 49.109375 h 1.25 V 17.875 Z"
+         id="path6984"
+         inkscape:connector-curvature="0" />
+    </g>
+    <g
+       aria-label="PyGObject"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:25px;font-family:Cantarell;-inkscape-font-specification:Cantarell;letter-spacing:-2px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="text4518"
+       transform="matrix(0.73060434,0,0,0.73060434,39.415227,17.281548)">
+      <path
+         d="m 134.29489,66.572751 c 14.8,0 26.88,-2.96 26.88,-17.44 0,-10.96 -7.68,-16.4 -22.96,-16.4 h -18.32 v 55.36 h 9.84 v -21.52 z m 16.8,-17.04 c 0,7.44 -7.76,8 -16.4,8 h -4.96 v -15.76 h 7.44 c 7.36,0 13.92,0.96 13.92,7.76 z"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';letter-spacing:0px;word-spacing:0px;fill:#646464;fill-opacity:1"
+         id="path4529"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 164.91864,49.612751 17.44,38.8 -8.8,20.399999 h 10.8 l 23.6,-59.199999 h -10.16 l -10.4,29.52 -11.44,-29.52 z"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';letter-spacing:0px;word-spacing:0px;fill:#646464;fill-opacity:1"
+         id="path4531"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 211.70114,60.092751 c 0,18.64 11.12,28.8 27.68,28.8 9.68,0 17.04,-2.4 22.4,-6.64 v -25.44 h -24.4 v 9.04 h 14.56 v 11.68 c -3.28,1.6 -5.84,2.32 -10.96,2.32 -11.28,0 -19.44,-6.48 -19.44,-20.16 0,-12.96 6.08,-19.04 19.68,-19.04 6.4,0 12,2.24 16.24,4.4 l 2.72,-8.72 c -5.28,-3.2 -12.48,-4.72 -19.2,-4.72 -19.52,0 -29.28,11.36 -29.28,28.48 z"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';letter-spacing:0px;word-spacing:0px;fill:#646464;fill-opacity:1"
+         id="path4533"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 297.97114,31.612751 c -16.48,0 -27.52,11.92 -27.52,28.64 0,16.8 10.88,28.64 27.2,28.64 16.8,0 27.84,-12.24 27.84,-29.2 0,-16.64 -11.04,-28.08 -27.52,-28.08 z m -0.4,9.04 c 11.12,0 18.08,8.08 18.08,19.6 0,11.52 -6.24,19.6 -17.36,19.6 -11.2,0 -18,-8.72 -18,-20.24 0,-11.28 6.32,-18.96 17.28,-18.96 z"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';letter-spacing:0px;word-spacing:0px;fill:#646464;fill-opacity:1"
+         id="path4535"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 340.09739,88.092751 1.92,-2.48 c 3.84,2.24 7.68,3.28 11.6,3.28 11.52,0 18.32,-8.56 18.32,-20.24 0,-11.68 -4.72,-19.92 -16.56,-19.92 -4.56,0 -8.32,1.12 -11.6,2.8 v -20.88 h -9.84 v 57.44 z m 3.68,-29.2 c 1.92,-0.8 5.68,-2.16 9.2,-2.16 6.88,0 9.12,4.96 9.12,12.48 0,7.52 -3.52,11.92 -10.08,11.92 -3.2,0 -5.92,-0.8 -8.24,-2.64 z"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';letter-spacing:0px;word-spacing:0px;fill:#646464;fill-opacity:1"
+         id="path4537"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 385.06739,44.972751 c 3.12,0 5.92,-2.8 5.92,-5.92 0,-3.12 -2.8,-5.92 -5.92,-5.92 -3.12,0 -5.92,2.8 -5.92,5.92 0,3.12 2.8,5.92 5.92,5.92 z m -8.48,56.319999 c -1.2,0 -2.24,-0.24 -2.24,-0.24 l -1.92,7.36 c 0,0 2.64,0.8 5.44,0.8 10.16,0 12.24,-6.96 12.24,-13.999999 v -45.6 h -9.84 v 43.6 c 0,4.96 -0.56,8.079999 -3.68,8.079999 z"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';letter-spacing:0px;word-spacing:0px;fill:#646464;fill-opacity:1"
+         id="path4539"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 420.17989,81.132751 c -9.44,0 -11.6,-4.08 -12,-9.04 h 26.4 v -3.76 c 0,-12.56 -5.92,-19.6 -17.44,-19.6 -11.44,0 -18.88,8.96 -18.88,20.08 0,12.48 7.68,20.08 20.64,20.08 5.12,0 10.08,-0.88 14.8,-2.4 l -1.92,-7.36 c -3.52,1.36 -7.36,2 -11.6,2 z m -11.92,-15.84 c 0.48,-4.64 2.48,-8.96 9.04,-8.96 4.8,0 7.44,2.8 7.44,8.96 z"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';letter-spacing:0px;word-spacing:0px;fill:#646464;fill-opacity:1"
+         id="path4541"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 450.44364,68.812751 c 0,-7.44 4.4,-12.4 12.8,-12.4 5.04,0 7.84,1.2 9.6,2.16 l 1.92,-7.36 c -1.92,-1.2 -5.44,-2.48 -12.56,-2.48 -13.2,0 -21.6,7.76 -21.6,20.08 0,12 8.72,20.08 21.92,20.08 6.08,0 11.04,-1.76 12.4,-2.4 l -1.92,-7.36 c -1.68,0.8 -4.72,2.08 -9.76,2.08 -7.92,0 -12.8,-5.12 -12.8,-12.4 z"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';letter-spacing:0px;word-spacing:0px;fill:#646464;fill-opacity:1"
+         id="path4543"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 510.91239,86.732751 -1.44,-6.8 c -1.44,0.4 -4.4,1.12 -7.12,1.12 -5.28,0 -6.48,-2.64 -6.48,-5.6 v -18.16 h 13.28 v -7.68 h -13.28 v -12.08 h -9.84 v 12.08 h -6.4 v 7.68 h 6.4 v 18.4 c 0,6.8 1.68,13.2 13.92,13.2 3.68,0 8.24,-1.12 10.96,-2.16 z"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';letter-spacing:0px;word-spacing:0px;fill:#646464;fill-opacity:1"
+         id="path4545"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/docs/images/start_linux.png b/docs/images/start_linux.png
new file mode 100644 (file)
index 0000000..5a557ee
Binary files /dev/null and b/docs/images/start_linux.png differ
diff --git a/docs/images/start_macos.png b/docs/images/start_macos.png
new file mode 100644 (file)
index 0000000..8b35a95
Binary files /dev/null and b/docs/images/start_macos.png differ
diff --git a/docs/images/start_windows.png b/docs/images/start_windows.png
new file mode 100644 (file)
index 0000000..db4ce32
Binary files /dev/null and b/docs/images/start_windows.png differ
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644 (file)
index 0000000..81fba33
--- /dev/null
@@ -0,0 +1,91 @@
+.. include:: icons.rst
+
+.. title:: Overview
+
+.. toctree::
+    :hidden:
+    :titlesonly:
+    :maxdepth: 1
+
+    getting_started
+    changelog
+    bugs_repo
+    guide/index
+    devguide/index
+    packagingguide
+    maintguide
+    further
+    contact
+
+.. image:: images/pygobject.svg
+   :align: center
+   :width: 400px
+   :height: 98px
+
+|
+
+.. include:: ../README.rst
+    :start-after: |
+    :end-before: ----
+
+If you want to write a Python application for `GNOME
+<https://www.gnome.org/>`__ or a Python GUI application using GTK+, then
+PyGObject is the way to go. For more information on specific libraries check
+out the "`Python GTK+ 3 Tutorial
+<https://python-gtk-3-tutorial.readthedocs.io>`__" and the "`Python GI API
+Reference <https://lazka.github.io/pgi-docs>`__".
+
+.. code:: python
+
+    import gi
+    gi.require_version("Gtk", "3.0")
+    from gi.repository import Gtk
+
+    window = Gtk.Window(title="Hello World")
+    window.show()
+    window.connect("destroy", Gtk.main_quit)
+    Gtk.main()
+
+
+How does it work?
+-----------------
+
+.. figure:: images/overview.svg
+    :width: 600px
+    :height: 222px
+    :align: center
+
+PyGObject uses `glib <https://developer.gnome.org/glib/stable/>`__, `gobject
+<https://developer.gnome.org/gobject/stable/>`__, `girepository
+<https://developer.gnome.org/gi/stable/>`__, `libffi
+<https://sourceware.org/libffi/>`__ and other libraries to access the C
+library (libgtk-3.so) in combination with the additional metadata from the
+accompanying typelib file (Gtk-3.0.typelib) and dynamically provides a Python
+interface based on that information.
+
+
+Who Is Using PyGObject?
+-----------------------
+
+* `Anaconda <https://fedoraproject.org/wiki/Anaconda>`__ - an installation program used by Fedora, RHEL and others
+* `D-Feet <https://wiki.gnome.org/action/show/Apps/DFeet>`__ - an easy to use D-Bus debugger
+* `GNOME Music <https://wiki.gnome.org/Apps/Music>`__ - a music player for GNOME
+* `GNOME Tweak Tool <https://wiki.gnome.org/action/show/Apps/GnomeTweakTool>`__ - a tool to customize advanced GNOME 3 options
+* `Gramps <https://gramps-project.org/>`__ - a genealogy program
+* `Lollypop <https://gnumdk.github.io/lollypop-web/>`__ - a modern music player
+* `Meld <http://meldmerge.org/>`__ - a visual diff and merge tool
+* `MyPaint <http://mypaint.org/>`__ - a nimble, distraction-free, and easy tool for digital painters
+* `Orca <https://wiki.gnome.org/Projects/Orca>`__ - a flexible and extensible screen reader
+* `Pithos <https://pithos.github.io/>`__ - a Pandora Radio client
+* `Pitivi <http://www.pitivi.org/>`__ - a free and open source video editor
+* `Quod Libet <https://quodlibet.readthedocs.io/>`__ - a music library manager / player
+* `Transmageddon <http://www.linuxrising.org/>`__ - a video transcoder
+
+
+The following applications or libraries use PyGObject for optional features,
+such as plugins or as optional backends:
+
+* `beets <http://beets.io/>`__ - a music library manager and MusicBrainz tagger
+* `gedit <https://wiki.gnome.org/Apps/Gedit>`_- a GNOME text editor
+* `matplotlib <http://matplotlib.org/>`__ - a python 2D plotting library
+* `Totem <https://wiki.gnome.org/Apps/Videos>`__ - a video player for GNOME
diff --git a/docs/maintguide.rst b/docs/maintguide.rst
new file mode 100644 (file)
index 0000000..5715ab8
--- /dev/null
@@ -0,0 +1,34 @@
+================
+Maintainer Guide
+================
+
+Making a Release
+----------------
+
+#. Make sure setup.py has the right version number
+#. Update NEWS file
+#. Run ``python3 setup.py distcheck``, fix any issues and commit.
+#. Commit NEWS as ``"release 3.X.Y"`` and push
+#. Tag with: ``git tag -s 3.X.Y -m "release 3.X.Y"``
+#. Push tag with: ``git push origin 3.X.Y``
+#. In case of a stable release, upload to PyPI:
+   ``twine upload dist/PyGObject-3.X.Y.tar.gz``
+#. Commit post-release version bump to setup.py
+#. Create GNOME tarball ``python3 setup.py sdist_gnome``
+#. Upload tarball: ``scp pygobject-3.X.Y.tar.xz user@master.gnome.org:``
+#. Install tarball:
+   ``ssh user@master.gnome.org 'ftpadmin install pygobject-3.X.Y.tar.xz'``
+#. In case the release happens on a stable branch copy the NEWS changes to
+   the master branch
+
+
+Branching
+---------
+
+Each cycle after the feature freeze, we create a stable branch so development
+can continue in the master branch unaffected by the freezes.
+
+#. Create the branch locally with: ``git checkout -b pygobject-3-2``
+#. Push new branch: ``git push origin pygobject-3-2``
+#. In master, update setup.py to what will be the next version number
+   (3.3.0)
diff --git a/docs/packagingguide.rst b/docs/packagingguide.rst
new file mode 100644 (file)
index 0000000..7d6fafa
--- /dev/null
@@ -0,0 +1,45 @@
+Packaging Guide
+===============
+
+Some notes on how to package PyGObject
+
+Source packages can be found at
+https://ftp.gnome.org/pub/GNOME/sources/pygobject
+
+Existing Packages:
+
+* https://www.archlinux.org/packages/extra/x86_64/python-gobject
+* https://tracker.debian.org/pkg/pygobject
+* https://github.com/Alexpux/MINGW-packages/tree/master/mingw-w64-pygobject
+
+Building::
+
+    python3 setup.py build
+    python3 setup.py test # if you want to run the test suite
+    python3 setup.py install --prefix="${PREFIX}" --root="${PKGDIR}"
+
+Runtime dependencies:
+
+    * glib
+    * libgirepository (gobject-introspection)
+    * libffi
+    * Python 2 or 3
+
+    The overrides directory contains various files which includes various
+    Python imports mentioning gtk, gdk etc. They are only used when the
+    corresponding library is present, they are not direct dependencies.
+
+Build dependencies:
+
+    * The runtime dependencies
+    * cairo (optional)
+    * pycairo (optional)
+    * pkg-config
+    * setuptools (optional)
+
+Test Suite dependencies:
+
+    * The runtime dependencies
+    * GTK+ 3 (optional)
+    * pango (optional)
+    * pycairo (optional)
diff --git a/examples/cairo-demo.py b/examples/cairo-demo.py
new file mode 100755 (executable)
index 0000000..ee33cdd
--- /dev/null
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+"""
+Based on cairo-demo/X11/cairo-demo.c
+"""
+
+import cairo
+from gi.repository import Gtk
+
+SIZE = 30
+
+
+def triangle(ctx):
+    ctx.move_to(SIZE, 0)
+    ctx.rel_line_to(SIZE, 2 * SIZE)
+    ctx.rel_line_to(-2 * SIZE, 0)
+    ctx.close_path()
+
+
+def square(ctx):
+    ctx.move_to(0, 0)
+    ctx.rel_line_to(2 * SIZE, 0)
+    ctx.rel_line_to(0, 2 * SIZE)
+    ctx.rel_line_to(-2 * SIZE, 0)
+    ctx.close_path()
+
+
+def bowtie(ctx):
+    ctx.move_to(0, 0)
+    ctx.rel_line_to(2 * SIZE, 2 * SIZE)
+    ctx.rel_line_to(-2 * SIZE, 0)
+    ctx.rel_line_to(2 * SIZE, -2 * SIZE)
+    ctx.close_path()
+
+
+def inf(ctx):
+    ctx.move_to(0, SIZE)
+    ctx.rel_curve_to(0, SIZE, SIZE, SIZE, 2 * SIZE, 0)
+    ctx.rel_curve_to(SIZE, -SIZE, 2 * SIZE, -SIZE, 2 * SIZE, 0)
+    ctx.rel_curve_to(0, SIZE, -SIZE, SIZE, - 2 * SIZE, 0)
+    ctx.rel_curve_to(-SIZE, -SIZE, - 2 * SIZE, -SIZE, - 2 * SIZE, 0)
+    ctx.close_path()
+
+
+def draw_shapes(ctx, x, y, fill):
+    ctx.save()
+
+    ctx.new_path()
+    ctx.translate(x + SIZE, y + SIZE)
+    bowtie(ctx)
+    if fill:
+        ctx.fill()
+    else:
+        ctx.stroke()
+
+    ctx.new_path()
+    ctx.translate(3 * SIZE, 0)
+    square(ctx)
+    if fill:
+        ctx.fill()
+    else:
+        ctx.stroke()
+
+    ctx.new_path()
+    ctx.translate(3 * SIZE, 0)
+    triangle(ctx)
+    if fill:
+        ctx.fill()
+    else:
+        ctx.stroke()
+
+    ctx.new_path()
+    ctx.translate(3 * SIZE, 0)
+    inf(ctx)
+    if fill:
+        ctx.fill()
+    else:
+        ctx.stroke()
+
+    ctx.restore()
+
+
+def fill_shapes(ctx, x, y):
+    draw_shapes(ctx, x, y, True)
+
+
+def stroke_shapes(ctx, x, y):
+    draw_shapes(ctx, x, y, False)
+
+
+def draw(da, ctx):
+    ctx.set_source_rgb(0, 0, 0)
+
+    ctx.set_line_width(SIZE / 4)
+    ctx.set_tolerance(0.1)
+
+    ctx.set_line_join(cairo.LINE_JOIN_ROUND)
+    ctx.set_dash([SIZE / 4.0, SIZE / 4.0], 0)
+    stroke_shapes(ctx, 0, 0)
+
+    ctx.set_dash([], 0)
+    stroke_shapes(ctx, 0, 3 * SIZE)
+
+    ctx.set_line_join(cairo.LINE_JOIN_BEVEL)
+    stroke_shapes(ctx, 0, 6 * SIZE)
+
+    ctx.set_line_join(cairo.LINE_JOIN_MITER)
+    stroke_shapes(ctx, 0, 9 * SIZE)
+
+    fill_shapes(ctx, 0, 12 * SIZE)
+
+    ctx.set_line_join(cairo.LINE_JOIN_BEVEL)
+    fill_shapes(ctx, 0, 15 * SIZE)
+    ctx.set_source_rgb(1, 0, 0)
+    stroke_shapes(ctx, 0, 15 * SIZE)
+
+
+def main():
+    win = Gtk.Window()
+    win.connect('destroy', lambda w: Gtk.main_quit())
+    win.set_default_size(450, 550)
+
+    drawingarea = Gtk.DrawingArea()
+    win.add(drawingarea)
+    drawingarea.connect('draw', draw)
+
+    win.show_all()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demo.py b/examples/demo/demo.py
new file mode 100755 (executable)
index 0000000..d67935d
--- /dev/null
@@ -0,0 +1,356 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+
+import codecs
+import os
+import sys
+import textwrap
+
+from gi.repository import GLib, GObject, Pango, GdkPixbuf, Gtk, Gio
+
+try:
+    from gi.repository import GtkSource
+    GtkSource  # PyFlakes
+except ImportError:
+    GtkSource = None
+
+
+DEMOROOTDIR = os.path.abspath(os.path.dirname(__file__))
+DEMOCODEDIR = os.path.join(DEMOROOTDIR, 'demos')
+sys.path.insert(0, DEMOROOTDIR)
+
+
+class Demo(GObject.GObject):
+    __gtype_name__ = 'GtkDemo'
+
+    def __init__(self, title, module, filename):
+        super(Demo, self).__init__()
+
+        self.title = title
+        self.module = module
+        self.filename = filename
+
+    @classmethod
+    def new_from_file(cls, path):
+        relpath = os.path.relpath(path, DEMOROOTDIR)
+        packagename = os.path.dirname(relpath).replace(os.sep, '.')
+        modulename = os.path.basename(relpath)[0:-3]
+
+        package = __import__(packagename, globals(), locals(), [modulename], 0)
+        module = getattr(package, modulename)
+
+        try:
+            return cls(module.title, module, path)
+        except AttributeError as e:
+            raise AttributeError('(%s): %s' % (path, e.message))
+
+
+class DemoTreeStore(Gtk.TreeStore):
+    __gtype_name__ = 'GtkDemoTreeStore'
+
+    def __init__(self, *args):
+        super(DemoTreeStore, self).__init__(str, Demo, Pango.Style)
+
+        self._parent_nodes = {}
+
+        for filename in self._list_dir(DEMOCODEDIR):
+            fullpath = os.path.join(DEMOCODEDIR, filename)
+            initfile = os.path.join(os.path.dirname(fullpath), '__init__.py')
+
+            if fullpath != initfile and os.path.isfile(initfile) and fullpath.endswith('.py'):
+                parentname = os.path.dirname(os.path.relpath(fullpath, DEMOCODEDIR))
+
+                if parentname:
+                    parent = self._get_parent_node(parentname)
+                else:
+                    parent = None
+
+                demo = Demo.new_from_file(fullpath)
+                self.append(parent, (demo.title, demo, Pango.Style.NORMAL))
+
+    def _list_dir(self, path):
+        demo_file_list = []
+
+        for filename in os.listdir(path):
+            fullpath = os.path.join(path, filename)
+
+            if os.path.isdir(fullpath):
+                demo_file_list.extend(self._list_dir(fullpath))
+            elif os.path.isfile(fullpath):
+                demo_file_list.append(fullpath)
+
+        return sorted(demo_file_list, key=str.lower)
+
+    def _get_parent_node(self, name):
+        if name not in self._parent_nodes.keys():
+            node = self.append(None, (name, None, Pango.Style.NORMAL))
+            self._parent_nodes[name] = node
+
+        return self._parent_nodes[name]
+
+
+class GtkDemoApp(Gtk.Application):
+    __gtype_name__ = 'GtkDemoWindow'
+
+    def __init__(self):
+        super(GtkDemoApp, self).__init__(application_id='org.gnome.pygobject.gtkdemo')
+
+        # Use a GResource to hold the CSS files. Resource bundles are created by
+        # the glib-compile-resources program shipped with Glib which takes an xml
+        # file that describes the bundle, and a set of files that the xml
+        # references. These are combined into a binary resource bundle.
+        base_path = os.path.abspath(os.path.dirname(__file__))
+        resource_path = os.path.join(base_path, 'demos/data/demo.gresource')
+        resource = Gio.Resource.load(resource_path)
+
+        # FIXME: method register() should be without the underscore
+        # FIXME: see https://bugzilla.gnome.org/show_bug.cgi?id=684319
+        # Once the resource has been globally registered it can be used
+        # throughout the application.
+        resource._register()
+
+    def on_activate(self, app):
+        self.window = Gtk.ApplicationWindow.new(self)
+        self.window.set_title('PyGObject GTK+ Code Demos')
+        self.window.set_default_size(600, 400)
+        self.setup_default_icon()
+
+        self.header_bar = Gtk.HeaderBar(show_close_button=True,
+                                        subtitle='Foobar')
+        self.window.set_titlebar(self.header_bar)
+
+        stack = Gtk.Stack(transition_type=Gtk.StackTransitionType.SLIDE_LEFT_RIGHT,
+                          homogeneous=True)
+        switcher = Gtk.StackSwitcher(stack=stack, halign=Gtk.Align.CENTER)
+
+        self.header_bar.set_custom_title(switcher)
+
+        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
+                       homogeneous=False,
+                       spacing=0)
+        self.window.add(hbox)
+
+        tree = self.create_tree()
+        hbox.pack_start(child=tree, expand=False, fill=False, padding=0)
+        hbox.pack_start(child=stack, expand=True, fill=True, padding=0)
+
+        text_widget, info_buffer = self.create_text_view()
+        stack.add_titled(text_widget, name='info', title='Info')
+
+        self.info_buffer = info_buffer
+        self.info_buffer.create_tag('title', font='Sans 18')
+
+        text_widget, self.source_buffer = self.create_source_view()
+        stack.add_titled(text_widget, name='source', title='Source')
+
+        self.window.show_all()
+
+        self.selection_cb(self.tree_view.get_selection(),
+                          self.tree_view.get_model())
+
+    def find_file(self, base=''):
+        dir = os.path.join(DEMOCODEDIR, 'data')
+        logo_file = os.path.join(dir, 'gtk-logo-rgb.gif')
+        base_file = os.path.join(dir, base)
+
+        if (GLib.file_test(logo_file, GLib.FileTest.EXISTS) and
+                GLib.file_test(base_file, GLib.FileTest.EXISTS)):
+            return base_file
+        else:
+            filename = os.path.join(DEMOCODEDIR, base)
+
+            if GLib.file_test(filename, GLib.FileTest.EXISTS):
+                return filename
+
+            # can't find the file
+            raise IOError('Cannot find demo data file "%s"' % base)
+
+    def setup_default_icon(self):
+        filename = self.find_file('gtk-logo-rgb.gif')
+        pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
+        transparent = pixbuf.add_alpha(True, 0xff, 0xff, 0xff)
+        list = []
+        list.append(transparent)
+        Gtk.Window.set_default_icon_list(list)
+
+    def selection_cb(self, selection, model):
+        sel = selection.get_selected()
+        if sel == ():
+            return
+
+        treeiter = sel[1]
+        title = model.get_value(treeiter, 0)
+        demo = model.get_value(treeiter, 1)
+
+        if demo is None:
+            return
+
+        # Split into paragraphs based on double newlines and use
+        # textwrap to strip out all other formatting whitespace
+        description = ''
+        for paragraph in demo.module.description.split('\n\n'):
+            description += '\n'.join(textwrap.wrap(paragraph, 99999))
+            description += '\n\n'  # Add paragraphs back in
+
+        f = codecs.open(demo.filename, 'rU', 'utf-8')
+        code = f.read()
+        f.close()
+
+        # output and style the title
+        (start, end) = self.info_buffer.get_bounds()
+        self.info_buffer.delete(start, end)
+        (start, end) = self.source_buffer.get_bounds()
+        self.source_buffer.delete(start, end)
+
+        start = self.info_buffer.get_iter_at_offset(0)
+        end = start.copy()
+        self.info_buffer.insert(end, title)
+        start = end.copy()
+        start.backward_chars(len(title))
+        self.info_buffer.apply_tag_by_name('title', start, end)
+        self.info_buffer.insert(end, '\n')
+
+        # output the description
+        self.info_buffer.insert(end, description)
+
+        # output the code
+        start = self.source_buffer.get_iter_at_offset(0)
+        end = start.copy()
+        self.source_buffer.insert(end, code)
+
+    def row_activated_cb(self, view, path, col, store):
+        iter = store.get_iter(path)
+        demo = store.get_value(iter, 1)
+
+        if demo is not None:
+            store.set_value(iter, 2, Pango.Style.ITALIC)
+            try:
+                demo.module.main(self)
+            finally:
+                store.set_value(iter, 2, Pango.Style.NORMAL)
+
+    def create_tree(self):
+        tree_store = DemoTreeStore()
+        tree_view = Gtk.TreeView()
+        self.tree_view = tree_view
+        tree_view.set_model(tree_store)
+        selection = tree_view.get_selection()
+        selection.set_mode(Gtk.SelectionMode.BROWSE)
+        tree_view.set_size_request(200, -1)
+
+        cell = Gtk.CellRendererText()
+        column = Gtk.TreeViewColumn(title='Widget (double click for demo)',
+                                    cell_renderer=cell,
+                                    text=0,
+                                    style=2)
+
+        first_iter = tree_store.get_iter_first()
+        if first_iter is not None:
+            selection.select_iter(first_iter)
+
+        selection.connect('changed', self.selection_cb, tree_store)
+        tree_view.connect('row_activated', self.row_activated_cb, tree_store)
+
+        tree_view.append_column(column)
+
+        tree_view.expand_all()
+        tree_view.set_headers_visible(False)
+        scrolled_window = Gtk.ScrolledWindow(hadjustment=None,
+                                             vadjustment=None)
+        scrolled_window.set_policy(Gtk.PolicyType.NEVER,
+                                   Gtk.PolicyType.AUTOMATIC)
+
+        scrolled_window.add(tree_view)
+
+        label = Gtk.Label(label='Widget (double click for demo)')
+
+        box = Gtk.Notebook()
+        box.append_page(scrolled_window, label)
+
+        tree_view.grab_focus()
+
+        return box
+
+    def create_scrolled_window(self):
+        scrolled_window = Gtk.ScrolledWindow(hadjustment=None,
+                                             vadjustment=None)
+        scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,
+                                   Gtk.PolicyType.AUTOMATIC)
+        scrolled_window.set_shadow_type(Gtk.ShadowType.IN)
+        return scrolled_window
+
+    def create_text_view(self):
+        text_view = Gtk.TextView()
+        buffer = Gtk.TextBuffer()
+
+        text_view.set_buffer(buffer)
+        text_view.set_editable(False)
+        text_view.set_cursor_visible(False)
+
+        scrolled_window = self.create_scrolled_window()
+        scrolled_window.add(text_view)
+
+        text_view.set_wrap_mode(Gtk.WrapMode.WORD)
+        text_view.set_pixels_above_lines(2)
+        text_view.set_pixels_below_lines(2)
+
+        return scrolled_window, buffer
+
+    def create_source_view(self):
+        font_desc = Pango.FontDescription('monospace 11')
+
+        if GtkSource:
+            lang_mgr = GtkSource.LanguageManager()
+            lang = lang_mgr.get_language('python')
+
+            buffer = GtkSource.Buffer()
+            buffer.set_language(lang)
+            buffer.set_highlight_syntax(True)
+
+            view = GtkSource.View()
+            view.set_buffer(buffer)
+            view.set_show_line_numbers(True)
+
+            scrolled_window = self.create_scrolled_window()
+            scrolled_window.add(view)
+
+        else:
+            scrolled_window, buffer = self.create_text_view()
+            view = scrolled_window.get_child()
+
+        view.modify_font(font_desc)
+        view.set_wrap_mode(Gtk.WrapMode.NONE)
+        return scrolled_window, buffer
+
+    def run(self, argv):
+        self.connect('activate', self.on_activate)
+        return super(GtkDemoApp, self).run(argv)
+
+
+def main(argv):
+    """Entry point for demo manager"""
+    app = GtkDemoApp()
+    return app.run(argv)
+
+
+if __name__ == '__main__':
+    SystemExit(main(sys.argv))
diff --git a/examples/demo/demos/Css/__init__.py b/examples/demo/demos/Css/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/examples/demo/demos/Css/css_accordion.py b/examples/demo/demos/Css/css_accordion.py
new file mode 100644 (file)
index 0000000..2b7cddc
--- /dev/null
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2013 Gian Mario Tagliaretti <gianmt@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "CSS Accordion"
+description = """
+A simple accordion demo written using CSS transitions and multiple backgrounds.
+"""
+
+
+from gi.repository import Gtk, Gio
+
+
+class CSSAccordionApp:
+    def __init__(self):
+        window = Gtk.Window()
+        window.set_title('CSS Accordion')
+        window.set_default_size(600, 300)
+        window.set_border_width(10)
+        window.connect('destroy', Gtk.main_quit)
+
+        hbox = Gtk.Box(homogeneous=False, spacing=2,
+                       orientation=Gtk.Orientation.HORIZONTAL)
+        hbox.set_halign(Gtk.Align.CENTER)
+        hbox.set_valign(Gtk.Align.CENTER)
+        window.add(hbox)
+
+        for label in ('This', 'Is', 'A', 'CSS', 'Accordion', ':-)'):
+            hbox.add(Gtk.Button(label=label))
+
+        bytes = Gio.resources_lookup_data("/css_accordion/css_accordion.css", 0)
+
+        provider = Gtk.CssProvider()
+        provider.load_from_data(bytes.get_data())
+
+        self.apply_css(window, provider)
+
+        window.show_all()
+
+    def apply_css(self, widget, provider):
+        Gtk.StyleContext.add_provider(widget.get_style_context(),
+                                      provider,
+                                      Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+        if isinstance(widget, Gtk.Container):
+            widget.forall(self.apply_css, provider)
+
+
+def main(demoapp=None):
+    CSSAccordionApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    import os
+    base_path = os.path.abspath(os.path.dirname(__file__))
+    resource_path = os.path.join(base_path, '../data/demo.gresource')
+    resource = Gio.Resource.load(resource_path)
+
+    # FIXME: method register() should be without the underscore
+    # FIXME: see https://bugzilla.gnome.org/show_bug.cgi?id=684319
+    resource._register()
+    main()
diff --git a/examples/demo/demos/Css/css_basics.py b/examples/demo/demos/Css/css_basics.py
new file mode 100644 (file)
index 0000000..18c3d12
--- /dev/null
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2013 Gian Mario Tagliaretti <gianmt@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "CSS Basics"
+description = """
+Gtk themes are written using CSS. Every widget is build of multiple items
+that you can style very similarly to a regular website.
+"""
+
+import os
+from gi.repository import Gtk, Gdk, Pango, Gio, GLib
+
+
+class CSSBasicsApp:
+    def __init__(self, demoapp):
+        self.demoapp = demoapp
+        #: Store the last successful parsing of the css so we can revert
+        #: this in case of an error.
+        self.last_good_text = ''
+        #: Set when we receive a parsing-error callback. This is needed
+        #: to handle logic after a parsing-error callback which does not raise
+        #: an exception with provider.load_from_data()
+        self.last_error_code = 0
+
+        self.window = Gtk.Window()
+        self.window.set_title('CSS Basics')
+        self.window.set_default_size(400, 300)
+        self.window.set_border_width(10)
+        self.window.connect('destroy', lambda w: Gtk.main_quit())
+
+        self.infobar = Gtk.InfoBar()
+        self.infolabel = Gtk.Label()
+        self.infobar.get_content_area().pack_start(self.infolabel, False, False, 0)
+        self.infobar.set_message_type(Gtk.MessageType.WARNING)
+
+        scrolled = Gtk.ScrolledWindow()
+        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        box.pack_start(scrolled, expand=True, fill=True, padding=0)
+        box.pack_start(self.infobar, expand=False, fill=True, padding=0)
+        self.window.add(box)
+
+        provider = Gtk.CssProvider()
+
+        buffer = Gtk.TextBuffer()
+        buffer.create_tag(tag_name="warning", underline=Pango.Underline.SINGLE)
+        buffer.create_tag(tag_name="error", underline=Pango.Underline.ERROR)
+        buffer.connect("changed", self.css_text_changed, provider)
+
+        provider.connect("parsing-error", self.show_parsing_error, buffer)
+
+        textview = Gtk.TextView()
+        textview.set_buffer(buffer)
+        scrolled.add(textview)
+
+        bytes = Gio.resources_lookup_data("/css_basics/css_basics.css", 0)
+        buffer.set_text(bytes.get_data().decode('utf-8'))
+
+        self.apply_css(self.window, provider)
+        self.window.show_all()
+        self.infobar.hide()
+
+    def apply_css(self, widget, provider):
+        Gtk.StyleContext.add_provider(widget.get_style_context(),
+                                      provider,
+                                      Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+        if isinstance(widget, Gtk.Container):
+            widget.forall(self.apply_css, provider)
+
+    def show_parsing_error(self, provider, section, error, buffer):
+        start = buffer.get_iter_at_line_index(section.get_start_line(),
+                                              section.get_start_position())
+
+        end = buffer.get_iter_at_line_index(section.get_end_line(),
+                                            section.get_end_position())
+
+        if error.code == Gtk.CssProviderError.DEPRECATED:
+            tag_name = "warning"
+        else:
+            tag_name = "error"
+        self.last_error_code = error.code
+
+        self.infolabel.set_text(error.message)
+        self.infobar.show_all()
+
+        buffer.apply_tag_by_name(tag_name, start, end)
+
+    def css_text_changed(self, buffer, provider):
+        start = buffer.get_start_iter()
+        end = buffer.get_end_iter()
+        buffer.remove_all_tags(start, end)
+
+        text = buffer.get_text(start, end, False).encode('utf-8')
+
+        # Ignore CSS errors as they are shown by highlighting
+        try:
+            provider.load_from_data(text)
+        except GLib.GError as e:
+            if e.domain != 'gtk-css-provider-error-quark':
+                raise e
+
+        # If the parsing-error callback is ever run (even in the case of warnings)
+        # load the last good css text that ran without any warnings. Otherwise
+        # we may have a discrepancy in "last_good_text" vs the current buffer
+        # causing section.get_start_position() to give back an invalid position
+        # for the editor buffer.
+        if self.last_error_code:
+            provider.load_from_data(self.last_good_text)
+            self.last_error_code = 0
+        else:
+            self.last_good_text = text
+            self.infobar.hide()
+
+        Gtk.StyleContext.reset_widgets(Gdk.Screen.get_default())
+
+
+def main(demoapp=None):
+    CSSBasicsApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    base_path = os.path.abspath(os.path.dirname(__file__))
+    resource_path = os.path.join(base_path, '../data/demo.gresource')
+    resource = Gio.Resource.load(resource_path)
+
+    # FIXME: method register() should be without the underscore
+    # FIXME: see https://bugzilla.gnome.org/show_bug.cgi?id=684319
+    resource._register()
+    main()
diff --git a/examples/demo/demos/Css/css_multiplebgs.py b/examples/demo/demos/Css/css_multiplebgs.py
new file mode 100644 (file)
index 0000000..9e1b011
--- /dev/null
@@ -0,0 +1,187 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2013 Gian Mario Tagliaretti <gianmt@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "CSS Theming/Multiple Backgrounds"
+description = """
+Gtk themes are written using CSS. Every widget is build of multiple items
+that you can style very similarly to a regular website.
+"""
+
+from gi.repository import Gtk, Gdk, Pango, Gio, GLib
+
+
+class CSSMultiplebgsApp:
+    def __init__(self, demoapp):
+        self.demoapp = demoapp
+        #: Store the last successful parsing of the css so we can revert
+        #: this in case of an error.
+        self.last_good_text = ''
+        #: Set when we receive a parsing-error callback. This is needed
+        #: to handle logic after a parsing-error callback which does not raise
+        #: an exception with provider.load_from_data()
+        self.last_error_code = 0
+
+        self.window = Gtk.Window()
+        self.window.set_title('CSS Multiplebgs')
+        self.window.set_default_size(400, 300)
+        self.window.set_border_width(10)
+        self.window.connect('destroy', lambda w: Gtk.main_quit())
+
+        overlay = Gtk.Overlay()
+        overlay.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK |
+                           Gdk.EventMask.LEAVE_NOTIFY_MASK |
+                           Gdk.EventMask.POINTER_MOTION_MASK)
+
+        self.infobar = Gtk.InfoBar()
+        self.infolabel = Gtk.Label()
+        self.infobar.get_content_area().pack_start(self.infolabel, False, False, 0)
+        self.infobar.set_message_type(Gtk.MessageType.WARNING)
+
+        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        box.pack_start(overlay, expand=True, fill=True, padding=0)
+        box.pack_start(self.infobar, expand=False, fill=True, padding=0)
+        self.window.add(box)
+
+        canvas = Gtk.DrawingArea()
+        canvas.set_name("canvas")
+        canvas.connect("draw", self.drawing_area_draw)
+        overlay.add(canvas)
+
+        button = Gtk.Button()
+        button.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK |
+                          Gdk.EventMask.LEAVE_NOTIFY_MASK |
+                          Gdk.EventMask.POINTER_MOTION_MASK)
+        button.set_name("bricks-button")
+        button.set_halign(Gtk.Align.CENTER)
+        button.set_valign(Gtk.Align.CENTER)
+        button.set_size_request(250, 84)
+        overlay.add_overlay(button)
+
+        paned = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL)
+        overlay.add_overlay(paned)
+
+        # We need a filler so we get a handle
+        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        paned.add(box)
+
+        buffer = Gtk.TextBuffer()
+        buffer.create_tag(tag_name="warning", underline=Pango.Underline.SINGLE)
+        buffer.create_tag(tag_name="error", underline=Pango.Underline.ERROR)
+
+        provider = Gtk.CssProvider()
+
+        buffer.connect("changed", self.css_text_changed, provider)
+        provider.connect("parsing-error", self.show_parsing_error, buffer)
+
+        textview = Gtk.TextView()
+        textview.set_buffer(buffer)
+
+        scrolled = Gtk.ScrolledWindow()
+        scrolled.add(textview)
+        paned.add(scrolled)
+
+        bytes = Gio.resources_lookup_data("/css_multiplebgs/css_multiplebgs.css", 0)
+        buffer.set_text(bytes.get_data().decode('utf-8'))
+
+        self.apply_css(self.window, provider)
+        self.window.show_all()
+        self.infobar.hide()
+
+    def drawing_area_draw(self, widget, cairo_t):
+        context = widget.get_style_context()
+        Gtk.render_background(context, cairo_t, 0, 0,
+                              widget.get_allocated_width(),
+                              widget.get_allocated_height())
+
+        Gtk.render_frame(context, cairo_t, 0, 0,
+                         widget.get_allocated_width(),
+                         widget.get_allocated_height())
+
+    def apply_css(self, widget, provider):
+        Gtk.StyleContext.add_provider(widget.get_style_context(),
+                                      provider,
+                                      Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+        if isinstance(widget, Gtk.Container):
+            widget.forall(self.apply_css, provider)
+
+    def show_parsing_error(self, provider, section, error, buffer):
+        start = buffer.get_iter_at_line_index(section.get_start_line(),
+                                              section.get_start_position())
+
+        end = buffer.get_iter_at_line_index(section.get_end_line(),
+                                            section.get_end_position())
+
+        if error.code == Gtk.CssProviderError.DEPRECATED:
+            tag_name = "warning"
+        else:
+            tag_name = "error"
+        self.last_error_code = error.code
+
+        self.infolabel.set_text(error.message)
+        self.infobar.show_all()
+
+        buffer.apply_tag_by_name(tag_name, start, end)
+
+    def css_text_changed(self, buffer, provider):
+        start = buffer.get_start_iter()
+        end = buffer.get_end_iter()
+        buffer.remove_all_tags(start, end)
+
+        text = buffer.get_text(start, end, False).encode('utf-8')
+
+        # Ignore CSS errors as they are shown by highlighting
+        try:
+            provider.load_from_data(text)
+        except GLib.GError as e:
+            if e.domain != 'gtk-css-provider-error-quark':
+                raise e
+
+        # If the parsing-error callback is ever run (even in the case of warnings)
+        # load the last good css text that ran without any warnings. Otherwise
+        # we may have a discrepancy in "last_good_text" vs the current buffer
+        # causing section.get_start_position() to give back an invalid position
+        # for the editor buffer.
+        if self.last_error_code:
+            provider.load_from_data(self.last_good_text)
+            self.last_error_code = 0
+        else:
+            self.last_good_text = text
+            self.infobar.hide()
+
+        Gtk.StyleContext.reset_widgets(Gdk.Screen.get_default())
+
+
+def main(demoapp=None):
+    CSSMultiplebgsApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    import os
+    base_path = os.path.abspath(os.path.dirname(__file__))
+    resource_path = os.path.join(base_path, '../data/demo.gresource')
+    resource = Gio.Resource.load(resource_path)
+
+    # FIXME: method register() should be without the underscore
+    # FIXME: see https://bugzilla.gnome.org/show_bug.cgi?id=684319
+    resource._register()
+    main()
diff --git a/examples/demo/demos/Entry/__init__.py b/examples/demo/demos/Entry/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/examples/demo/demos/Entry/entry_buffer.py b/examples/demo/demos/Entry/entry_buffer.py
new file mode 100644 (file)
index 0000000..f0c04a4
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Entry Buffer"
+description = """
+Gtk.EntryBuffer provides the text content in a Gtk.Entry.
+"""
+
+
+from gi.repository import Gtk
+
+
+class EntryBufferApp:
+    def __init__(self):
+        self.window = Gtk.Dialog(title='Gtk.EntryBuffer')
+        self.window.add_buttons(Gtk.STOCK_CLOSE, Gtk.ResponseType.NONE)
+        self.window.connect('response', self.destroy)
+        self.window.connect('destroy', lambda x: Gtk.main_quit())
+        self.window.set_resizable(False)
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=0)
+        self.window.get_content_area().pack_start(vbox, True, True, 0)
+        vbox.set_border_width(5)
+
+        label = Gtk.Label()
+        label.set_markup('Entries share a buffer. Typing in one is reflected in the other.')
+        vbox.pack_start(label, False, False, 0)
+
+        # create a buffer
+        buffer = Gtk.EntryBuffer()
+
+        # create our first entry
+        entry = Gtk.Entry(buffer=buffer)
+        vbox.pack_start(entry, False, False, 0)
+
+        # create the second entry
+        entry = Gtk.Entry(buffer=buffer)
+        entry.set_visibility(False)
+        vbox.pack_start(entry, False, False, 0)
+
+        self.window.show_all()
+
+    def destroy(self, *args):
+        self.window.destroy()
+        Gtk.main_quit()
+
+
+def main(demoapp=None):
+    EntryBufferApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/Entry/entry_completion.py b/examples/demo/demos/Entry/entry_completion.py
new file mode 100644 (file)
index 0000000..107c45a
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Entry Completion"
+description = """
+Gtk.EntryCompletion provides a mechanism for adding support for
+completion in Gtk.Entry.
+"""
+
+
+from gi.repository import Gtk
+
+
+class EntryBufferApp:
+    def __init__(self):
+        self.window = Gtk.Dialog(title='Gtk.EntryCompletion')
+        self.window.add_buttons(Gtk.STOCK_CLOSE, Gtk.ResponseType.NONE)
+        self.window.connect('response', self.destroy)
+        self.window.connect('destroy', lambda x: Gtk.main_quit())
+        self.window.set_resizable(False)
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=0)
+        self.window.get_content_area().pack_start(vbox, True, True, 0)
+        vbox.set_border_width(5)
+
+        label = Gtk.Label()
+        label.set_markup('Completion demo, try writing <b>total</b> or <b>gnome</b> for example.')
+        vbox.pack_start(label, False, False, 0)
+
+        # create our entry
+        entry = Gtk.Entry()
+        vbox.pack_start(entry, False, False, 0)
+
+        # create the completion object
+        completion = Gtk.EntryCompletion()
+
+        # assign the completion to the entry
+        entry.set_completion(completion)
+
+        # create tree model and use it as the completion model
+        completion_model = self.create_completion_model()
+        completion.set_model(completion_model)
+
+        completion.set_text_column(0)
+
+        self.window.show_all()
+
+    def create_completion_model(self):
+        store = Gtk.ListStore(str)
+
+        store.append(['GNOME'])
+        store.append(['total'])
+        store.append(['totally'])
+
+        return store
+
+    def destroy(self, *args):
+        self.window.destroy()
+        Gtk.main_quit()
+
+
+def main(demoapp=None):
+    EntryBufferApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/Entry/search_entry.py b/examples/demo/demos/Entry/search_entry.py
new file mode 100644 (file)
index 0000000..793b81a
--- /dev/null
@@ -0,0 +1,254 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Search Entry"
+description = """GtkEntry allows to display icons and progress information.
+This demo shows how to use these features in a search entry.
+"""
+
+from gi.repository import Gtk, GObject
+
+(PIXBUF_COL,
+ TEXT_COL) = range(2)
+
+
+class SearchboxApp:
+    def __init__(self, demoapp):
+        self.demoapp = demoapp
+
+        self.window = Gtk.Dialog(title='Search Entry')
+        self.window.add_buttons(Gtk.STOCK_CLOSE, Gtk.ResponseType.NONE)
+
+        self.window.connect('response', lambda x, y: self.window.destroy())
+        self.window.connect('destroy', Gtk.main_quit)
+
+        content_area = self.window.get_content_area()
+
+        vbox = Gtk.VBox(spacing=5)
+        content_area.pack_start(vbox, True, True, 0)
+        vbox.set_border_width(5)
+
+        label = Gtk.Label()
+        label.set_markup('Search entry demo')
+        vbox.pack_start(label, False, False, 0)
+
+        hbox = Gtk.HBox(homogeneous=False, spacing=10)
+        hbox.set_border_width(0)
+        vbox.pack_start(hbox, True, True, 0)
+
+        # Create our entry
+        entry = Gtk.Entry()
+        hbox.pack_start(entry, False, False, 0)
+
+        # Create the find and cancel buttons
+        notebook = Gtk.Notebook()
+        self.notebook = notebook
+        notebook.set_show_tabs(False)
+        notebook.set_show_border(False)
+        hbox.pack_start(notebook, False, False, 0)
+
+        find_button = Gtk.Button(label='Find')
+        find_button.connect('clicked', self.start_search, entry)
+        notebook.append_page(find_button, None)
+        find_button.show()
+
+        cancel_button = Gtk.Button(label='Cancel')
+        cancel_button.connect('clicked', self.stop_search, entry)
+        notebook.append_page(cancel_button, None)
+        cancel_button.show()
+
+        # Set up the search icon
+        self.search_by_name(None, entry)
+
+        # Set up the clear icon
+        entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY,
+                                  Gtk.STOCK_CLEAR)
+        self.text_changed_cb(entry, None, find_button)
+
+        entry.connect('notify::text', self.text_changed_cb, find_button)
+
+        entry.connect('activate', self.activate_cb)
+
+        # Create the menu
+        menu = self.create_search_menu(entry)
+        entry.connect('icon-press', self.icon_press_cb, menu)
+
+        # FIXME: this should take None for the detach callback
+        #        but our callback implementation does not allow
+        #        it yet, so we pass in a noop callback
+        menu.attach_to_widget(entry, self.detach)
+
+        # add accessible alternatives for icon functionality
+        entry.connect('populate-popup', self.entry_populate_popup)
+
+        self.window.show_all()
+
+    def detach(self, *args):
+        pass
+
+    def show_find_button(self):
+        self.notebook.set_current_page(0)
+
+    def show_cancel_button(self):
+        self.notebook.set_current_page(1)
+
+    def search_progress(self, entry):
+        entry.progress_pulse()
+        return True
+
+    def search_progress_done(self, entry):
+        entry.set_progress_fraction(0.0)
+
+    def finish_search(self, button, entry):
+        self.show_find_button()
+        GObject.source_remove(self.search_progress_id)
+        self.search_progress_done(entry)
+        self.search_progress_id = 0
+
+        return False
+
+    def start_search_feedback(self, entry):
+        self.search_progress_id = GObject.timeout_add(100,
+                                                      self.search_progress,
+                                                      entry)
+
+        return False
+
+    def start_search(self, button, entry):
+        self.show_cancel_button()
+        self.search_progress_id = GObject.timeout_add_seconds(1,
+                                                              self.start_search_feedback,
+                                                              entry)
+        self.finish_search_id = GObject.timeout_add_seconds(15,
+                                                            self.finish_search,
+                                                            button)
+
+    def stop_search(self, button, entry):
+        GObject.source_remove(self.finish_search_id)
+        self.finish_search(button, entry)
+
+    def clear_entry_swapped(self, widget, entry):
+        self.clear_entry(entry)
+
+    def clear_entry(self, entry):
+        entry.set_text('')
+
+    def search_by_name(self, item, entry):
+        entry.set_icon_from_stock(Gtk.EntryIconPosition.PRIMARY,
+                                  Gtk.STOCK_FIND)
+        entry.set_icon_tooltip_text(Gtk.EntryIconPosition.PRIMARY,
+                                    'Search by name\n' +
+                                    'Click here to change the search type')
+
+    def search_by_description(self, item, entry):
+        entry.set_icon_from_stock(Gtk.EntryIconPosition.PRIMARY,
+                                  Gtk.STOCK_EDIT)
+        entry.set_icon_tooltip_text(Gtk.EntryIconPosition.PRIMARY,
+                                    'Search by description\n' +
+                                    'Click here to change the search type')
+
+    def search_by_file(self, item, entry):
+        entry.set_icon_from_stock(Gtk.EntryIconPosition.PRIMARY,
+                                  Gtk.STOCK_OPEN)
+        entry.set_icon_tooltip_text(Gtk.EntryIconPosition.PRIMARY,
+                                    'Search by file name\n' +
+                                    'Click here to change the search type')
+
+    def create_search_menu(self, entry):
+        menu = Gtk.Menu()
+
+        item = Gtk.ImageMenuItem.new_with_mnemonic('Search by _name')
+        image = Gtk.Image.new_from_stock(Gtk.STOCK_FIND, Gtk.IconSize.MENU)
+        item.set_image(image)
+        item.set_always_show_image(True)
+        item.connect('activate', self.search_by_name, entry)
+        menu.append(item)
+
+        item = Gtk.ImageMenuItem.new_with_mnemonic('Search by _description')
+        image = Gtk.Image.new_from_stock(Gtk.STOCK_EDIT, Gtk.IconSize.MENU)
+        item.set_image(image)
+        item.set_always_show_image(True)
+        item.connect('activate', self.search_by_description, entry)
+        menu.append(item)
+
+        item = Gtk.ImageMenuItem.new_with_mnemonic('Search by _file name')
+        image = Gtk.Image.new_from_stock(Gtk.STOCK_OPEN, Gtk.IconSize.MENU)
+        item.set_image(image)
+        item.set_always_show_image(True)
+        item.connect('activate', self.search_by_name, entry)
+        menu.append(item)
+
+        menu.show_all()
+
+        return menu
+
+    def icon_press_cb(self, entry, position, event, menu):
+        if position == Gtk.EntryIconPosition.PRIMARY:
+            menu.popup(None, None, None, None,
+                       event.button, event.time)
+        else:
+            self.clear_entry(entry)
+
+    def text_changed_cb(self, entry, pspec, button):
+        has_text = entry.get_text_length() > 0
+        entry.set_icon_sensitive(Gtk.EntryIconPosition.SECONDARY, has_text)
+        button.set_sensitive(has_text)
+
+    def activate_cb(self, entry, button):
+        if self.search_progress_id != 0:
+            return
+        self.start_search(button, entry)
+
+    def search_entry_destroyed(self, widget):
+        if self.finish_search_id != 0:
+            GObject.source_remove(self.finish_search_id)
+        if self.search_progress_id != 0:
+            GObject.source_remove(self.search_progress_id)
+
+        self.window = None
+
+    def entry_populate_popup(self, entry, menu):
+        has_text = entry.get_text_length() > 0
+
+        item = Gtk.SeparatorMenuItem()
+        item.show()
+        menu.append(item)
+
+        item = Gtk.MenuItem.new_with_mnemonic("C_lear")
+        item.show()
+        item.connect('activate', self.clear_entry_swapped, entry)
+        menu.append(item)
+        item.set_sensitive(has_text)
+
+        search_menu = self.create_search_menu(entry)
+        item = Gtk.MenuItem.new_with_label('Search by')
+        item.show()
+        item.set_submenu(search_menu)
+        menu.append(item)
+
+
+def main(demoapp=None):
+    SearchboxApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/IconView/__init__.py b/examples/demo/demos/IconView/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/examples/demo/demos/IconView/iconviewbasics.py b/examples/demo/demos/IconView/iconviewbasics.py
new file mode 100644 (file)
index 0000000..8cb71a8
--- /dev/null
@@ -0,0 +1,221 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Icon View Basics"
+description = """The GtkIconView widget is used to display and manipulate
+icons. It uses a GtkTreeModel for data storage, so the list store example might
+be helpful. We also use the Gio.File API to get the icons for each file type.
+"""
+
+
+import os
+
+from gi.repository import GLib, Gio, GdkPixbuf, Gtk
+
+
+class IconViewApp:
+    (COL_PATH,
+     COL_DISPLAY_NAME,
+     COL_PIXBUF,
+     COL_IS_DIRECTORY,
+     NUM_COLS) = range(5)
+
+    def __init__(self, demoapp):
+        self.pixbuf_lookup = {}
+
+        self.demoapp = demoapp
+
+        self.window = Gtk.Window()
+        self.window.set_title('Gtk.IconView demo')
+        self.window.set_default_size(650, 400)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        vbox = Gtk.VBox()
+        self.window.add(vbox)
+
+        tool_bar = Gtk.Toolbar()
+        vbox.pack_start(tool_bar, False, False, 0)
+
+        up_button = Gtk.ToolButton(stock_id=Gtk.STOCK_GO_UP)
+        up_button.set_is_important(True)
+        up_button.set_sensitive(False)
+        tool_bar.insert(up_button, -1)
+
+        home_button = Gtk.ToolButton(stock_id=Gtk.STOCK_HOME)
+        home_button.set_is_important(True)
+        tool_bar.insert(home_button, -1)
+
+        sw = Gtk.ScrolledWindow()
+        sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
+        sw.set_policy(Gtk.PolicyType.AUTOMATIC,
+                      Gtk.PolicyType.AUTOMATIC)
+
+        vbox.pack_start(sw, True, True, 0)
+
+        # create the store and fill it with content
+        self.parent_dir = '/'
+        store = self.create_store()
+        self.fill_store(store)
+
+        icon_view = Gtk.IconView(model=store)
+        icon_view.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
+        sw.add(icon_view)
+
+        # connect to the 'clicked' signal of the "Up" tool button
+        up_button.connect('clicked', self.up_clicked, store)
+
+        # connect to the 'clicked' signal of the "home" tool button
+        home_button.connect('clicked', self.home_clicked, store)
+
+        self.up_button = up_button
+        self.home_button = home_button
+
+        # we now set which model columns that correspond to the text
+        # and pixbuf of each item
+        icon_view.set_text_column(self.COL_DISPLAY_NAME)
+        icon_view.set_pixbuf_column(self.COL_PIXBUF)
+
+        # connect to the "item-activated" signal
+        icon_view.connect('item-activated', self.item_activated, store)
+        icon_view.grab_focus()
+
+        self.window.show_all()
+
+    def sort_func(self, store, a_iter, b_iter, user_data):
+        (a_name, a_is_dir) = store.get(a_iter,
+                                       self.COL_DISPLAY_NAME,
+                                       self.COL_IS_DIRECTORY)
+
+        (b_name, b_is_dir) = store.get(b_iter,
+                                       self.COL_DISPLAY_NAME,
+                                       self.COL_IS_DIRECTORY)
+
+        if a_name is None:
+            a_name = ''
+
+        if b_name is None:
+            b_name = ''
+
+        if (not a_is_dir) and b_is_dir:
+            return 1
+        elif a_is_dir and (not b_is_dir):
+            return -1
+        elif a_name > b_name:
+            return 1
+        elif a_name < b_name:
+            return -1
+        else:
+            return 0
+
+    def up_clicked(self, item, store):
+        self.parent_dir = os.path.split(self.parent_dir)[0]
+        self.fill_store(store)
+        # de-sensitize the up button if we are at the root
+        self.up_button.set_sensitive(self.parent_dir != '/')
+
+    def home_clicked(self, item, store):
+        self.parent_dir = GLib.get_home_dir()
+        self.fill_store(store)
+
+        # Sensitize the up button
+        self.up_button.set_sensitive(True)
+
+    def item_activated(self, icon_view, tree_path, store):
+        iter_ = store.get_iter(tree_path)
+        (path, is_dir) = store.get(iter_, self.COL_PATH, self.COL_IS_DIRECTORY)
+        if not is_dir:
+            return
+
+        self.parent_dir = path
+        self.fill_store(store)
+
+        self.up_button.set_sensitive(True)
+
+    def create_store(self):
+        store = Gtk.ListStore(str, str, GdkPixbuf.Pixbuf, bool)
+
+        # set sort column and function
+        store.set_default_sort_func(self.sort_func)
+        store.set_sort_column_id(-1, Gtk.SortType.ASCENDING)
+
+        return store
+
+    def file_to_icon_pixbuf(self, path):
+        pixbuf = None
+
+        # get the theme icon
+        f = Gio.file_new_for_path(path)
+        info = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_ICON,
+                            Gio.FileQueryInfoFlags.NONE,
+                            None)
+        gicon = info.get_icon()
+
+        # check to see if it is an image format we support
+        for format in GdkPixbuf.Pixbuf.get_formats():
+            for mime_type in format.get_mime_types():
+                content_type = Gio.content_type_from_mime_type(mime_type)
+                if content_type is not None:
+                    break
+
+            format_gicon = Gio.content_type_get_icon(content_type)
+            if format_gicon.equal(gicon):
+                gicon = f.icon_new()
+                break
+
+        if gicon in self.pixbuf_lookup:
+            return self.pixbuf_lookup[gicon]
+
+        if isinstance(gicon, Gio.ThemedIcon):
+            names = gicon.get_names()
+            icon_theme = Gtk.IconTheme.get_default()
+            for name in names:
+                try:
+                    pixbuf = icon_theme.load_icon(name, 64, 0)
+                    break
+                except GLib.GError:
+                    pass
+
+            self.pixbuf_lookup[gicon] = pixbuf
+
+        elif isinstance(gicon, Gio.FileIcon):
+            icon_file = gicon.get_file()
+            path = icon_file.get_path()
+            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path, 72, 72)
+            self.pixbuf_lookup[gicon] = pixbuf
+
+        return pixbuf
+
+    def fill_store(self, store):
+        store.clear()
+        for name in os.listdir(self.parent_dir):
+            path = os.path.join(self.parent_dir, name)
+            is_dir = os.path.isdir(path)
+            pixbuf = self.file_to_icon_pixbuf(path)
+            store.append((path, name, pixbuf, is_dir))
+
+
+def main(demoapp=None):
+    IconViewApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/IconView/iconviewedit.py b/examples/demo/demos/IconView/iconviewedit.py
new file mode 100644 (file)
index 0000000..85dfa93
--- /dev/null
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Editing and Drag-and-Drop"
+description = """The GtkIconView widget supports Editing and Drag-and-Drop.
+This example also demonstrates using the generic GtkCellLayout interface to set
+up cell renderers in an icon view.
+"""
+
+from gi.repository import Gtk, Gdk, GdkPixbuf
+
+
+class IconviewEditApp:
+    COL_TEXT = 0
+    NUM_COLS = 1
+
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Editing and Drag-and-Drop')
+        self.window.set_border_width(8)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        store = Gtk.ListStore(str)
+        colors = ['Red', 'Green', 'Blue', 'Yellow']
+        store.clear()
+        for c in colors:
+            store.append([c])
+
+        icon_view = Gtk.IconView(model=store)
+        icon_view.set_selection_mode(Gtk.SelectionMode.SINGLE)
+        icon_view.set_item_orientation(Gtk.Orientation.HORIZONTAL)
+        icon_view.set_columns(2)
+        icon_view.set_reorderable(True)
+
+        renderer = Gtk.CellRendererPixbuf()
+        icon_view.pack_start(renderer, True)
+        icon_view.set_cell_data_func(renderer,
+                                     self.set_cell_color,
+                                     None)
+
+        renderer = Gtk.CellRendererText()
+        icon_view.pack_start(renderer, True)
+        renderer.props.editable = True
+        renderer.connect('edited', self.edited, icon_view)
+        icon_view.add_attribute(renderer, 'text', self.COL_TEXT)
+
+        self.window.add(icon_view)
+
+        self.window.show_all()
+
+    def set_cell_color(self, cell_layout, cell, tree_model, iter_, icon_view):
+
+        # FIXME return single element instead of tuple
+        text = tree_model.get(iter_, self.COL_TEXT)[0]
+        color = Gdk.color_parse(text)
+        pixel = 0
+        if color is not None:
+            pixel = ((color.red >> 8) << 24 |
+                     (color.green >> 8) << 16 |
+                     (color.blue >> 8) << 8)
+
+        pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, 24, 24)
+        pixbuf.fill(pixel)
+
+        cell.props.pixbuf = pixbuf
+
+    def edited(self, cell, path_string, text, icon_view):
+        model = icon_view.get_model()
+        path = Gtk.TreePath(path_string)
+
+        iter_ = model.get_iter(path)
+        model.set_row(iter_, [text])
+
+
+def main(demoapp=None):
+    IconviewEditApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/TreeView/__init__.py b/examples/demo/demos/TreeView/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/examples/demo/demos/TreeView/liststore.py b/examples/demo/demos/TreeView/liststore.py
new file mode 100644 (file)
index 0000000..4b3daa1
--- /dev/null
@@ -0,0 +1,212 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "List Store"
+description = """
+The GtkListStore is used to store data in list form, to be used later on by a
+GtkTreeView to display it. This demo builds a simple GtkListStore and displays
+it. See the Stock Browser demo for a more advanced example.
+"""
+
+
+from gi.repository import Gtk, GObject, GLib
+
+
+class Bug:
+    def __init__(self, is_fixed, number, severity, description):
+        self.is_fixed = is_fixed
+        self.number = number
+        self.severity = severity
+        self.description = description
+
+
+# initial data we use to fill in the store
+data = [Bug(False, 60482, "Normal", "scrollable notebooks and hidden tabs"),
+        Bug(False, 60620, "Critical", "gdk_window_clear_area (gdkwindow-win32.c) is not thread-safe"),
+        Bug(False, 50214, "Major", "Xft support does not clean up correctly"),
+        Bug(True, 52877, "Major", "GtkFileSelection needs a refresh method. "),
+        Bug(False, 56070, "Normal", "Can't click button after setting in sensitive"),
+        Bug(True, 56355, "Normal", "GtkLabel - Not all changes propagate correctly"),
+        Bug(False, 50055, "Normal", "Rework width/height computations for TreeView"),
+        Bug(False, 58278, "Normal", "gtk_dialog_set_response_sensitive () doesn't work"),
+        Bug(False, 55767, "Normal", "Getters for all setters"),
+        Bug(False, 56925, "Normal", "Gtkcalender size"),
+        Bug(False, 56221, "Normal", "Selectable label needs right-click copy menu"),
+        Bug(True, 50939, "Normal", "Add shift clicking to GtkTextView"),
+        Bug(False, 6112, "Enhancement", "netscape-like collapsable toolbars"),
+        Bug(False, 1, "Normal", "First bug :=)")]
+
+
+class ListStoreApp:
+    (COLUMN_FIXED,
+     COLUMN_NUMBER,
+     COLUMN_SEVERITY,
+     COLUMN_DESCRIPTION,
+     COLUMN_PULSE,
+     COLUMN_ICON,
+     COLUMN_ACTIVE,
+     COLUMN_SENSITIVE,
+     NUM_COLUMNS) = range(9)
+
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Gtk.ListStore Demo')
+        self.window.connect('destroy', Gtk.main_quit)
+
+        vbox = Gtk.VBox(spacing=8)
+        self.window.add(vbox)
+
+        label = Gtk.Label(label='This is the bug list (note: not based on real data, it would be nice to have a nice ODBC interface to bugzilla or so, though).')
+        vbox.pack_start(label, False, False, 0)
+
+        sw = Gtk.ScrolledWindow()
+        sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
+        sw.set_policy(Gtk.PolicyType.NEVER,
+                      Gtk.PolicyType.AUTOMATIC)
+        vbox.pack_start(sw, True, True, 0)
+
+        self.create_model()
+        treeview = Gtk.TreeView(model=self.model)
+        treeview.set_rules_hint(True)
+        treeview.set_search_column(self.COLUMN_DESCRIPTION)
+        sw.add(treeview)
+
+        self.add_columns(treeview)
+
+        self.window.set_default_size(280, 250)
+        self.window.show_all()
+
+        self.window.connect('delete-event', self.window_closed)
+        self.timeout = GLib.timeout_add(80, self.spinner_timeout)
+
+    def window_closed(self, window, event):
+        if self.timeout != 0:
+            GLib.source_remove(self.timeout)
+
+    def spinner_timeout(self):
+        if self.model is None:
+            return False
+
+        iter_ = self.model.get_iter_first()
+        pulse = self.model.get(iter_, self.COLUMN_PULSE)[0]
+        if pulse == 999999999:
+            pulse = 0
+        else:
+            pulse += 1
+
+        self.model.set_value(iter_, self.COLUMN_PULSE, pulse)
+        self.model.set_value(iter_, self.COLUMN_ACTIVE, True)
+
+        return True
+
+    def create_model(self):
+        self.model = Gtk.ListStore(bool,
+                                   GObject.TYPE_INT,
+                                   str,
+                                   str,
+                                   GObject.TYPE_INT,
+                                   str,
+                                   bool,
+                                   bool)
+
+        col = 0
+        for bug in data:
+            if col == 1 or col == 3:
+                icon_name = 'battery-critical-charging-symbolic'
+            else:
+                icon_name = ''
+            if col == 3:
+                is_sensitive = False
+            else:
+                is_sensitive = True
+
+            self.model.append([bug.is_fixed,
+                               bug.number,
+                               bug.severity,
+                               bug.description,
+                               0,
+                               icon_name,
+                               False,
+                               is_sensitive])
+            col += 1
+
+    def add_columns(self, treeview):
+        model = treeview.get_model()
+
+        # column for is_fixed toggle
+        renderer = Gtk.CellRendererToggle()
+        renderer.connect('toggled', self.is_fixed_toggled, model)
+
+        column = Gtk.TreeViewColumn("Fixed?", renderer,
+                                    active=self.COLUMN_FIXED)
+        column.set_fixed_width(50)
+        column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
+        treeview.append_column(column)
+
+        # column for severities
+        renderer = Gtk.CellRendererText()
+        column = Gtk.TreeViewColumn("Severity", renderer,
+                                    text=self.COLUMN_SEVERITY)
+        column.set_sort_column_id(self.COLUMN_SEVERITY)
+        treeview.append_column(column)
+
+        # column for description
+        renderer = Gtk.CellRendererText()
+        column = Gtk.TreeViewColumn("Description", renderer,
+                                    text=self.COLUMN_DESCRIPTION)
+        column.set_sort_column_id(self.COLUMN_DESCRIPTION)
+        treeview.append_column(column)
+
+        # column for spinner
+        renderer = Gtk.CellRendererSpinner()
+        column = Gtk.TreeViewColumn("Spinning", renderer,
+                                    pulse=self.COLUMN_PULSE,
+                                    active=self.COLUMN_ACTIVE)
+        column.set_sort_column_id(self.COLUMN_PULSE)
+        treeview.append_column(column)
+
+        # column for symbolic icon
+        renderer = Gtk.CellRendererPixbuf()
+        renderer.props.follow_state = True
+        column = Gtk.TreeViewColumn("Symbolic icon", renderer,
+                                    icon_name=self.COLUMN_ICON,
+                                    sensitive=self.COLUMN_SENSITIVE)
+        column.set_sort_column_id(self.COLUMN_ICON)
+        treeview.append_column(column)
+
+    def is_fixed_toggled(self, cell, path_str, model):
+        # get toggled iter
+        iter_ = model.get_iter(path_str)
+        is_fixed = model.get_value(iter_, self.COLUMN_FIXED)
+
+        # do something with value
+        is_fixed ^= 1
+
+        model.set_value(iter_, self.COLUMN_FIXED, is_fixed)
+
+
+def main(demoapp=None):
+    ListStoreApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/TreeView/treemodel_filelist.py b/examples/demo/demos/TreeView/treemodel_filelist.py
new file mode 100644 (file)
index 0000000..f011a47
--- /dev/null
@@ -0,0 +1,235 @@
+#!/usr/bin/env python
+
+title = "File List (GenericTreeModel)"
+description = """
+This is a file list demo which makes use of the GenericTreeModel python
+implementation of the Gtk.TreeModel interface. This demo shows what methods
+need to be overridden to provide a valid TreeModel to a TreeView.
+"""
+
+import os
+import stat
+import time
+
+import pygtkcompat
+pygtkcompat.enable()
+pygtkcompat.enable_gtk('3.0')
+
+import gtk
+
+
+folderxpm = [
+    "17 16 7 1",
+    "  c #000000",
+    ". c #808000",
+    "X c yellow",
+    "o c #808080",
+    "O c #c0c0c0",
+    "+ c white",
+    "@ c None",
+    "@@@@@@@@@@@@@@@@@",
+    "@@@@@@@@@@@@@@@@@",
+    "@@+XXXX.@@@@@@@@@",
+    "@+OOOOOO.@@@@@@@@",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OOOOOOOOOOOOO. ",
+    "@                ",
+    "@@@@@@@@@@@@@@@@@",
+    "@@@@@@@@@@@@@@@@@"
+    ]
+folderpb = gtk.gdk.pixbuf_new_from_xpm_data(folderxpm)
+
+filexpm = [
+    "12 12 3 1",
+    "  c #000000",
+    ". c #ffff04",
+    "X c #b2c0dc",
+    "X        XXX",
+    "X ...... XXX",
+    "X ......   X",
+    "X .    ... X",
+    "X ........ X",
+    "X .   .... X",
+    "X ........ X",
+    "X .     .. X",
+    "X ........ X",
+    "X .     .. X",
+    "X ........ X",
+    "X          X"
+    ]
+filepb = gtk.gdk.pixbuf_new_from_xpm_data(filexpm)
+
+
+class FileListModel(gtk.GenericTreeModel):
+    __gtype_name__ = 'DemoFileListModel'
+
+    column_types = (gtk.gdk.Pixbuf, str, int, str, str)
+    column_names = ['Name', 'Size', 'Mode', 'Last Changed']
+
+    def __init__(self, dname=None):
+        gtk.GenericTreeModel.__init__(self)
+        self._sort_column_id = 0
+        self._sort_order = gtk.SORT_ASCENDING
+
+        if not dname:
+            self.dirname = os.path.expanduser('~')
+        else:
+            self.dirname = os.path.abspath(dname)
+        self.files = ['..'] + [f for f in os.listdir(self.dirname)]
+        return
+
+    def get_pathname(self, path):
+        filename = self.files[path[0]]
+        return os.path.join(self.dirname, filename)
+
+    def is_folder(self, path):
+        filename = self.files[path[0]]
+        pathname = os.path.join(self.dirname, filename)
+        filestat = os.stat(pathname)
+        if stat.S_ISDIR(filestat.st_mode):
+            return True
+        return False
+
+    def get_column_names(self):
+        return self.column_names[:]
+
+    #
+    # GenericTreeModel Implementation
+    #
+    def on_get_flags(self):
+        return 0  # gtk.TREE_MODEL_ITERS_PERSIST
+
+    def on_get_n_columns(self):
+        return len(self.column_types)
+
+    def on_get_column_type(self, n):
+        return self.column_types[n]
+
+    def on_get_iter(self, path):
+        return self.files[path[0]]
+
+    def on_get_path(self, rowref):
+        return self.files.index(rowref)
+
+    def on_get_value(self, rowref, column):
+        fname = os.path.join(self.dirname, rowref)
+        try:
+            filestat = os.stat(fname)
+        except OSError:
+            return None
+        mode = filestat.st_mode
+        if column is 0:
+            if stat.S_ISDIR(mode):
+                return folderpb
+            else:
+                return filepb
+        elif column is 1:
+            return rowref
+        elif column is 2:
+            return filestat.st_size
+        elif column is 3:
+            return oct(stat.S_IMODE(mode))
+        return time.ctime(filestat.st_mtime)
+
+    def on_iter_next(self, rowref):
+        try:
+            i = self.files.index(rowref) + 1
+            return self.files[i]
+        except IndexError:
+            return None
+
+    def on_iter_children(self, rowref):
+        if rowref:
+            return None
+        return self.files[0]
+
+    def on_iter_has_child(self, rowref):
+        return False
+
+    def on_iter_n_children(self, rowref):
+        if rowref:
+            return 0
+        return len(self.files)
+
+    def on_iter_nth_child(self, rowref, n):
+        if rowref:
+            return None
+        try:
+            return self.files[n]
+        except IndexError:
+            return None
+
+    def on_iter_parent(child):
+        return None
+
+
+class GenericTreeModelExample:
+    def delete_event(self, widget, event, data=None):
+        gtk.main_quit()
+        return False
+
+    def __init__(self):
+        # Create a new window
+        self.window = gtk.Window(type=gtk.WINDOW_TOPLEVEL)
+
+        self.window.set_size_request(300, 200)
+
+        self.window.connect("delete_event", self.delete_event)
+
+        self.listmodel = FileListModel()
+
+        # create the TreeView
+        self.treeview = gtk.TreeView()
+
+        self.tvcolumns = []
+
+        # create the TreeViewColumns to display the data
+        for n, name in enumerate(self.listmodel.get_column_names()):
+            if n == 0:
+                cellpb = gtk.CellRendererPixbuf()
+                col = gtk.TreeViewColumn(name, cellpb, pixbuf=0)
+                cell = gtk.CellRendererText()
+                col.pack_start(cell, False)
+                col.add_attribute(cell, 'text', 1)
+            else:
+                cell = gtk.CellRendererText()
+                col = gtk.TreeViewColumn(name, cell, text=n + 1)
+            if n == 1:
+                cell.set_property('xalign', 1.0)
+
+            self.treeview.append_column(col)
+
+        self.treeview.connect('row-activated', self.open_file)
+
+        self.scrolledwindow = gtk.ScrolledWindow()
+        self.scrolledwindow.add(self.treeview)
+        self.window.add(self.scrolledwindow)
+        self.treeview.set_model(self.listmodel)
+        self.window.set_title(self.listmodel.dirname)
+        self.window.show_all()
+
+    def open_file(self, treeview, path, column):
+        model = treeview.get_model()
+        if model.is_folder(path):
+            pathname = model.get_pathname(path)
+            new_model = FileListModel(pathname)
+            self.window.set_title(new_model.dirname)
+            treeview.set_model(new_model)
+        return
+
+
+def main(demoapp=None):
+    demo = GenericTreeModelExample()
+    demo
+    gtk.main()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/demo/demos/TreeView/treemodel_filetree.py b/examples/demo/demos/TreeView/treemodel_filetree.py
new file mode 100644 (file)
index 0000000..3b43190
--- /dev/null
@@ -0,0 +1,280 @@
+#!/usr/bin/env python
+
+title = "File Tree (GenericTreeModel)"
+description = """
+This is a file list demo which makes use of the GenericTreeModel python
+implementation of the Gtk.TreeModel interface. This demo shows what methods
+need to be overridden to provide a valid TreeModel to a TreeView.
+"""
+
+import os
+import stat
+import time
+from collections import OrderedDict
+
+import pygtkcompat
+pygtkcompat.enable_gtk('3.0')
+
+import gtk
+
+
+folderxpm = [
+    "17 16 7 1",
+    "  c #000000",
+    ". c #808000",
+    "X c yellow",
+    "o c #808080",
+    "O c #c0c0c0",
+    "+ c white",
+    "@ c None",
+    "@@@@@@@@@@@@@@@@@",
+    "@@@@@@@@@@@@@@@@@",
+    "@@+XXXX.@@@@@@@@@",
+    "@+OOOOOO.@@@@@@@@",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OOOOOOOOOOOOO. ",
+    "@                ",
+    "@@@@@@@@@@@@@@@@@",
+    "@@@@@@@@@@@@@@@@@"
+    ]
+folderpb = gtk.gdk.pixbuf_new_from_xpm_data(folderxpm)
+
+filexpm = [
+    "12 12 3 1",
+    "  c #000000",
+    ". c #ffff04",
+    "X c #b2c0dc",
+    "X        XXX",
+    "X ...... XXX",
+    "X ......   X",
+    "X .    ... X",
+    "X ........ X",
+    "X .   .... X",
+    "X ........ X",
+    "X .     .. X",
+    "X ........ X",
+    "X .     .. X",
+    "X ........ X",
+    "X          X"
+    ]
+filepb = gtk.gdk.pixbuf_new_from_xpm_data(filexpm)
+
+
+class FileTreeModel(gtk.GenericTreeModel):
+    __gtype_name__ = 'DemoFileTreeModel'
+
+    column_types = (gtk.gdk.Pixbuf, str, int, str, str)
+    column_names = ['Name', 'Size', 'Mode', 'Last Changed']
+
+    def __init__(self, dname=None):
+        gtk.GenericTreeModel.__init__(self)
+        if not dname:
+            self.dirname = os.path.expanduser('~')
+        else:
+            self.dirname = os.path.abspath(dname)
+        self.files = self.build_file_dict(self.dirname)
+        return
+
+    def build_file_dict(self, dirname):
+        """
+        :Returns:
+            A dictionary containing the files in the given dirname keyed by filename.
+            If the child filename is a sub-directory, the dict value is a dict.
+            Otherwise it will be None.
+        """
+        d = OrderedDict()
+        for fname in os.listdir(dirname):
+            try:
+                filestat = os.stat(os.path.join(dirname, fname))
+            except OSError:
+                d[fname] = None
+            else:
+                d[fname] = OrderedDict() if stat.S_ISDIR(filestat.st_mode) else None
+
+        return d
+
+    def get_node_from_treepath(self, path):
+        """
+        :Returns:
+            The node stored at the given tree path in local storage.
+        """
+        # TreePaths are a series of integer indices so just iterate through them
+        # and index values by each integer since we are using an OrderedDict
+        if path is None:
+            path = []
+        node = self.files
+        for index in path:
+            node = list(node.values())[index]
+        return node
+
+    def get_node_from_filepath(self, filepath):
+        """
+        :Returns:
+            The node stored at the given file path in local storage.
+        """
+        if not filepath:
+            return self.files
+        node = self.files
+        for key in filepath.split(os.path.sep):
+            node = node[key]
+        return node
+
+    def get_column_names(self):
+        return self.column_names[:]
+
+    #
+    # GenericTreeModel Implementation
+    #
+
+    def on_get_flags(self):
+        return 0
+
+    def on_get_n_columns(self):
+        return len(self.column_types)
+
+    def on_get_column_type(self, n):
+        return self.column_types[n]
+
+    def on_get_path(self, relpath):
+        path = []
+        node = self.files
+        for key in relpath.split(os.path.sep):
+            path.append(list(node.keys()).index(key))
+            node = node[key]
+        return path
+
+    def on_get_value(self, relpath, column):
+        fname = os.path.join(self.dirname, relpath)
+        try:
+            filestat = os.stat(fname)
+        except OSError:
+            return None
+        mode = filestat.st_mode
+        if column is 0:
+            if stat.S_ISDIR(mode):
+                return folderpb
+            else:
+                return filepb
+        elif column is 1:
+            return os.path.basename(relpath)
+        elif column is 2:
+            return filestat.st_size
+        elif column is 3:
+            return oct(stat.S_IMODE(mode))
+        return time.ctime(filestat.st_mtime)
+
+    def on_get_iter(self, path):
+        filepath = ''
+        value = self.files
+        for index in path:
+            filepath = os.path.join(filepath, list(value.keys())[index])
+            value = list(value.values())[index]
+        return filepath
+
+    def on_iter_next(self, filepath):
+        parent_path, child_path = os.path.split(filepath)
+        parent = self.get_node_from_filepath(parent_path)
+
+        # Index of filepath within its parents child list
+        sibling_names = list(parent.keys())
+        index = sibling_names.index(child_path)
+        try:
+            return os.path.join(parent_path, sibling_names[index + 1])
+        except IndexError:
+            return None
+
+    def on_iter_children(self, filepath):
+        if filepath:
+            children = list(self.get_node_from_filepath(filepath).keys())
+            if children:
+                return os.path.join(filepath, children[0])
+        elif self.files:
+            return list(self.files.keys())[0]
+
+        return None
+
+    def on_iter_has_child(self, filepath):
+        return bool(self.get_node_from_filepath(filepath))
+
+    def on_iter_n_children(self, filepath):
+        return len(self.get_node_from_filepath(filepath))
+
+    def on_iter_nth_child(self, filepath, n):
+        try:
+            child = list(self.get_node_from_filepath(filepath).keys())[n]
+            if filepath:
+                return os.path.join(filepath, child)
+            else:
+                return child
+        except IndexError:
+            return None
+
+    def on_iter_parent(self, filepath):
+        return os.path.dirname(filepath)
+
+    def on_ref_node(self, filepath):
+        value = self.get_node_from_filepath(filepath)
+        if value is not None:
+            value.update(self.build_file_dict(os.path.join(self.dirname, filepath)))
+
+    def on_unref_node(self, filepath):
+        pass
+
+
+class GenericTreeModelExample:
+    def delete_event(self, widget, event, data=None):
+        gtk.main_quit()
+        return False
+
+    def __init__(self):
+        # Create a new window
+        self.window = gtk.Window(type=gtk.WINDOW_TOPLEVEL)
+        self.window.set_size_request(300, 200)
+        self.window.connect("delete_event", self.delete_event)
+
+        self.listmodel = FileTreeModel()
+
+        # create the TreeView
+        self.treeview = gtk.TreeView()
+
+        # create the TreeViewColumns to display the data
+        column_names = self.listmodel.get_column_names()
+        self.tvcolumn = [None] * len(column_names)
+        cellpb = gtk.CellRendererPixbuf()
+        self.tvcolumn[0] = gtk.TreeViewColumn(column_names[0],
+                                              cellpb, pixbuf=0)
+        cell = gtk.CellRendererText()
+        self.tvcolumn[0].pack_start(cell, False)
+        self.tvcolumn[0].add_attribute(cell, 'text', 1)
+        self.treeview.append_column(self.tvcolumn[0])
+        for n in range(1, len(column_names)):
+            cell = gtk.CellRendererText()
+            if n == 1:
+                cell.set_property('xalign', 1.0)
+            self.tvcolumn[n] = gtk.TreeViewColumn(column_names[n],
+                                                  cell, text=n + 1)
+            self.treeview.append_column(self.tvcolumn[n])
+
+        self.scrolledwindow = gtk.ScrolledWindow()
+        self.scrolledwindow.add(self.treeview)
+        self.window.add(self.scrolledwindow)
+        self.treeview.set_model(self.listmodel)
+        self.window.set_title(self.listmodel.dirname)
+        self.window.show_all()
+
+
+def main(demoapp=None):
+    demo = GenericTreeModelExample()
+    demo
+    gtk.main()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/demo/demos/TreeView/treemodel_large.py b/examples/demo/demos/TreeView/treemodel_large.py
new file mode 100644 (file)
index 0000000..b129521
--- /dev/null
@@ -0,0 +1,143 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# pygobject - Python bindings for the GObject library
+# Copyright (C) 2014 Simon Feltman
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+title = "Tree Model with Large Data"
+description = """
+Implementation of the Gtk.TreeModel interface to create a custom model.
+The demo uses a fake data store (it is not backed by a Python list) and is for
+the purpose of showing how to override the TreeModel interfaces virtual methods.
+"""
+
+from gi.repository import GObject
+from gi.repository import GLib
+from gi.repository import Gtk
+
+
+class Model(GObject.Object, Gtk.TreeModel):
+    columns_types = (str, str)
+    item_count = 100000
+    item_data = 'abcdefghijklmnopqrstuvwxyz'
+
+    def __init__(self):
+        super(Model, self).__init__()
+
+    def do_get_flags(self):
+        return Gtk.TreeModelFlags.LIST_ONLY
+
+    def do_get_n_columns(self):
+        return len(self.columns_types)
+
+    def do_get_column_type(self, n):
+        return self.columns_types[n]
+
+    def do_get_iter(self, path):
+        # Return False and an empty iter when out of range
+        index = path.get_indices()[0]
+        if index < 0 or index >= self.item_count:
+            return False, None
+
+        it = Gtk.TreeIter()
+        it.user_data = index
+        return True, it
+
+    def do_get_path(self, it):
+        return Gtk.TreePath([it.user_data])
+
+    def do_get_value(self, it, column):
+        if column == 0:
+            return str(it.user_data)
+        elif column == 1:
+            return self.item_data
+
+    def do_iter_next(self, it):
+        # Return False if there is not a next item
+        next = it.user_data + 1
+        if next >= self.item_count:
+            return False
+
+        # Set the iters data and return True
+        it.user_data = next
+        return True
+
+    def do_iter_previous(self, it):
+        prev = it.user_data - 1
+        if prev < 0:
+            return False
+
+        it.user_data = prev
+        return True
+
+    def do_iter_children(self, parent):
+        # If parent is None return the first item
+        if parent is None:
+            it = Gtk.TreeIter()
+            it.user_data = 0
+            return True, it
+        return False, None
+
+    def do_iter_has_child(self, it):
+        return it is None
+
+    def do_iter_n_children(self, it):
+        # If iter is None, return the number of top level nodes
+        if it is None:
+            return self.item_count
+        return 0
+
+    def do_iter_nth_child(self, parent, n):
+        if parent is not None or n >= self.item_count:
+            return False, None
+        elif parent is None:
+            # If parent is None, return the nth iter
+            it = Gtk.TreeIter()
+            it.user_data = n
+            return True, it
+
+    def do_iter_parent(self, child):
+        return False, None
+
+
+def main(demoapp=None):
+    model = Model()
+    # Use fixed-height-mode to get better model load and display performance.
+    view = Gtk.TreeView(fixed_height_mode=True, headers_visible=False)
+    column = Gtk.TreeViewColumn()
+    column.props.sizing = Gtk.TreeViewColumnSizing.FIXED
+
+    renderer1 = Gtk.CellRendererText()
+    renderer2 = Gtk.CellRendererText()
+    column.pack_start(renderer1, expand=True)
+    column.pack_start(renderer2, expand=True)
+    column.add_attribute(renderer1, 'text', 0)
+    column.add_attribute(renderer2, 'text', 1)
+    view.append_column(column)
+
+    scrolled = Gtk.ScrolledWindow()
+    scrolled.add(view)
+
+    window = Gtk.Window(title=title)
+    window.set_size_request(480, 640)
+    window.add(scrolled)
+    window.show_all()
+    GLib.timeout_add(10, lambda *args: view.set_model(model))
+    return window
+
+
+if __name__ == "__main__":
+    window = main()
+    window.connect('destroy', Gtk.main_quit)
+    Gtk.main()
diff --git a/examples/demo/demos/__init__.py b/examples/demo/demos/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/examples/demo/demos/appwindow.py b/examples/demo/demos/appwindow.py
new file mode 100644 (file)
index 0000000..893ecc0
--- /dev/null
@@ -0,0 +1,408 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Application main window"
+description = """
+Demonstrates a typical application window with menubar, toolbar, statusbar.
+"""
+
+import os
+
+from gi.repository import GdkPixbuf, Gtk
+
+
+infobar = None
+window = None
+messagelabel = None
+_demoapp = None
+
+
+def widget_destroy(widget, button):
+    widget.destroy()
+
+
+def activate_action(action, user_data=None):
+    global window
+
+    name = action.get_name()
+    _type = type(action)
+    if name == 'DarkTheme':
+        value = action.get_active()
+        settings = Gtk.Settings.get_default()
+        settings.set_property('gtk-application-prefer-dark-theme', value)
+        return
+
+    dialog = Gtk.MessageDialog(message_type=Gtk.MessageType.INFO,
+                               buttons=Gtk.ButtonsType.CLOSE,
+                               text='You activated action: "%s" of type %s' % (name, _type))
+
+    # FIXME: this should be done in the constructor
+    dialog.set_transient_for(window)
+    dialog.connect('response', widget_destroy)
+    dialog.show()
+
+
+def activate_radio_action(action, current, user_data=None):
+    global infobar
+    global messagelabel
+
+    name = current.get_name()
+    _type = type(current)
+    active = current.get_active()
+    value = current.get_current_value()
+    if active:
+        text = 'You activated radio action: "%s" of type %s.\n Current value: %d' % (name, _type, value)
+        messagelabel.set_text(text)
+        infobar.set_message_type(Gtk.MessageType(value))
+        infobar.show()
+
+
+def update_statusbar(buffer, statusbar):
+    statusbar.pop(0)
+    count = buffer.get_char_count()
+
+    iter = buffer.get_iter_at_mark(buffer.get_insert())
+    row = iter.get_line()
+    col = iter.get_line_offset()
+    msg = 'Cursor at row %d column %d - %d chars in document' % (row, col, count)
+
+    statusbar.push(0, msg)
+
+
+def mark_set_callback(buffer, new_location, mark, data):
+    update_statusbar(buffer, data)
+
+
+def about_cb(widget, user_data=None):
+    global window
+
+    authors = ['John (J5) Palmieri',
+               'Tomeu Vizoso',
+               'and many more...']
+
+    documentors = ['David Malcolm',
+                   'Zack Goldberg',
+                   'and many more...']
+
+    license = """
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+This library 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the Gnome Library; see the file COPYING.LIB.  If not,
+write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+"""
+    dirname = os.path.abspath(os.path.dirname(__file__))
+    filename = os.path.join(dirname, 'data', 'gtk-logo-rgb.gif')
+    pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
+    transparent = pixbuf.add_alpha(True, 0xff, 0xff, 0xff)
+
+    about = Gtk.AboutDialog(parent=window,
+                            program_name='GTK+ Code Demos',
+                            version='0.1',
+                            copyright='(C) 2010 The PyGI Team',
+                            license=license,
+                            website='http://live.gnome.org/PyGI',
+                            comments='Program to demonstrate PyGI functions.',
+                            authors=authors,
+                            documenters=documentors,
+                            logo=transparent,
+                            title='About GTK+ Code Demos')
+
+    about.connect('response', widget_destroy)
+    about.show()
+
+
+action_entries = (
+    ("FileMenu", None, "_File"),                # name, stock id, label
+    ("OpenMenu", None, "_Open"),                # name, stock id, label
+    ("PreferencesMenu", None, "_Preferences"),  # name, stock id, label
+    ("ColorMenu", None, "_Color"),              # name, stock id, label
+    ("ShapeMenu", None, "_Shape"),              # name, stock id, label
+    ("HelpMenu", None, "_Help"),                # name, stock id, label
+    ("New", Gtk.STOCK_NEW,                      # name, stock id
+     "_New", "<control>N",                      # label, accelerator
+     "Create a new file",                       # tooltip
+     activate_action),
+    ("File1", None,                             # name, stock id
+     "File1", None,                             # label, accelerator
+     "Open first file",                         # tooltip
+     activate_action),
+    ("Save", Gtk.STOCK_SAVE,                    # name, stock id
+     "_Save", "<control>S",                     # label, accelerator
+     "Save current file",                       # tooltip
+     activate_action),
+    ("SaveAs", Gtk.STOCK_SAVE,                  # name, stock id
+     "Save _As...", None,                       # label, accelerator
+     "Save to a file",                          # tooltip
+     activate_action),
+    ("Quit", Gtk.STOCK_QUIT,                    # name, stock id
+     "_Quit", "<control>Q",                     # label, accelerator
+     "Quit",                                    # tooltip
+     activate_action),
+    ("About", None,                             # name, stock id
+     "_About", "<control>A",                    # label, accelerator
+     "About",                                   # tooltip
+     about_cb),
+    ("Logo", "demo-gtk-logo",                   # name, stock id
+     None, None,                                # label, accelerator
+     "GTK+",                                    # tooltip
+     activate_action),
+)
+
+toggle_action_entries = (
+    ("Bold", Gtk.STOCK_BOLD,                    # name, stock id
+     "_Bold", "<control>B",                     # label, accelerator
+     "Bold",                                    # tooltip
+     activate_action,
+     True),                                     # is_active
+    ("DarkTheme", None,                         # name, stock id
+     "_Prefer Dark Theme", None,                # label, accelerator
+     "Prefer Dark Theme",                       # tooltip
+     activate_action,
+     False),                                    # is_active
+)
+
+(COLOR_RED,
+ COLOR_GREEN,
+ COLOR_BLUE) = range(3)
+
+color_action_entries = (
+    ("Red", None,                               # name, stock id
+     "_Red", "<control>R",                      # label, accelerator
+     "Blood", COLOR_RED),                       # tooltip, value
+    ("Green", None,                             # name, stock id
+     "_Green", "<control>G",                    # label, accelerator
+     "Grass", COLOR_GREEN),                     # tooltip, value
+    ("Blue", None,                              # name, stock id
+     "_Blue", "<control>B",                     # label, accelerator
+     "Sky", COLOR_BLUE),                        # tooltip, value
+)
+
+(SHAPE_SQUARE,
+ SHAPE_RECTANGLE,
+ SHAPE_OVAL) = range(3)
+
+shape_action_entries = (
+    ("Square", None,                            # name, stock id
+     "_Square", "<control>S",                   # label, accelerator
+     "Square", SHAPE_SQUARE),                   # tooltip, value
+    ("Rectangle", None,                         # name, stock id
+     "_Rectangle", "<control>R",                # label, accelerator
+     "Rectangle", SHAPE_RECTANGLE),             # tooltip, value
+    ("Oval", None,                              # name, stock id
+     "_Oval", "<control>O",                     # label, accelerator
+     "Egg", SHAPE_OVAL),                        # tooltip, value
+)
+
+ui_info = """
+<ui>
+  <menubar name='MenuBar'>
+    <menu action='FileMenu'>
+      <menuitem action='New'/>
+      <menuitem action='Open'/>
+      <menuitem action='Save'/>
+      <menuitem action='SaveAs'/>
+      <separator/>
+      <menuitem action='Quit'/>
+    </menu>
+    <menu action='PreferencesMenu'>
+      <menuitem action='DarkTheme'/>
+      <menu action='ColorMenu'>
+    <menuitem action='Red'/>
+    <menuitem action='Green'/>
+    <menuitem action='Blue'/>
+      </menu>
+      <menu action='ShapeMenu'>
+        <menuitem action='Square'/>
+        <menuitem action='Rectangle'/>
+        <menuitem action='Oval'/>
+      </menu>
+      <menuitem action='Bold'/>
+    </menu>
+    <menu action='HelpMenu'>
+      <menuitem action='About'/>
+    </menu>
+  </menubar>
+  <toolbar name='ToolBar'>
+    <toolitem action='Open'>
+      <menu action='OpenMenu'>
+        <menuitem action='File1'/>
+      </menu>
+    </toolitem>
+    <toolitem action='Quit'/>
+    <separator action='Sep1'/>
+    <toolitem action='Logo'/>
+  </toolbar>
+</ui>
+"""
+
+
+def _quit(*args):
+    Gtk.main_quit()
+
+
+def register_stock_icons():
+    """
+    This function registers our custom toolbar icons, so they can be themed.
+    It's totally optional to do this, you could just manually insert icons
+    and have them not be themeable, especially if you never expect people
+    to theme your app.
+    """
+    '''
+    item = Gtk.StockItem()
+    item.stock_id = 'demo-gtk-logo'
+    item.label = '_GTK!'
+    item.modifier = 0
+    item.keyval = 0
+    item.translation_domain = None
+
+    Gtk.stock_add(item, 1)
+    '''
+    global _demoapp
+
+    factory = Gtk.IconFactory()
+    factory.add_default()
+
+    if _demoapp is None:
+        filename = os.path.join('data', 'gtk-logo-rgb.gif')
+    else:
+        filename = _demoapp.find_file('gtk-logo-rgb.gif')
+
+    pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
+    transparent = pixbuf.add_alpha(True, 0xff, 0xff, 0xff)
+    icon_set = Gtk.IconSet.new_from_pixbuf(transparent)
+
+    factory.add('demo-gtk-logo', icon_set)
+
+
+class ToolMenuAction(Gtk.Action):
+    __gtype_name__ = "GtkToolMenuAction"
+
+    def do_create_tool_item(self):
+        return Gtk.MenuToolButton()
+
+
+def main(demoapp=None):
+    global infobar
+    global window
+    global messagelabel
+    global _demoapp
+
+    _demoapp = demoapp
+
+    register_stock_icons()
+
+    window = Gtk.Window()
+    window.set_title('Application Window')
+    window.set_icon_name('gtk-open')
+    window.connect_after('destroy', _quit)
+    table = Gtk.Table(n_rows=1,
+                      n_columns=5,
+                      homogeneous=False)
+    window.add(table)
+
+    action_group = Gtk.ActionGroup(name='AppWindowActions')
+    open_action = ToolMenuAction(name='Open',
+                                 stock_id=Gtk.STOCK_OPEN,
+                                 label='_Open',
+                                 tooltip='Open a file')
+
+    action_group.add_action(open_action)
+    action_group.add_actions(action_entries)
+    action_group.add_toggle_actions(toggle_action_entries)
+    action_group.add_radio_actions(color_action_entries,
+                                   COLOR_RED,
+                                   activate_radio_action)
+    action_group.add_radio_actions(shape_action_entries,
+                                   SHAPE_SQUARE,
+                                   activate_radio_action)
+
+    merge = Gtk.UIManager()
+    merge.insert_action_group(action_group, 0)
+    window.add_accel_group(merge.get_accel_group())
+
+    merge.add_ui_from_string(ui_info)
+
+    bar = merge.get_widget('/MenuBar')
+    bar.show()
+    table.attach(bar, 0, 1, 0, 1,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 0, 0, 0)
+
+    bar = merge.get_widget('/ToolBar')
+    bar.show()
+    table.attach(bar, 0, 1, 1, 2,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 0, 0, 0)
+
+    infobar = Gtk.InfoBar()
+    infobar.set_no_show_all(True)
+    messagelabel = Gtk.Label()
+    messagelabel.show()
+    infobar.get_content_area().pack_start(messagelabel, True, True, 0)
+    infobar.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
+    infobar.connect('response', lambda a, b: Gtk.Widget.hide(a))
+
+    table.attach(infobar, 0, 1, 2, 3,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 0, 0, 0)
+
+    sw = Gtk.ScrolledWindow(hadjustment=None,
+                            vadjustment=None)
+    sw.set_shadow_type(Gtk.ShadowType.IN)
+    table.attach(sw, 0, 1, 3, 4,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 0, 0)
+
+    contents = Gtk.TextView()
+    contents.grab_focus()
+    sw.add(contents)
+
+    # Create statusbar
+    statusbar = Gtk.Statusbar()
+    table.attach(statusbar, 0, 1, 4, 5,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 0, 0, 0)
+
+    # show text widget info in the statusbar
+    buffer = contents.get_buffer()
+    buffer.connect('changed', update_statusbar, statusbar)
+    buffer.connect('mark_set', mark_set_callback, statusbar)
+
+    update_statusbar(buffer, statusbar)
+
+    window.set_default_size(200, 200)
+    window.show_all()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/assistant.py b/examples/demo/demos/assistant.py
new file mode 100644 (file)
index 0000000..9e729e9
--- /dev/null
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Assistant"
+description = """
+Demonstrates a sample multistep assistant. Assistants are used to divide
+an operation into several simpler sequential steps, and to guide the user
+through these steps.
+"""
+
+
+from gi.repository import Gtk
+
+
+class AssistantApp:
+    def __init__(self):
+        self.assistant = Gtk.Assistant()
+        self.assistant.set_default_size(-1, 300)
+
+        self.create_page1()
+        self.create_page2()
+        self.create_page3()
+
+        self.assistant.connect('cancel', self.on_close_cancel)
+        self.assistant.connect('close', self.on_close_cancel)
+        self.assistant.connect('apply', self.on_apply)
+        self.assistant.connect('prepare', self.on_prepare)
+
+        self.assistant.show()
+
+    def on_close_cancel(self, assistant):
+        assistant.destroy()
+        Gtk.main_quit()
+
+    def on_apply(self, assistant):
+        # apply changes here; this is a fictional example so just do
+        # nothing here
+        pass
+
+    def on_prepare(self, assistant, page):
+        current_page = assistant.get_current_page()
+        n_pages = assistant.get_n_pages()
+        title = 'Sample assistant (%d of %d)' % (current_page + 1, n_pages)
+        assistant.set_title(title)
+
+    def on_entry_changed(self, widget):
+        page_number = self.assistant.get_current_page()
+        current_page = self.assistant.get_nth_page(page_number)
+        text = widget.get_text()
+
+        if text:
+            self.assistant.set_page_complete(current_page, True)
+        else:
+            self.assistant.set_page_complete(current_page, False)
+
+    def create_page1(self):
+        box = Gtk.HBox(homogeneous=False,
+                       spacing=12)
+        box.set_border_width(12)
+        label = Gtk.Label(label='You must fill out this entry to continue:')
+        box.pack_start(label, False, False, 0)
+
+        entry = Gtk.Entry()
+        box.pack_start(entry, True, True, 0)
+        entry.connect('changed', self.on_entry_changed)
+
+        box.show_all()
+        self.assistant.append_page(box)
+        self.assistant.set_page_title(box, 'Page 1')
+        self.assistant.set_page_type(box, Gtk.AssistantPageType.INTRO)
+
+        pixbuf = self.assistant.render_icon(Gtk.STOCK_DIALOG_INFO,
+                                            Gtk.IconSize.DIALOG,
+                                            None)
+
+        self.assistant.set_page_header_image(box, pixbuf)
+
+    def create_page2(self):
+        box = Gtk.VBox(homogeneous=False,
+                       spacing=12)
+        box.set_border_width(12)
+
+        checkbutton = Gtk.CheckButton(label='This is optional data, you may continue even if you do not check this')
+        box.pack_start(checkbutton, False, False, 0)
+
+        box.show_all()
+
+        self.assistant.append_page(box)
+        self.assistant.set_page_complete(box, True)
+        self.assistant.set_page_title(box, 'Page 2')
+
+        pixbuf = self.assistant.render_icon(Gtk.STOCK_DIALOG_INFO,
+                                            Gtk.IconSize.DIALOG,
+                                            None)
+        self.assistant.set_page_header_image(box, pixbuf)
+
+    def create_page3(self):
+        label = Gtk.Label(label='This is a confirmation page, press "Apply" to apply changes')
+        label.show()
+        self.assistant.append_page(label)
+        self.assistant.set_page_complete(label, True)
+        self.assistant.set_page_title(label, 'Confirmation')
+        self.assistant.set_page_type(label, Gtk.AssistantPageType.CONFIRM)
+
+        pixbuf = self.assistant.render_icon(Gtk.STOCK_DIALOG_INFO,
+                                            Gtk.IconSize.DIALOG,
+                                            None)
+        self.assistant.set_page_header_image(label, pixbuf)
+
+
+def main(demoapp=None):
+    AssistantApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/builder.py b/examples/demo/demos/builder.py
new file mode 100644 (file)
index 0000000..47e09a4
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Builder"
+description = """
+Demonstrates an interface loaded from a XML description.
+"""
+
+
+import os
+
+from gi.repository import Gtk
+
+
+class BuilderApp:
+    def __init__(self, demoapp):
+        self.demoapp = demoapp
+
+        self.builder = Gtk.Builder()
+        if demoapp is None:
+            filename = os.path.join('data', 'demo.ui')
+        else:
+            filename = demoapp.find_file('demo.ui')
+
+        self.builder.add_from_file(filename)
+        self.builder.connect_signals(self)
+
+        window = self.builder.get_object('window1')
+        window.connect('destroy', lambda x: Gtk.main_quit())
+        window.show_all()
+
+    def about_activate(self, action):
+        about_dlg = self.builder.get_object('aboutdialog1')
+        about_dlg.run()
+        about_dlg.hide()
+
+    def quit_activate(self, action):
+        Gtk.main_quit()
+
+
+def main(demoapp=None):
+    BuilderApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/button_box.py b/examples/demo/demos/button_box.py
new file mode 100644 (file)
index 0000000..be94984
--- /dev/null
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Button Boxes"
+description = """
+The Button Box widgets are used to arrange buttons with padding.
+"""
+
+
+from gi.repository import Gtk
+
+
+class ButtonBoxApp:
+    def __init__(self):
+        window = Gtk.Window()
+        window.set_title('Button Boxes')
+        window.connect('destroy', lambda x: Gtk.main_quit())
+        window.set_border_width(10)
+
+        main_vbox = Gtk.VBox(homogeneous=False, spacing=0)
+        window.add(main_vbox)
+
+        frame_horz = Gtk.Frame(label='Horizontal Button Boxes')
+        main_vbox.pack_start(frame_horz, True, True, 10)
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=0)
+        vbox.set_border_width(10)
+        frame_horz.add(vbox)
+
+        vbox.pack_start(
+            self.create_bbox(True, 'Spread', 40, Gtk.ButtonBoxStyle.SPREAD),
+            True, True, 0)
+
+        vbox.pack_start(
+            self.create_bbox(True, 'Edge', 40, Gtk.ButtonBoxStyle.EDGE),
+            True, True, 5)
+
+        vbox.pack_start(
+            self.create_bbox(True, 'Start', 40, Gtk.ButtonBoxStyle.START),
+            True, True, 5)
+
+        vbox.pack_start(
+            self.create_bbox(True, 'End', 40, Gtk.ButtonBoxStyle.END),
+            True, True, 5)
+
+        frame_vert = Gtk.Frame(label='Vertical Button Boxes')
+        main_vbox.pack_start(frame_vert, True, True, 10)
+
+        hbox = Gtk.HBox(homogeneous=False, spacing=0)
+        hbox.set_border_width(10)
+        frame_vert.add(hbox)
+
+        hbox.pack_start(
+            self.create_bbox(False, 'Spread', 30, Gtk.ButtonBoxStyle.SPREAD),
+            True, True, 0)
+
+        hbox.pack_start(
+            self.create_bbox(False, 'Edge', 30, Gtk.ButtonBoxStyle.EDGE),
+            True, True, 5)
+
+        hbox.pack_start(
+            self.create_bbox(False, 'Start', 30, Gtk.ButtonBoxStyle.START),
+            True, True, 5)
+
+        hbox.pack_start(
+            self.create_bbox(False, 'End', 30, Gtk.ButtonBoxStyle.END),
+            True, True, 5)
+
+        window.show_all()
+
+    def create_bbox(self, is_horizontal, title, spacing, layout):
+        frame = Gtk.Frame(label=title)
+
+        if is_horizontal:
+            bbox = Gtk.HButtonBox()
+        else:
+            bbox = Gtk.VButtonBox()
+
+        bbox.set_border_width(5)
+        frame.add(bbox)
+
+        bbox.set_layout(layout)
+        bbox.set_spacing(spacing)
+
+        # FIXME: GtkButton consturctor should take a stock_id
+        button = Gtk.Button.new_from_stock(Gtk.STOCK_OK)
+        bbox.add(button)
+
+        button = Gtk.Button.new_from_stock(Gtk.STOCK_CANCEL)
+        bbox.add(button)
+
+        button = Gtk.Button.new_from_stock(Gtk.STOCK_HELP)
+        bbox.add(button)
+
+        return frame
+
+
+def main(demoapp=None):
+    ButtonBoxApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/clipboard.py b/examples/demo/demos/clipboard.py
new file mode 100644 (file)
index 0000000..5a88828
--- /dev/null
@@ -0,0 +1,228 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Clipboard"
+description = """
+GtkClipboard is used for clipboard handling. This demo shows how to
+copy and paste text to and from the clipboard.
+
+It also shows how to transfer images via the clipboard or via
+drag-and-drop, and how to make clipboard contents persist after
+the application exits. Clipboard persistence requires a clipboard
+manager to run.
+"""
+
+
+from gi.repository import Gtk, Gdk
+
+
+class ClipboardApp:
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Clipboard demo')
+        self.window.connect('destroy', lambda w: Gtk.main_quit())
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=0)
+        vbox.set_border_width(8)
+        self.window.add(vbox)
+
+        label = Gtk.Label(label='"Copy" will copy the text\nin the entry to the clipboard')
+        vbox.pack_start(label, False, False, 0)
+
+        hbox = Gtk.HBox(homogeneous=False, spacing=4)
+        hbox.set_border_width(8)
+        vbox.pack_start(hbox, False, False, 0)
+
+        # create first entry
+        entry = Gtk.Entry()
+        hbox.pack_start(entry, True, True, 0)
+
+        # create button
+        button = Gtk.Button.new_from_stock(Gtk.STOCK_COPY)
+        hbox.pack_start(button, False, False, 0)
+        button.connect('clicked', self.copy_button_clicked, entry)
+
+        label = Gtk.Label(label='"Paste" will paste the text from the clipboard to the entry')
+        vbox.pack_start(label, False, False, 0)
+
+        hbox = Gtk.HBox(homogeneous=False, spacing=4)
+        hbox.set_border_width(8)
+        vbox.pack_start(hbox, False, False, 0)
+
+        # create secondary entry
+        entry = Gtk.Entry()
+        hbox.pack_start(entry, True, True, 0)
+        # create button
+        button = Gtk.Button.new_from_stock(Gtk.STOCK_PASTE)
+        hbox.pack_start(button, False, False, 0)
+        button.connect('clicked', self.paste_button_clicked, entry)
+
+        label = Gtk.Label(label='Images can be transferred via the clipboard, too')
+        vbox.pack_start(label, False, False, 0)
+
+        hbox = Gtk.HBox(homogeneous=False, spacing=4)
+        hbox.set_border_width(8)
+        vbox.pack_start(hbox, False, False, 0)
+
+        # create the first image
+        image = Gtk.Image(stock=Gtk.STOCK_DIALOG_WARNING,
+                          icon_size=Gtk.IconSize.BUTTON)
+
+        ebox = Gtk.EventBox()
+        ebox.add(image)
+        hbox.add(ebox)
+
+        # make ebox a drag source
+        ebox.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
+                             None, Gdk.DragAction.COPY)
+        ebox.drag_source_add_image_targets()
+        ebox.connect('drag-begin', self.drag_begin, image)
+        ebox.connect('drag-data-get', self.drag_data_get, image)
+
+        # accept drops on ebox
+        ebox.drag_dest_set(Gtk.DestDefaults.ALL,
+                           None, Gdk.DragAction.COPY)
+        ebox.drag_dest_add_image_targets()
+        ebox.connect('drag-data-received', self.drag_data_received, image)
+
+        # context menu on ebox
+        ebox.connect('button-press-event', self.button_press, image)
+
+        # create the second image
+        image = Gtk.Image(stock=Gtk.STOCK_STOP,
+                          icon_size=Gtk.IconSize.BUTTON)
+
+        ebox = Gtk.EventBox()
+        ebox.add(image)
+        hbox.add(ebox)
+
+        # make ebox a drag source
+        ebox.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
+                             None, Gdk.DragAction.COPY)
+        ebox.drag_source_add_image_targets()
+        ebox.connect('drag-begin', self.drag_begin, image)
+        ebox.connect('drag-data-get', self.drag_data_get, image)
+
+        # accept drops on ebox
+        ebox.drag_dest_set(Gtk.DestDefaults.ALL,
+                           None, Gdk.DragAction.COPY)
+        ebox.drag_dest_add_image_targets()
+        ebox.connect('drag-data-received', self.drag_data_received, image)
+
+        # context menu on ebox
+        ebox.connect('button-press-event', self.button_press, image)
+
+        # tell the clipboard manager to make data persistent
+        # FIXME: Allow sending strings a Atoms and convert in PyGI
+        atom = Gdk.atom_intern('CLIPBOARD', True)
+        clipboard = Gtk.Clipboard.get(atom)
+        clipboard.set_can_store(None)
+
+        self.window.show_all()
+
+    def copy_button_clicked(self, button, entry):
+        # get the default clipboard
+        atom = Gdk.atom_intern('CLIPBOARD', True)
+        clipboard = entry.get_clipboard(atom)
+
+        # set the clipboard's text
+        # FIXME: don't require passing length argument
+        clipboard.set_text(entry.get_text(), -1)
+
+    def paste_received(self, clipboard, text, entry):
+        if text is not None:
+            entry.set_text(text)
+
+    def paste_button_clicked(self, button, entry):
+        # get the default clipboard
+        atom = Gdk.atom_intern('CLIPBOARD', True)
+        clipboard = entry.get_clipboard(atom)
+
+        # set the clipboard's text
+        clipboard.request_text(self.paste_received, entry)
+
+    def get_image_pixbuf(self, image):
+        # FIXME: We should hide storage types in an override
+        storage_type = image.get_storage_type()
+        if storage_type == Gtk.ImageType.PIXBUF:
+            return image.get_pixbuf()
+        elif storage_type == Gtk.ImageType.STOCK:
+            (stock_id, size) = image.get_stock()
+            return image.render_icon(stock_id, size, None)
+
+        return None
+
+    def drag_begin(self, widget, context, data):
+        pixbuf = self.get_image_pixbuf(data)
+        Gtk.drag_set_icon_pixbuf(context, pixbuf, -2, -2)
+
+    def drag_data_get(self, widget, context, selection_data, info, time, data):
+        pixbuf = self.get_image_pixbuf(data)
+        selection_data.set_pixbuf(pixbuf)
+
+    def drag_data_received(self, widget, context, x, y, selection_data, info, time, data):
+        if selection_data.get_length() > 0:
+            pixbuf = selection_data.get_pixbuf()
+            data.set_from_pixbuf(pixbuf)
+
+    def copy_image(self, item, data):
+        # get the default clipboard
+        atom = Gdk.atom_intern('CLIPBOARD', True)
+        clipboard = Gtk.Clipboard.get(atom)
+        pixbuf = self.get_image_pixbuf(data)
+
+        clipboard.set_image(pixbuf)
+
+    def paste_image(self, item, data):
+        # get the default clipboard
+        atom = Gdk.atom_intern('CLIPBOARD', True)
+        clipboard = Gtk.Clipboard.get(atom)
+        pixbuf = clipboard.wait_for_image()
+
+        if pixbuf is not None:
+            data.set_from_pixbuf(pixbuf)
+
+    def button_press(self, widget, event, data):
+        if event.button != 3:
+            return False
+
+        self.menu = Gtk.Menu()
+
+        item = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_COPY, None)
+        item.connect('activate', self.copy_image, data)
+        item.show()
+        self.menu.append(item)
+
+        item = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_PASTE, None)
+        item.connect('activate', self.paste_image, data)
+        item.show()
+        self.menu.append(item)
+
+        self.menu.popup(None, None, None, None, event.button, event.time)
+
+
+def main(demoapp=None):
+    ClipboardApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/colorselector.py b/examples/demo/demos/colorselector.py
new file mode 100644 (file)
index 0000000..d05ca52
--- /dev/null
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Color Selector"
+description = """
+ GtkColorSelection lets the user choose a color. GtkColorSelectionDialog is
+ a prebuilt dialog containing a GtkColorSelection.
+ """
+
+
+from gi.repository import Gtk, Gdk
+
+
+class ColorSelectorApp:
+    def __init__(self):
+        # FIXME: we should allow Gdk.Color to be allocated without parameters
+        #        Also color doesn't seem to work
+        self.color = Gdk.RGBA()
+        self.color.red = 0
+        self.color.blue = 1
+        self.color.green = 0
+        self.color.alpha = 1
+
+        self.window = Gtk.Window()
+        self.window.set_title('Color Selection')
+        self.window.set_border_width(8)
+        self.window.connect('destroy', lambda w: Gtk.main_quit())
+
+        vbox = Gtk.VBox(homogeneous=False,
+                        spacing=8)
+        vbox.set_border_width(8)
+        self.window.add(vbox)
+
+        # create color swatch area
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+        vbox.pack_start(frame, True, True, 0)
+
+        self.da = Gtk.DrawingArea()
+        self.da.connect('draw', self.draw_cb)
+
+        # set a minimum size
+        self.da.set_size_request(200, 200)
+        # set the color
+        self.da.override_background_color(0, self.color)
+        frame.add(self.da)
+
+        alignment = Gtk.Alignment(xalign=1.0,
+                                  yalign=0.5,
+                                  xscale=0.0,
+                                  yscale=0.0)
+
+        button = Gtk.Button(label='_Change the above color',
+                            use_underline=True)
+        alignment.add(button)
+        vbox.pack_start(alignment, False, False, 0)
+
+        button.connect('clicked', self.change_color_cb)
+
+        self.window.show_all()
+
+    def draw_cb(self, widget, cairo_ctx):
+        style = widget.get_style_context()
+        bg_color = style.get_background_color(0)
+        Gdk.cairo_set_source_rgba(cairo_ctx, bg_color)
+        cairo_ctx.paint()
+
+        return True
+
+    def change_color_cb(self, button):
+        dialog = Gtk.ColorSelectionDialog(title='Changing color')
+        dialog.set_transient_for(self.window)
+
+        colorsel = dialog.get_color_selection()
+        colorsel.set_previous_rgba(self.color)
+        colorsel.set_current_rgba(self.color)
+        colorsel.set_has_palette(True)
+
+        response = dialog.run()
+
+        if response == Gtk.ResponseType.OK:
+            self.color = colorsel.get_current_rgba()
+            self.da.override_background_color(0, self.color)
+
+        dialog.destroy()
+
+
+def main(demoapp=None):
+    ColorSelectorApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/combobox.py b/examples/demo/demos/combobox.py
new file mode 100644 (file)
index 0000000..36034ba
--- /dev/null
@@ -0,0 +1,319 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Combo boxes"
+description = """
+The ComboBox widget allows to select one option out of a list.
+The ComboBoxEntry additionally allows the user to enter a value
+that is not in the list of options.
+
+How the options are displayed is controlled by cell renderers.
+ """
+
+
+from gi.repository import Gtk, Gdk, GdkPixbuf, GLib, GObject
+
+
+(PIXBUF_COL,
+ TEXT_COL) = range(2)
+
+
+class MaskEntry(Gtk.Entry):
+    __gtype_name__ = 'MaskEntry'
+
+    def __init__(self, mask=None):
+        self.mask = mask
+        super(MaskEntry, self).__init__()
+
+        self.connect('changed', self.changed_cb)
+
+        self.error_color = Gdk.RGBA()
+        self.error_color.red = 1.0
+        self.error_color.green = 0.9
+        self.error_color_blue = 0.9
+        self.error_color.alpha = 1.0
+
+        # workaround since override_color doesn't accept None yet
+        style_ctx = self.get_style_context()
+        self.normal_color = style_ctx.get_color(0)
+
+    def set_background(self):
+        if self.mask:
+            if not GLib.regex_match_simple(self.mask,
+                                           self.get_text(), 0, 0):
+                self.override_color(0, self.error_color)
+                return
+
+        self.override_color(0, self.normal_color)
+
+    def changed_cb(self, entry):
+        self.set_background()
+
+
+class ComboboxApp:
+    def __init__(self, demoapp):
+        self.demoapp = demoapp
+
+        self.window = Gtk.Window()
+        self.window.set_title('Combo boxes')
+        self.window.set_border_width(10)
+        self.window.connect('destroy', lambda w: Gtk.main_quit())
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=2)
+        self.window.add(vbox)
+
+        frame = Gtk.Frame(label='Some stock icons')
+        vbox.pack_start(frame, False, False, 0)
+
+        box = Gtk.VBox(homogeneous=False, spacing=0)
+        box.set_border_width(5)
+        frame.add(box)
+
+        model = self.create_stock_icon_store()
+        combo = Gtk.ComboBox(model=model)
+        box.add(combo)
+
+        renderer = Gtk.CellRendererPixbuf()
+        combo.pack_start(renderer, False)
+
+        # FIXME: override set_attributes
+        combo.add_attribute(renderer, 'pixbuf', PIXBUF_COL)
+        combo.set_cell_data_func(renderer, self.set_sensitive, None)
+
+        renderer = Gtk.CellRendererText()
+        combo.pack_start(renderer, True)
+        combo.add_attribute(renderer, 'text', TEXT_COL)
+        combo.set_cell_data_func(renderer, self.set_sensitive, None)
+
+        combo.set_row_separator_func(self.is_separator, None)
+        combo.set_active(0)
+
+        # a combobox demonstrating trees
+        frame = Gtk.Frame(label='Where are we ?')
+        vbox.pack_start(frame, False, False, 0)
+
+        box = Gtk.VBox(homogeneous=False, spacing=0)
+        box.set_border_width(5)
+        frame.add(box)
+
+        model = self.create_capital_store()
+        combo = Gtk.ComboBox(model=model)
+        box.add(combo)
+
+        renderer = Gtk.CellRendererText()
+        combo.pack_start(renderer, True)
+        combo.add_attribute(renderer, 'text', 0)
+        combo.set_cell_data_func(renderer, self.is_capital_sensistive, None)
+
+        # FIXME: make new_from_indices work
+        #        make constructor take list or string of indices
+        path = Gtk.TreePath.new_from_string('0:8')
+        treeiter = model.get_iter(path)
+        combo.set_active_iter(treeiter)
+
+        # A GtkComboBoxEntry with validation.
+
+        frame = Gtk.Frame(label='Editable')
+        vbox.pack_start(frame, False, False, 0)
+
+        box = Gtk.VBox(homogeneous=False, spacing=0)
+        box.set_border_width(5)
+        frame.add(box)
+
+        combo = Gtk.ComboBoxText.new_with_entry()
+        self.fill_combo_entry(combo)
+        box.add(combo)
+
+        entry = MaskEntry(mask='^([0-9]*|One|Two|2\302\275|Three)$')
+
+        Gtk.Container.remove(combo, combo.get_child())
+        combo.add(entry)
+
+        # A combobox with string IDs
+
+        frame = Gtk.Frame(label='String IDs')
+        vbox.pack_start(frame, False, False, 0)
+
+        box = Gtk.VBox(homogeneous=False, spacing=0)
+        box.set_border_width(5)
+        frame.add(box)
+
+        # FIXME: model is not setup when constructing Gtk.ComboBoxText()
+        #        so we call new() - Gtk should fix this to setup the model
+        #        in __init__, not in the constructor
+        combo = Gtk.ComboBoxText.new()
+        combo.append('never', 'Not visible')
+        combo.append('when-active', 'Visible when active')
+        combo.append('always', 'Always visible')
+        box.add(combo)
+
+        entry = Gtk.Entry()
+
+        combo.bind_property('active-id',
+                            entry, 'text',
+                            GObject.BindingFlags.BIDIRECTIONAL)
+
+        box.add(entry)
+        self.window.show_all()
+
+    def strip_underscore(self, s):
+        return s.replace('_', '')
+
+    def create_stock_icon_store(self):
+        stock_id = (Gtk.STOCK_DIALOG_WARNING,
+                    Gtk.STOCK_STOP,
+                    Gtk.STOCK_NEW,
+                    Gtk.STOCK_CLEAR,
+                    None,
+                    Gtk.STOCK_OPEN)
+
+        cellview = Gtk.CellView()
+        store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
+
+        for id in stock_id:
+            if id is not None:
+                pixbuf = cellview.render_icon(id, Gtk.IconSize.BUTTON, None)
+                item = Gtk.stock_lookup(id)
+                label = self.strip_underscore(item.label)
+                store.append((pixbuf, label))
+            else:
+                store.append((None, 'separator'))
+
+        return store
+
+    def set_sensitive(self, cell_layout, cell, tree_model, treeiter, data):
+        """
+        A GtkCellLayoutDataFunc that demonstrates how one can control
+        sensitivity of rows. This particular function does nothing
+        useful and just makes the second row insensitive.
+        """
+
+        path = tree_model.get_path(treeiter)
+        indices = path.get_indices()
+
+        sensitive = not(indices[0] == 1)
+
+        cell.set_property('sensitive', sensitive)
+
+    def is_separator(self, model, treeiter, data):
+        """
+        A GtkTreeViewRowSeparatorFunc that demonstrates how rows can be
+        rendered as separators. This particular function does nothing
+        useful and just turns the fourth row into a separator.
+        """
+
+        path = model.get_path(treeiter)
+
+        indices = path.get_indices()
+        result = (indices[0] == 4)
+
+        return result
+
+    def create_capital_store(self):
+        capitals = (
+            {'group': 'A - B', 'capital': None},
+            {'group': None, 'capital': 'Albany'},
+            {'group': None, 'capital': 'Annapolis'},
+            {'group': None, 'capital': 'Atlanta'},
+            {'group': None, 'capital': 'Augusta'},
+            {'group': None, 'capital': 'Austin'},
+            {'group': None, 'capital': 'Baton Rouge'},
+            {'group': None, 'capital': 'Bismarck'},
+            {'group': None, 'capital': 'Boise'},
+            {'group': None, 'capital': 'Boston'},
+            {'group': 'C - D', 'capital': None},
+            {'group': None, 'capital': 'Carson City'},
+            {'group': None, 'capital': 'Charleston'},
+            {'group': None, 'capital': 'Cheyeene'},
+            {'group': None, 'capital': 'Columbia'},
+            {'group': None, 'capital': 'Columbus'},
+            {'group': None, 'capital': 'Concord'},
+            {'group': None, 'capital': 'Denver'},
+            {'group': None, 'capital': 'Des Moines'},
+            {'group': None, 'capital': 'Dover'},
+            {'group': 'E - J', 'capital': None},
+            {'group': None, 'capital': 'Frankfort'},
+            {'group': None, 'capital': 'Harrisburg'},
+            {'group': None, 'capital': 'Hartford'},
+            {'group': None, 'capital': 'Helena'},
+            {'group': None, 'capital': 'Honolulu'},
+            {'group': None, 'capital': 'Indianapolis'},
+            {'group': None, 'capital': 'Jackson'},
+            {'group': None, 'capital': 'Jefferson City'},
+            {'group': None, 'capital': 'Juneau'},
+            {'group': 'K - O', 'capital': None},
+            {'group': None, 'capital': 'Lansing'},
+            {'group': None, 'capital': 'Lincon'},
+            {'group': None, 'capital': 'Little Rock'},
+            {'group': None, 'capital': 'Madison'},
+            {'group': None, 'capital': 'Montgomery'},
+            {'group': None, 'capital': 'Montpelier'},
+            {'group': None, 'capital': 'Nashville'},
+            {'group': None, 'capital': 'Oklahoma City'},
+            {'group': None, 'capital': 'Olympia'},
+            {'group': 'P - S', 'capital': None},
+            {'group': None, 'capital': 'Phoenix'},
+            {'group': None, 'capital': 'Pierre'},
+            {'group': None, 'capital': 'Providence'},
+            {'group': None, 'capital': 'Raleigh'},
+            {'group': None, 'capital': 'Richmond'},
+            {'group': None, 'capital': 'Sacramento'},
+            {'group': None, 'capital': 'Salem'},
+            {'group': None, 'capital': 'Salt Lake City'},
+            {'group': None, 'capital': 'Santa Fe'},
+            {'group': None, 'capital': 'Springfield'},
+            {'group': None, 'capital': 'St. Paul'},
+            {'group': 'T - Z', 'capital': None},
+            {'group': None, 'capital': 'Tallahassee'},
+            {'group': None, 'capital': 'Topeka'},
+            {'group': None, 'capital': 'Trenton'}
+        )
+
+        parent = None
+
+        store = Gtk.TreeStore(str)
+
+        for item in capitals:
+            if item['group']:
+                parent = store.append(None, (item['group'],))
+            elif item['capital']:
+                store.append(parent, (item['capital'],))
+
+        return store
+
+    def is_capital_sensistive(self, cell_layout, cell, tree_model, treeiter, data):
+        sensitive = not tree_model.iter_has_child(treeiter)
+        cell.set_property('sensitive', sensitive)
+
+    def fill_combo_entry(self, entry):
+        entry.append_text('One')
+        entry.append_text('Two')
+        entry.append_text('2\302\275')
+        entry.append_text('Three')
+
+
+def main(demoapp=None):
+    ComboboxApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/data/alphatest.png b/examples/demo/demos/data/alphatest.png
new file mode 100644 (file)
index 0000000..eb5885f
Binary files /dev/null and b/examples/demo/demos/data/alphatest.png differ
diff --git a/examples/demo/demos/data/apple-red.png b/examples/demo/demos/data/apple-red.png
new file mode 100644 (file)
index 0000000..b0a24e9
Binary files /dev/null and b/examples/demo/demos/data/apple-red.png differ
diff --git a/examples/demo/demos/data/background.jpg b/examples/demo/demos/data/background.jpg
new file mode 100644 (file)
index 0000000..86c006a
Binary files /dev/null and b/examples/demo/demos/data/background.jpg differ
diff --git a/examples/demo/demos/data/brick.png b/examples/demo/demos/data/brick.png
new file mode 100644 (file)
index 0000000..d413cd2
Binary files /dev/null and b/examples/demo/demos/data/brick.png differ
diff --git a/examples/demo/demos/data/brick2.png b/examples/demo/demos/data/brick2.png
new file mode 100644 (file)
index 0000000..cfcd079
Binary files /dev/null and b/examples/demo/demos/data/brick2.png differ
diff --git a/examples/demo/demos/data/css_accordion.css b/examples/demo/demos/data/css_accordion.css
new file mode 100644 (file)
index 0000000..a243427
--- /dev/null
@@ -0,0 +1,52 @@
+@import url("resource://css_accordion/reset.css");
+
+* {
+    transition-property: color, background-color, border-color, background-image, padding, border-width;
+    transition-duration: 1s;
+
+    font: Sans 20px;
+}
+
+GtkWindow {
+    background: linear-gradient(153deg, #151515, #151515 5px, transparent 5px) 0 0,
+                linear-gradient(333deg, #151515, #151515 5px, transparent 5px) 10px 5px,
+                linear-gradient(153deg, #222, #222 5px, transparent 5px) 0 5px,
+                linear-gradient(333deg, #222, #222 5px, transparent 5px) 10px 10px,
+                linear-gradient(90deg, #1b1b1b, #1b1b1b 10px, transparent 10px),
+                linear-gradient(#1d1d1d, #1d1d1d 25%, #1a1a1a 25%, #1a1a1a 50%, transparent 50%, transparent 75%, #242424 75%, #242424);
+    background-color: #131313;
+    background-size: 20px 20px;
+}
+
+.button {
+    color: black;
+    background-color: #bbb;
+    border-style: solid;
+    border-width: 2px 0 2px 2px;
+    border-color: #333;
+
+    padding: 12px 4px;
+}
+
+.button:first-child {
+    border-radius: 5px 0 0 5px;
+}
+
+.button:last-child {
+    border-radius: 0 5px 5px 0;
+    border-width: 2px;
+}
+
+.button:hover {
+    padding: 12px 48px;
+    background-color: #4870bc;
+}
+
+.button *:hover {
+    color: white;
+}
+
+.button:hover:active,
+.button:active {
+    background-color: #993401;
+}
diff --git a/examples/demo/demos/data/css_basics.css b/examples/demo/demos/data/css_basics.css
new file mode 100644 (file)
index 0000000..62dba7a
--- /dev/null
@@ -0,0 +1,22 @@
+/* You can edit the text in this window to change the
+ * appearance of this Window.
+ * Be careful, if you screw it up, nothing might be visible
+ * anymore. :)
+ */
+
+/* This CSS resets all properties to their defaults values
+ *    and overrides all user settings and the theme in use */
+@import url("resource://css_basics/reset.css");
+
+/* Set a very futuristic style by default */
+* {
+  color: green;
+  font-family: Monospace;
+  border: 1px solid;
+}
+
+/* Make sure selections are visible */
+:selected {
+  background-color: darkGreen;
+  color: black;
+}
diff --git a/examples/demo/demos/data/css_multiplebgs.css b/examples/demo/demos/data/css_multiplebgs.css
new file mode 100644 (file)
index 0000000..eb9d4d6
--- /dev/null
@@ -0,0 +1,136 @@
+/* You can edit the text in this window to change the
+ * appearance of this Window.
+ * Be careful, if you screw it up, nothing might be visible
+ * anymore. :)
+ */
+
+/* This CSS resets all properties to their defaults values
+ *    and overrides all user settings and the theme in use */
+@import url("resource://css_multiplebgs/reset.css");
+@import url("resource://css_multiplebgs/cssview.css");
+
+#canvas {
+    transition-property: background-color, background-image;
+    transition-duration: 0.5s;
+
+    background-color: #4870bc;
+}
+
+/* The gradients below are adapted versions of Lea Verou's CSS3 patterns,
+ * licensed under the MIT license:
+ * Copyright (c) 2011 Lea Verou, http://lea.verou.me/
+ *
+ * See https://github.com/LeaVerou/CSS3-Patterns-Gallery
+ */
+
+/**********
+ * Bricks *
+ **********/
+/*
+@define-color brick_hi #d42;
+@define-color brick_lo #b42;
+@define-color brick_hi_backdrop #888;
+@define-color brick_lo_backdrop #999;
+
+#canvas {
+    background-color: #999;
+    background-image: linear-gradient(205deg, @brick_lo, @brick_lo 23px, transparent 23px),
+                      linear-gradient(25deg, @brick_hi, @brick_hi 23px, transparent 23px),
+                      linear-gradient(205deg, @brick_lo, @brick_lo 23px, transparent 23px),
+                      linear-gradient(25deg, @brick_hi, @brick_hi 23px, transparent 23px);
+    background-size: 58px 58px;
+    background-position: 0px 6px, 4px 31px, 29px 35px, 34px 2px;
+}
+
+#canvas:backdrop {
+    background-color: #444;
+    background-image: linear-gradient(205deg, @brick_lo_backdrop, @brick_lo_backdrop 23px, transparent 23px),
+                      linear-gradient(25deg, @brick_hi_backdrop, @brick_hi_backdrop 23px, transparent 23px),
+                     linear-gradient(205deg, @brick_lo_backdrop, @brick_lo_backdrop 23px, transparent 23px),
+                     linear-gradient(25deg, @brick_hi_backdrop, @brick_hi_backdrop 23px, transparent 23px);
+    background-size: 58px 58px;
+    background-position: 0px 6px, 4px 31px, 29px 35px, 34px 2px;
+}
+*/
+
+/*
+#bricks-button {
+    background-color: #eef;
+    background-image: -gtk-scaled(url('resource://css_multiplebgs/brick.png'),url('resource://css_multiplebgs/brick2.png'));
+    background-repeat: no-repeat;
+    background-position: center;
+}
+
+*/
+/**********
+ * Tartan *
+ **********/
+/*
+@define-color tartan_bg #662e2c;
+@define-color tartan_bg_backdrop #333;
+
+#canvas {
+    background-color: @tartan_bg;
+    background-image: repeating-linear-gradient(transparent, transparent 50px, rgba(0,0,0,.4) 50px,
+                                                rgba(0,0,0,.4) 53px, transparent 53px, transparent 63px,
+                                                rgba(0,0,0,.4) 63px, rgba(0,0,0,.4) 66px, transparent 66px,
+                                                transparent 116px, rgba(0,0,0,.5) 116px, rgba(0,0,0,.5) 166px,
+                                                rgba(255,255,255,.2) 166px, rgba(255,255,255,.2) 169px, rgba(0,0,0,.5) 169px,
+                                                rgba(0,0,0,.5) 179px, rgba(255,255,255,.2) 179px, rgba(255,255,255,.2) 182px,
+                                                rgba(0,0,0,.5) 182px, rgba(0,0,0,.5) 232px, transparent 232px),
+                      repeating-linear-gradient(90deg, transparent, transparent 50px, rgba(0,0,0,.4) 50px, rgba(0,0,0,.4) 53px,
+                                                transparent 53px, transparent 63px, rgba(0,0,0,.4) 63px, rgba(0,0,0,.4) 66px,
+                                                transparent 66px, transparent 116px, rgba(0,0,0,.5) 116px, rgba(0,0,0,.5) 166px,
+                                                rgba(255,255,255,.2) 166px, rgba(255,255,255,.2) 169px, rgba(0,0,0,.5) 169px,
+                                                rgba(0,0,0,.5) 179px, rgba(255,255,255,.2) 179px, rgba(255,255,255,.2) 182px,
+                                                rgba(0,0,0,.5) 182px, rgba(0,0,0,.5) 232px, transparent 232px),
+                      repeating-linear-gradient(-55deg, transparent, transparent 1px, rgba(0,0,0,.2) 1px, rgba(0,0,0,.2) 4px,
+                                                transparent 4px, transparent 19px, rgba(0,0,0,.2) 19px,
+                                                rgba(0,0,0,.2) 24px, transparent 24px, transparent 51px, rgba(0,0,0,.2) 51px,
+                                                rgba(0,0,0,.2) 54px, transparent 54px, transparent 74px);
+}
+
+#canvas:backdrop {
+    background-color: @tartan_bg_backdrop;
+}
+*/
+
+/***********
+ * Stripes *
+ ***********/
+
+/*
+@define-color base_bg #4870bc;
+@define-color backdrop_bg #555;
+
+#canvas {
+  background-color: @base_bg;
+  background-image: linear-gradient(to left, transparent, rgba(255,255,255,.07) 50%, transparent 50%),
+                    linear-gradient(to left, transparent, rgba(255,255,255,.13) 50%, transparent 50%),
+                    linear-gradient(to left, transparent, transparent 50%, rgba(255,255,255,.17) 50%),
+                    linear-gradient(to left, transparent, transparent 50%, rgba(255,255,255,.19) 50%);
+  background-size: 29px, 59px, 73px, 109px;
+}
+
+#canvas:backdrop {
+  background-color: @backdrop_bg;
+}
+*/
+
+/***************
+ * Lined Paper *
+ ***************/
+/*
+#canvas {
+    background-color: #fff;
+    background-image: linear-gradient(90deg, transparent 79px, alpha(#f98195, 0.40) 79px, #f98195 80px, alpha(#f98195, 0.40) 81px, transparent 81px),
+                      linear-gradient(alpha(#77c5cf, 0.60), alpha(#77c5cf, 0.60) 1px, transparent 1px);
+    background-size: 100% 36px;
+}
+
+#canvas:backdrop {
+    background-color: #f1f2f4;
+    background-image: linear-gradient(90deg, transparent 79px, alpha(#999, 0.40) 79px, #999 80px, alpha(#999, 0.40) 81px, transparent 81px),
+                      linear-gradient(alpha(#bbb, 0.60), alpha(#bbb, 0.60) 1px, transparent 1px);
+}
+*/
diff --git a/examples/demo/demos/data/cssview.css b/examples/demo/demos/data/cssview.css
new file mode 100644 (file)
index 0000000..5060c39
--- /dev/null
@@ -0,0 +1,41 @@
+/* Make the text editor has a nice style */
+.view {
+  color: #2e3436;
+  font: Monospace;
+  background-color: alpha(white, 0.30);
+}
+
+.view:selected {
+  color: white;
+  background-color: #4a90d9;
+}
+
+.scrollbar.trough,
+.scrollbars-junction {
+  background-color: alpha(white, 0.80);
+}
+
+.scrollbar.slider {
+  border-width: 3px;
+  border-style: solid;
+  border-radius: 10px;
+  border-color: transparent;
+  background-clip: padding-box;
+  background-color: #999;
+}
+
+.scrollbar.slider:prelight {
+  background-color: #555;
+}
+
+.pane-separator {
+  background-color: alpha(white, 0.80);
+  background-image: linear-gradient(transparent, transparent 1px, #999 1px, #999 4px, transparent 4px);
+  background-size: 40px auto;
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+.pane-separator:prelight {
+  background-image: linear-gradient(transparent, transparent 1px, #555 1px, #555 4px, transparent 4px);
+}
diff --git a/examples/demo/demos/data/demo.gresource b/examples/demo/demos/data/demo.gresource
new file mode 100644 (file)
index 0000000..e19d822
Binary files /dev/null and b/examples/demo/demos/data/demo.gresource differ
diff --git a/examples/demo/demos/data/demo.gresource.xml b/examples/demo/demos/data/demo.gresource.xml
new file mode 100644 (file)
index 0000000..866769f
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/css_accordion">
+    <file>css_accordion.css</file>
+    <file>reset.css</file>
+  </gresource>
+  <gresource prefix="/css_basics">
+    <file>css_basics.css</file>
+    <file>reset.css</file>
+  </gresource>
+  <gresource prefix="/css_multiplebgs">
+    <file>css_multiplebgs.css</file>
+    <file>brick.png</file>
+    <file>brick2.png</file>
+    <file>cssview.css</file>
+    <file>reset.css</file>
+  </gresource>
+</gresources>
diff --git a/examples/demo/demos/data/demo.ui b/examples/demo/demos/data/demo.ui
new file mode 100644 (file)
index 0000000..57dd232
--- /dev/null
@@ -0,0 +1,258 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<interface domain="gtk20">
+    <object class="GtkListStore" id="liststore1">
+      <columns>
+        <column type="gchararray"/>
+        <column type="gchararray"/>
+        <column type="gint"/>
+        <column type="gchararray"/>
+      </columns>
+      <data>
+        <row>
+          <col id="0" translatable="yes">John</col>
+          <col id="1" translatable="yes">Doe</col>
+          <col id="2">25</col>
+          <col id="3" translatable="yes">This is the John Doe row</col>
+        </row>
+        <row>
+          <col id="0" translatable="yes">Mary</col>
+          <col id="1" translatable="yes">Unknown</col>
+          <col id="2">50</col>
+          <col id="3" translatable="yes">This is the Mary Unknown row</col>
+        </row>
+      </data>
+    </object>
+    <object class="GtkUIManager" id="uimanager">
+        <child>
+            <object class="GtkActionGroup" id="DefaultActions">
+                <child>
+                    <object class="GtkAction" id="Copy">
+                        <property name="name">Copy</property>
+                        <property name="tooltip" translatable="yes">Copy selected object into the clipboard</property>
+                        <property name="stock_id">gtk-copy</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="Cut">
+                        <property name="name">Cut</property>
+                        <property name="tooltip" translatable="yes">Cut selected object into the clipboard</property>
+                        <property name="stock_id">gtk-cut</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="EditMenu">
+                        <property name="name">EditMenu</property>
+                        <property name="label" translatable="yes">_Edit</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="FileMenu">
+                        <property name="name">FileMenu</property>
+                        <property name="label" translatable="yes">_File</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="New">
+                        <property name="name">New</property>
+                        <property name="tooltip" translatable="yes">Create a new file</property>
+                        <property name="stock_id">gtk-new</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="Open">
+                        <property name="name">Open</property>
+                        <property name="tooltip" translatable="yes">Open a file</property>
+                        <property name="stock_id">gtk-open</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="Paste">
+                        <property name="name">Paste</property>
+                        <property name="tooltip" translatable="yes">Paste object from the Clipboard</property>
+                        <property name="stock_id">gtk-paste</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="Quit">
+                        <property name="name">Quit</property>
+                        <property name="tooltip" translatable="yes">Quit the program</property>
+                        <property name="stock_id">gtk-quit</property>
+                        <signal handler="quit_activate" name="activate"/>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="Save">
+                        <property name="name">Save</property>
+                        <property name="is_important">True</property>
+                        <property name="tooltip" translatable="yes">Save a file</property>
+                        <property name="stock_id">gtk-save</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="SaveAs">
+                        <property name="name">SaveAs</property>
+                        <property name="tooltip" translatable="yes">Save with a different name</property>
+                        <property name="stock_id">gtk-save-as</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="HelpMenu">
+                        <property name="name">HelpMenu</property>
+                        <property name="label" translatable="yes">_Help</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="About">
+                        <property name="name">About</property>
+                        <property name="stock_id">gtk-about</property>
+                        <signal handler="about_activate" name="activate"/>
+                    </object>
+                    <accelerator key="F1"/>
+                </child>
+            </object>
+        </child>
+        <ui>
+          <menubar name="menubar1">
+            <menu action="FileMenu" name="FileMenu">
+              <menuitem action="New" name="New"/>
+              <menuitem action="Open" name="Open"/>
+              <menuitem action="Save" name="Save"/>
+              <menuitem action="SaveAs" name="SaveAs"/>
+              <separator/>
+              <menuitem action="Quit" name="Quit"/>
+            </menu>
+            <menu action="EditMenu">
+              <menuitem action="Copy" name="Copy"/>
+              <menuitem action="Cut" name="Cut"/>
+              <menuitem action="Paste" name="Paste"/>
+            </menu>
+            <menu action="HelpMenu" name="HelpMenu">
+              <menuitem action="About" name="About"/>
+            </menu>
+          </menubar>
+          <toolbar name="toolbar1">
+            <toolitem action="New" name="New"/>
+            <toolitem action="Open" name="Open"/>
+            <toolitem action="Save" name="Save"/>
+            <separator/>
+            <toolitem action="Copy" name="Copy"/>
+            <toolitem action="Cut" name="Cut"/>
+            <toolitem action="Paste" name="Paste"/>
+          </toolbar>
+        </ui>
+    </object>
+    <object class="GtkAboutDialog" id="aboutdialog1">
+        <property name="program-name" translatable="yes">GtkBuilder demo</property>
+         <accessibility>
+            <relation target="window1" type="subwindow-of"/>
+        </accessibility>
+    </object>
+    <object class="GtkWindow" id="window1">
+        <property name="default_height">250</property>
+        <property name="default_width">440</property>
+        <property name="title">GtkBuilder demo</property>
+        <child>
+            <object class="GtkVBox" id="vbox1">
+                <property name="visible">True</property>
+                <child>
+                    <object constructor="uimanager" class="GtkMenuBar" id="menubar1">
+                        <property name="visible">True</property>
+                       <child internal-child="accessible">
+                           <object class="AtkObject" id="a11y-menubar">
+                               <property name="AtkObject::accessible-name">The menubar</property>
+                           </object>
+                       </child>
+                   </object>
+                    <packing>
+                        <property name="expand">False</property>
+                    </packing>
+                </child>
+                <child>
+                    <object constructor="uimanager" class="GtkToolbar" id="toolbar1">
+                        <property name="visible">True</property>
+                       <child internal-child="accessible">
+                           <object class="AtkObject" id="a11y-toolbar">
+                               <property name="AtkObject::accessible-name">The toolbar</property>
+                           </object>
+                       </child>
+                    </object>
+                    <packing>
+                        <property name="expand">False</property>
+                        <property name="position">1</property>
+                    </packing>
+                </child>
+                <child>
+                    <object class="GtkScrolledWindow" id="scrolledwindow1">
+                      <property name="hscrollbar_policy">automatic</property>
+                      <property name="shadow_type">in</property>
+                      <property name="visible">True</property>
+                      <property name="vscrollbar_policy">automatic</property>
+                      <child>
+                        <object class="GtkTreeView" id="treeview1">
+                          <property name="visible">True</property>
+                          <property name="model">liststore1</property>
+                          <property name="tooltip-column">3</property>
+                         <child internal-child="accessible">
+                             <object class="AtkObject" id="a11y-treeview">
+                                 <property name="AtkObject::accessible-name">Name list</property>
+                                  <property name="AtkObject::accessible-description">
+                                    A list of person with name, surname and age columns
+                                  </property>
+                             </object>
+                         </child>
+                          <child>
+                            <object class="GtkTreeViewColumn" id="column1">
+                              <property name="title">Name</property>
+                              <child>
+                                <object class="GtkCellRendererText" id="renderer1"/>
+                                <attributes>
+                                  <attribute name="text">0</attribute>
+                                </attributes>
+                              </child>
+                            </object>
+                          </child>
+                          <child>
+                            <object class="GtkTreeViewColumn" id="column2">
+                              <property name="title">Surname</property>
+                              <child>
+                                <object class="GtkCellRendererText" id="renderer2"/>
+                                <attributes>
+                                  <attribute name="text">1</attribute>
+                                </attributes>
+                              </child>
+                            </object>
+                          </child>
+                          <child>
+                            <object class="GtkTreeViewColumn" id="column3">
+                              <property name="title">Age</property>
+                              <child>
+                                <object class="GtkCellRendererText" id="renderer3"/>
+                                <attributes>
+                                  <attribute name="text">2</attribute>
+                                </attributes>
+                              </child>
+                            </object>
+                          </child>
+                        </object>
+                      </child>
+                   <accessibility>
+                       <action action_name="move-cursor" description="Move the cursor to select another person."/>
+                   </accessibility>
+                    </object>
+                    <packing>
+                        <property name="position">2</property>
+                    </packing>
+                </child>
+                <child>
+                    <object class="GtkStatusbar" id="statusbar1">
+                        <property name="visible">True</property>
+                    </object>
+                    <packing>
+                        <property name="expand">False</property>
+                        <property name="position">3</property>
+                    </packing>
+                </child>
+            </object>
+        </child>
+    </object>
+</interface>
diff --git a/examples/demo/demos/data/floppybuddy.gif b/examples/demo/demos/data/floppybuddy.gif
new file mode 100644 (file)
index 0000000..ac986c8
Binary files /dev/null and b/examples/demo/demos/data/floppybuddy.gif differ
diff --git a/examples/demo/demos/data/gnome-applets.png b/examples/demo/demos/data/gnome-applets.png
new file mode 100644 (file)
index 0000000..8d3549e
Binary files /dev/null and b/examples/demo/demos/data/gnome-applets.png differ
diff --git a/examples/demo/demos/data/gnome-calendar.png b/examples/demo/demos/data/gnome-calendar.png
new file mode 100644 (file)
index 0000000..889f329
Binary files /dev/null and b/examples/demo/demos/data/gnome-calendar.png differ
diff --git a/examples/demo/demos/data/gnome-foot.png b/examples/demo/demos/data/gnome-foot.png
new file mode 100644 (file)
index 0000000..0476658
Binary files /dev/null and b/examples/demo/demos/data/gnome-foot.png differ
diff --git a/examples/demo/demos/data/gnome-fs-directory.png b/examples/demo/demos/data/gnome-fs-directory.png
new file mode 100644 (file)
index 0000000..05921a6
Binary files /dev/null and b/examples/demo/demos/data/gnome-fs-directory.png differ
diff --git a/examples/demo/demos/data/gnome-fs-regular.png b/examples/demo/demos/data/gnome-fs-regular.png
new file mode 100644 (file)
index 0000000..0f5019c
Binary files /dev/null and b/examples/demo/demos/data/gnome-fs-regular.png differ
diff --git a/examples/demo/demos/data/gnome-gimp.png b/examples/demo/demos/data/gnome-gimp.png
new file mode 100644 (file)
index 0000000..f6bbc6d
Binary files /dev/null and b/examples/demo/demos/data/gnome-gimp.png differ
diff --git a/examples/demo/demos/data/gnome-gmush.png b/examples/demo/demos/data/gnome-gmush.png
new file mode 100644 (file)
index 0000000..0a4b0d0
Binary files /dev/null and b/examples/demo/demos/data/gnome-gmush.png differ
diff --git a/examples/demo/demos/data/gnome-gsame.png b/examples/demo/demos/data/gnome-gsame.png
new file mode 100644 (file)
index 0000000..01c0611
Binary files /dev/null and b/examples/demo/demos/data/gnome-gsame.png differ
diff --git a/examples/demo/demos/data/gnu-keys.png b/examples/demo/demos/data/gnu-keys.png
new file mode 100644 (file)
index 0000000..58a3377
Binary files /dev/null and b/examples/demo/demos/data/gnu-keys.png differ
diff --git a/examples/demo/demos/data/gtk-logo-rgb.gif b/examples/demo/demos/data/gtk-logo-rgb.gif
new file mode 100644 (file)
index 0000000..63c622b
Binary files /dev/null and b/examples/demo/demos/data/gtk-logo-rgb.gif differ
diff --git a/examples/demo/demos/data/reset.css b/examples/demo/demos/data/reset.css
new file mode 100644 (file)
index 0000000..1c27a8e
--- /dev/null
@@ -0,0 +1,68 @@
+/* @import this colorsheet to get the default values for every property.
+ * This is useful when writing special CSS tests that should not be
+ * inluenced by themes - not even the default ones.
+ * Keep in mind that the output will be very ugly and not look like
+ * anything GTK.
+ * Also, when adding new style properties, please add them here.
+ */
+
+* {
+  color: inherit;
+  font-size: inherit;
+  background-color: initial;
+  font-family: inherit;
+  font-style: inherit;
+  font-variant: inherit;
+  font-weight: inherit;
+  text-shadow: inherit;
+  icon-shadow: inherit;
+  box-shadow: initial;
+  margin-top: initial;
+  margin-left: initial;
+  margin-bottom: initial;
+  margin-right: initial;
+  padding-top: initial;
+  padding-left: initial;
+  padding-bottom: initial;
+  padding-right: initial;
+  border-top-style: initial;
+  border-top-width: initial;
+  border-left-style: initial;
+  border-left-width: initial;
+  border-bottom-style: initial;
+  border-bottom-width: initial;
+  border-right-style: initial;
+  border-right-width: initial;
+  border-top-left-radius: initial;
+  border-top-right-radius: initial;
+  border-bottom-right-radius: initial;
+  border-bottom-left-radius: initial;
+  outline-style: initial;
+  outline-width: initial;
+  outline-offset: initial;
+  background-clip: initial;
+  background-origin: initial;
+  background-size: initial;
+  background-position: initial;
+  border-top-color: initial;
+  border-right-color: initial;
+  border-bottom-color: initial;
+  border-left-color: initial;
+  outline-color:  initial;
+  background-repeat: initial;
+  background-image: initial;
+  border-image-source: initial;
+  border-image-repeat: initial;
+  border-image-slice: initial;
+  border-image-width: initial;
+  transition-property: initial;
+  transition-duration: initial;
+  transition-timing-function: initial;
+  transition-delay: initial;
+  engine: initial;
+  gtk-key-bindings: initial;
+
+  -GtkWidget-focus-line-width: 0;
+  -GtkWidget-focus-padding: 0;
+  -GtkNotebook-initial-gap: 0;
+}
diff --git a/examples/demo/demos/dialogs.py b/examples/demo/demos/dialogs.py
new file mode 100644 (file)
index 0000000..47d6822
--- /dev/null
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Dialog and Message Boxes"
+description = """
+Dialog widgets are used to pop up a transient window for user feedback.
+"""
+
+from gi.repository import Gtk
+
+
+class DialogsApp:
+    def __init__(self):
+        self.dialog_counter = 1
+
+        self.window = Gtk.Window(title="Dialogs")
+        self.window.set_border_width(8)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        frame = Gtk.Frame(label="Dialogs")
+        self.window.add(frame)
+
+        vbox = Gtk.VBox(spacing=8)
+        vbox.set_border_width(8)
+        frame.add(vbox)
+
+        # Standard message dialog
+        hbox = Gtk.HBox(spacing=8)
+        vbox.pack_start(hbox, False, False, 0)
+        button = Gtk.Button.new_with_mnemonic("_Message Dialog")
+
+        button.connect('clicked',
+                       self._message_dialog_clicked)
+        hbox.pack_start(button, False, False, 0)
+
+        vbox.pack_start(Gtk.HSeparator(),
+                        False, False, 0)
+
+        # Interactive dialog
+        hbox = Gtk.HBox(spacing=8)
+        vbox.pack_start(hbox, False, False, 0)
+        vbox2 = Gtk.VBox(spacing=0)
+        button = Gtk.Button.new_with_mnemonic("_Interactive Dialog")
+
+        button.connect('clicked',
+                       self._interactive_dialog_clicked)
+        hbox.pack_start(vbox2, False, False, 0)
+        vbox2.pack_start(button, False, False, 0)
+
+        table = Gtk.Table(n_rows=2, n_columns=2, homogeneous=False)
+        table.set_row_spacings(4)
+        table.set_col_spacings(4)
+        hbox.pack_start(table, False, False, 0)
+
+        label = Gtk.Label.new_with_mnemonic("_Entry 1")
+        table.attach_defaults(label, 0, 1, 0, 1)
+
+        self.entry1 = Gtk.Entry()
+        table.attach_defaults(self.entry1, 1, 2, 0, 1)
+        label.set_mnemonic_widget(self.entry1)
+
+        label = Gtk.Label.new_with_mnemonic("E_ntry 2")
+
+        table.attach_defaults(label, 0, 1, 1, 2)
+
+        self.entry2 = Gtk.Entry()
+        table.attach_defaults(self.entry2, 1, 2, 1, 2)
+        label.set_mnemonic_widget(self.entry2)
+
+        self.window.show_all()
+
+    def _interactive_dialog_clicked(self, button):
+        dialog = Gtk.Dialog(title='Interactive Dialog',
+                            transient_for=self.window,
+                            modal=True,
+                            destroy_with_parent=True)
+        dialog.add_buttons(Gtk.STOCK_OK, Gtk.ResponseType.OK,
+                           "_Non-stock Button", Gtk.ResponseType.CANCEL)
+
+        content_area = dialog.get_content_area()
+        hbox = Gtk.HBox(spacing=8)
+        hbox.set_border_width(8)
+        content_area.pack_start(hbox, False, False, 0)
+
+        stock = Gtk.Image(stock=Gtk.STOCK_DIALOG_QUESTION,
+                          icon_size=Gtk.IconSize.DIALOG)
+
+        hbox.pack_start(stock, False, False, 0)
+
+        table = Gtk.Table(n_rows=2, n_columns=2, homogeneous=False)
+        table.set_row_spacings(4)
+        table.set_col_spacings(4)
+        hbox.pack_start(table, True, True, 0)
+        label = Gtk.Label.new_with_mnemonic("_Entry 1")
+        table.attach_defaults(label, 0, 1, 0, 1)
+        local_entry1 = Gtk.Entry()
+        local_entry1.set_text(self.entry1.get_text())
+        table.attach_defaults(local_entry1, 1, 2, 0, 1)
+        label.set_mnemonic_widget(local_entry1)
+
+        label = Gtk.Label.new_with_mnemonic("E_ntry 2")
+        table.attach_defaults(label, 0, 1, 1, 2)
+
+        local_entry2 = Gtk.Entry()
+        local_entry2.set_text(self.entry2.get_text())
+        table.attach_defaults(local_entry2, 1, 2, 1, 2)
+        label.set_mnemonic_widget(local_entry2)
+
+        hbox.show_all()
+
+        response = dialog.run()
+        if response == Gtk.ResponseType.OK:
+            self.entry1.set_text(local_entry1.get_text())
+            self.entry2.set_text(local_entry2.get_text())
+
+        dialog.destroy()
+
+    def _message_dialog_clicked(self, button):
+        dialog = Gtk.MessageDialog(transient_for=self.window,
+                                   modal=True,
+                                   destroy_with_parent=True,
+                                   message_type=Gtk.MessageType.INFO,
+                                   buttons=Gtk.ButtonsType.OK,
+                                   text="This message box has been popped up the following\nnumber of times:")
+        dialog.format_secondary_text('%d' % self.dialog_counter)
+        dialog.run()
+
+        self.dialog_counter += 1
+        dialog.destroy()
+
+
+def main(demoapp=None):
+    DialogsApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/drawingarea.py b/examples/demo/demos/drawingarea.py
new file mode 100644 (file)
index 0000000..b04c41d
--- /dev/null
@@ -0,0 +1,207 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Drawing Area"
+description = """
+GtkDrawingArea is a blank area where you can draw custom displays
+of various kinds.
+
+This demo has two drawing areas. The checkerboard area shows
+how you can just draw something; all you have to do is write
+a signal handler for expose_event, as shown here.
+
+The "scribble" area is a bit more advanced, and shows how to handle
+events such as button presses and mouse motion. Click the mouse
+and drag in the scribble area to draw squiggles. Resize the window
+to clear the area.
+"""
+
+
+import cairo
+
+from gi.repository import Gtk, Gdk
+
+
+class DrawingAreaApp:
+    def __init__(self):
+        self.sureface = None
+
+        window = Gtk.Window()
+        window.set_title(title)
+        window.connect('destroy', lambda x: Gtk.main_quit())
+        window.set_border_width(8)
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=8)
+        window.add(vbox)
+
+        # create checkerboard area
+        label = Gtk.Label()
+        label.set_markup('<u>Checkerboard pattern</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+        vbox.pack_start(frame, True, True, 0)
+
+        da = Gtk.DrawingArea()
+        da.set_size_request(100, 100)
+        frame.add(da)
+        da.connect('draw', self.checkerboard_draw_event)
+
+        # create scribble area
+        label = Gtk.Label()
+        label.set_markup('<u>Scribble area</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+        vbox.pack_start(frame, True, True, 0)
+
+        da = Gtk.DrawingArea()
+        da.set_size_request(100, 100)
+        frame.add(da)
+        da.connect('draw', self.scribble_draw_event)
+        da.connect('configure-event', self.scribble_configure_event)
+
+        # event signals
+        da.connect('motion-notify-event', self.scribble_motion_notify_event)
+        da.connect('button-press-event', self.scribble_button_press_event)
+
+        # Ask to receive events the drawing area doesn't normally
+        # subscribe to
+        da.set_events(da.get_events() |
+                      Gdk.EventMask.LEAVE_NOTIFY_MASK |
+                      Gdk.EventMask.BUTTON_PRESS_MASK |
+                      Gdk.EventMask.POINTER_MOTION_MASK |
+                      Gdk.EventMask.POINTER_MOTION_HINT_MASK)
+
+        window.show_all()
+
+    def checkerboard_draw_event(self, da, cairo_ctx):
+
+        # At the start of a draw handler, a clip region has been set on
+        # the Cairo context, and the contents have been cleared to the
+        # widget's background color. The docs for
+        # gdk_window_begin_paint_region() give more details on how this
+        # works.
+        check_size = 10
+        spacing = 2
+
+        xcount = 0
+        i = spacing
+        width = da.get_allocated_width()
+        height = da.get_allocated_height()
+
+        while i < width:
+            j = spacing
+            ycount = xcount % 2  # start with even/odd depending on row
+            while j < height:
+                if ycount % 2:
+                    cairo_ctx.set_source_rgb(0.45777, 0, 0.45777)
+                else:
+                    cairo_ctx.set_source_rgb(1, 1, 1)
+                # If we're outside the clip this will do nothing.
+                cairo_ctx.rectangle(i, j,
+                                    check_size,
+                                    check_size)
+                cairo_ctx.fill()
+
+                j += check_size + spacing
+                ycount += 1
+
+            i += check_size + spacing
+            xcount += 1
+
+        return True
+
+    def scribble_draw_event(self, da, cairo_ctx):
+
+        cairo_ctx.set_source_surface(self.surface, 0, 0)
+        cairo_ctx.paint()
+
+        return False
+
+    def draw_brush(self, widget, x, y):
+        update_rect = Gdk.Rectangle()
+        update_rect.x = x - 3
+        update_rect.y = y - 3
+        update_rect.width = 6
+        update_rect.height = 6
+
+        # paint to the surface where we store our state
+        cairo_ctx = cairo.Context(self.surface)
+
+        Gdk.cairo_rectangle(cairo_ctx, update_rect)
+        cairo_ctx.fill()
+
+        widget.get_window().invalidate_rect(update_rect, False)
+
+    def scribble_configure_event(self, da, event):
+
+        allocation = da.get_allocation()
+        self.surface = da.get_window().create_similar_surface(cairo.CONTENT_COLOR,
+                                                              allocation.width,
+                                                              allocation.height)
+
+        cairo_ctx = cairo.Context(self.surface)
+        cairo_ctx.set_source_rgb(1, 1, 1)
+        cairo_ctx.paint()
+
+        return True
+
+    def scribble_motion_notify_event(self, da, event):
+        if self.surface is None:  # paranoia check, in case we haven't gotten a configure event
+            return False
+
+        # This call is very important; it requests the next motion event.
+        # If you don't call gdk_window_get_pointer() you'll only get
+        # a single motion event. The reason is that we specified
+        # GDK_POINTER_MOTION_HINT_MASK to gtk_widget_set_events().
+        # If we hadn't specified that, we could just use event->x, event->y
+        # as the pointer location. But we'd also get deluged in events.
+        # By requesting the next event as we handle the current one,
+        # we avoid getting a huge number of events faster than we
+        # can cope.
+
+        (window, x, y, state) = event.window.get_pointer()
+
+        if state & Gdk.ModifierType.BUTTON1_MASK:
+            self.draw_brush(da, x, y)
+
+        return True
+
+    def scribble_button_press_event(self, da, event):
+        if self.surface is None:  # paranoia check, in case we haven't gotten a configure event
+            return False
+
+        if event.button == 1:
+            self.draw_brush(da, event.x, event.y)
+
+        return True
+
+
+def main(demoapp=None):
+    DrawingAreaApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/expander.py b/examples/demo/demos/expander.py
new file mode 100644 (file)
index 0000000..0ec149e
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Expander"
+description = """
+GtkExpander allows to provide additional content that is initially hidden.
+This is also known as "disclosure triangle".
+"""
+
+from gi.repository import Gtk
+
+
+class ExpanderApp:
+    def __init__(self):
+        self.window = Gtk.Dialog(title="GtkExpander")
+        self.window.add_buttons(Gtk.STOCK_CLOSE, Gtk.ResponseType.NONE)
+        self.window.set_resizable(False)
+        self.window.connect('response', lambda window, x: window.destroy())
+        self.window.connect('destroy', Gtk.main_quit)
+
+        content_area = self.window.get_content_area()
+        vbox = Gtk.VBox(spacing=5)
+        content_area.pack_start(vbox, True, True, 0)
+        vbox.set_border_width(5)
+
+        label = Gtk.Label(label='Expander demo. Click on the triangle for details.')
+        vbox.pack_start(label, True, True, 0)
+
+        expander = Gtk.Expander(label='Details')
+        vbox.pack_start(expander, False, False, 0)
+
+        label = Gtk.Label(label='Details can be shown or hidden')
+        expander.add(label)
+
+        self.window.show_all()
+
+
+def main(demoapp=None):
+    ExpanderApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/flowbox.py b/examples/demo/demos/flowbox.py
new file mode 100755 (executable)
index 0000000..0485b7c
--- /dev/null
@@ -0,0 +1,752 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2014 Gian Mario Tagliaretti <gianmt@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "FlowBox"
+description = """
+A FlowBox allows flexible and responsive grids which reflow as needed and
+support sorting and filtering. The children of a GtkFlowBox are regular widgets.
+"""
+
+from gi.repository import Gtk, Gdk
+
+
+class FlowBoxApp:
+    def __init__(self):
+        window = Gtk.Window()
+        window.connect('destroy', lambda x: Gtk.main_quit())
+        window.set_border_width(10)
+        window.set_default_size(600, 400)
+
+        header = Gtk.HeaderBar(title="Flow Box")
+        header.set_subtitle("Sample FlowBox app")
+        header.props.show_close_button = True
+
+        window.set_titlebar(header)
+
+        scrolled = Gtk.ScrolledWindow()
+        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
+
+        flowbox = Gtk.FlowBox()
+        flowbox.set_valign(Gtk.Align.START)
+        flowbox.set_max_children_per_line(30)
+        flowbox.set_selection_mode(Gtk.SelectionMode.NONE)
+
+        self.create_flowbox(flowbox)
+
+        scrolled.add(flowbox)
+
+        window.add(scrolled)
+        window.show_all()
+
+    def color_swatch_new(self, str_color):
+        rgba = Gdk.RGBA()
+        rgba.parse(str_color)
+
+        text_rgba = Gdk.RGBA()  # default is white
+        if max(rgba.red, rgba.green, rgba.blue) > 0.6:
+            text_rgba.parse('black')
+
+        label = Gtk.Label(label=str_color)
+        label.override_background_color(0, rgba)
+        label.override_color(0, text_rgba)
+        return label
+
+    def create_flowbox(self, flowbox):
+        colors = [
+            'AliceBlue',
+            'AntiqueWhite',
+            'AntiqueWhite1',
+            'AntiqueWhite2',
+            'AntiqueWhite3',
+            'AntiqueWhite4',
+            'aqua',
+            'aquamarine',
+            'aquamarine1',
+            'aquamarine2',
+            'aquamarine3',
+            'aquamarine4',
+            'azure',
+            'azure1',
+            'azure2',
+            'azure3',
+            'azure4',
+            'beige',
+            'bisque',
+            'bisque1',
+            'bisque2',
+            'bisque3',
+            'bisque4',
+            'black',
+            'BlanchedAlmond',
+            'blue',
+            'blue1',
+            'blue2',
+            'blue3',
+            'blue4',
+            'BlueViolet',
+            'brown',
+            'brown1',
+            'brown2',
+            'brown3',
+            'brown4',
+            'burlywood',
+            'burlywood1',
+            'burlywood2',
+            'burlywood3',
+            'burlywood4',
+            'CadetBlue',
+            'CadetBlue1',
+            'CadetBlue2',
+            'CadetBlue3',
+            'CadetBlue4',
+            'chartreuse',
+            'chartreuse1',
+            'chartreuse2',
+            'chartreuse3',
+            'chartreuse4',
+            'chocolate',
+            'chocolate1',
+            'chocolate2',
+            'chocolate3',
+            'chocolate4',
+            'coral',
+            'coral1',
+            'coral2',
+            'coral3',
+            'coral4',
+            'CornflowerBlue',
+            'cornsilk',
+            'cornsilk1',
+            'cornsilk2',
+            'cornsilk3',
+            'cornsilk4',
+            'crimson',
+            'cyan',
+            'cyan1',
+            'cyan2',
+            'cyan3',
+            'cyan4',
+            'DarkBlue',
+            'DarkCyan',
+            'DarkGoldenrod',
+            'DarkGoldenrod1',
+            'DarkGoldenrod2',
+            'DarkGoldenrod3',
+            'DarkGoldenrod4',
+            'DarkGray',
+            'DarkGreen',
+            'DarkGrey',
+            'DarkKhaki',
+            'DarkMagenta',
+            'DarkOliveGreen',
+            'DarkOliveGreen1',
+            'DarkOliveGreen2',
+            'DarkOliveGreen3',
+            'DarkOliveGreen4',
+            'DarkOrange',
+            'DarkOrange1',
+            'DarkOrange2',
+            'DarkOrange3',
+            'DarkOrange4',
+            'DarkOrchid',
+            'DarkOrchid1',
+            'DarkOrchid2',
+            'DarkOrchid3',
+            'DarkOrchid4',
+            'DarkRed',
+            'DarkSalmon',
+            'DarkSeaGreen',
+            'DarkSeaGreen1',
+            'DarkSeaGreen2',
+            'DarkSeaGreen3',
+            'DarkSeaGreen4',
+            'DarkSlateBlue',
+            'DarkSlateGray',
+            'DarkSlateGray1',
+            'DarkSlateGray2',
+            'DarkSlateGray3',
+            'DarkSlateGray4',
+            'DarkSlateGrey',
+            'DarkTurquoise',
+            'DarkViolet',
+            'DeepPink',
+            'DeepPink1',
+            'DeepPink2',
+            'DeepPink3',
+            'DeepPink4',
+            'DeepSkyBlue',
+            'DeepSkyBlue1',
+            'DeepSkyBlue2',
+            'DeepSkyBlue3',
+            'DeepSkyBlue4',
+            'DimGray',
+            'DimGrey',
+            'DodgerBlue',
+            'DodgerBlue1',
+            'DodgerBlue2',
+            'DodgerBlue3',
+            'DodgerBlue4',
+            'firebrick',
+            'firebrick1',
+            'firebrick2',
+            'firebrick3',
+            'firebrick4',
+            'FloralWhite',
+            'ForestGreen',
+            'fuchsia',
+            'gainsboro',
+            'GhostWhite',
+            'gold',
+            'gold1',
+            'gold2',
+            'gold3',
+            'gold4',
+            'goldenrod',
+            'goldenrod1',
+            'goldenrod2',
+            'goldenrod3',
+            'goldenrod4',
+            'gray',
+            'gray0',
+            'gray1',
+            'gray2',
+            'gray3',
+            'gray4',
+            'gray5',
+            'gray6',
+            'gray7',
+            'gray8',
+            'gray9',
+            'gray10',
+            'gray11',
+            'gray12',
+            'gray13',
+            'gray14',
+            'gray15',
+            'gray16',
+            'gray17',
+            'gray18',
+            'gray19',
+            'gray20',
+            'gray21',
+            'gray22',
+            'gray23',
+            'gray24',
+            'gray25',
+            'gray26',
+            'gray27',
+            'gray28',
+            'gray29',
+            'gray30',
+            'gray31',
+            'gray32',
+            'gray33',
+            'gray34',
+            'gray35',
+            'gray36',
+            'gray37',
+            'gray38',
+            'gray39',
+            'gray40',
+            'gray41',
+            'gray42',
+            'gray43',
+            'gray44',
+            'gray45',
+            'gray46',
+            'gray47',
+            'gray48',
+            'gray49',
+            'gray50',
+            'gray51',
+            'gray52',
+            'gray53',
+            'gray54',
+            'gray55',
+            'gray56',
+            'gray57',
+            'gray58',
+            'gray59',
+            'gray60',
+            'gray61',
+            'gray62',
+            'gray63',
+            'gray64',
+            'gray65',
+            'gray66',
+            'gray67',
+            'gray68',
+            'gray69',
+            'gray70',
+            'gray71',
+            'gray72',
+            'gray73',
+            'gray74',
+            'gray75',
+            'gray76',
+            'gray77',
+            'gray78',
+            'gray79',
+            'gray80',
+            'gray81',
+            'gray82',
+            'gray83',
+            'gray84',
+            'gray85',
+            'gray86',
+            'gray87',
+            'gray88',
+            'gray89',
+            'gray90',
+            'gray91',
+            'gray92',
+            'gray93',
+            'gray94',
+            'gray95',
+            'gray96',
+            'gray97',
+            'gray98',
+            'gray99',
+            'gray100',
+            'green',
+            'green1',
+            'green2',
+            'green3',
+            'green4',
+            'GreenYellow',
+            'grey',
+            'grey0',
+            'grey1',
+            'grey2',
+            'grey3',
+            'grey4',
+            'grey5',
+            'grey6',
+            'grey7',
+            'grey8',
+            'grey9',
+            'grey10',
+            'grey11',
+            'grey12',
+            'grey13',
+            'grey14',
+            'grey15',
+            'grey16',
+            'grey17',
+            'grey18',
+            'grey19',
+            'grey20',
+            'grey21',
+            'grey22',
+            'grey23',
+            'grey24',
+            'grey25',
+            'grey26',
+            'grey27',
+            'grey28',
+            'grey29',
+            'grey30',
+            'grey31',
+            'grey32',
+            'grey33',
+            'grey34',
+            'grey35',
+            'grey36',
+            'grey37',
+            'grey38',
+            'grey39',
+            'grey40',
+            'grey41',
+            'grey42',
+            'grey43',
+            'grey44',
+            'grey45',
+            'grey46',
+            'grey47',
+            'grey48',
+            'grey49',
+            'grey50',
+            'grey51',
+            'grey52',
+            'grey53',
+            'grey54',
+            'grey55',
+            'grey56',
+            'grey57',
+            'grey58',
+            'grey59',
+            'grey60',
+            'grey61',
+            'grey62',
+            'grey63',
+            'grey64',
+            'grey65',
+            'grey66',
+            'grey67',
+            'grey68',
+            'grey69',
+            'grey70',
+            'grey71',
+            'grey72',
+            'grey73',
+            'grey74',
+            'grey75',
+            'grey76',
+            'grey77',
+            'grey78',
+            'grey79',
+            'grey80',
+            'grey81',
+            'grey82',
+            'grey83',
+            'grey84',
+            'grey85',
+            'grey86',
+            'grey87',
+            'grey88',
+            'grey89',
+            'grey90',
+            'grey91',
+            'grey92',
+            'grey93',
+            'grey94',
+            'grey95',
+            'grey96',
+            'grey97',
+            'grey98',
+            'grey99',
+            'grey100',
+            'honeydew',
+            'honeydew1',
+            'honeydew2',
+            'honeydew3',
+            'honeydew4',
+            'HotPink',
+            'HotPink1',
+            'HotPink2',
+            'HotPink3',
+            'HotPink4',
+            'IndianRed',
+            'IndianRed1',
+            'IndianRed2',
+            'IndianRed3',
+            'IndianRed4',
+            'indigo',
+            'ivory',
+            'ivory1',
+            'ivory2',
+            'ivory3',
+            'ivory4',
+            'khaki',
+            'khaki1',
+            'khaki2',
+            'khaki3',
+            'khaki4',
+            'lavender',
+            'LavenderBlush',
+            'LavenderBlush1',
+            'LavenderBlush2',
+            'LavenderBlush3',
+            'LavenderBlush4',
+            'LawnGreen',
+            'LemonChiffon',
+            'LemonChiffon1',
+            'LemonChiffon2',
+            'LemonChiffon3',
+            'LemonChiffon4',
+            'LightBlue',
+            'LightBlue1',
+            'LightBlue2',
+            'LightBlue3',
+            'LightBlue4',
+            'LightCoral',
+            'LightCyan',
+            'LightCyan1',
+            'LightCyan2',
+            'LightCyan3',
+            'LightCyan4',
+            'LightGoldenrod',
+            'LightGoldenrod1',
+            'LightGoldenrod2',
+            'LightGoldenrod3',
+            'LightGoldenrod4',
+            'LightGoldenrodYellow',
+            'LightGray',
+            'LightGreen',
+            'LightGrey',
+            'LightPink',
+            'LightPink1',
+            'LightPink2',
+            'LightPink3',
+            'LightPink4',
+            'LightSalmon',
+            'LightSalmon1',
+            'LightSalmon2',
+            'LightSalmon3',
+            'LightSalmon4',
+            'LightSeaGreen',
+            'LightSkyBlue',
+            'LightSkyBlue1',
+            'LightSkyBlue2',
+            'LightSkyBlue3',
+            'LightSkyBlue4',
+            'LightSlateBlue',
+            'LightSlateGray',
+            'LightSlateGrey',
+            'LightSteelBlue',
+            'LightSteelBlue1',
+            'LightSteelBlue2',
+            'LightSteelBlue3',
+            'LightSteelBlue4',
+            'LightYellow',
+            'LightYellow1',
+            'LightYellow2',
+            'LightYellow3',
+            'LightYellow4',
+            'lime',
+            'LimeGreen',
+            'linen',
+            'magenta',
+            'magenta1',
+            'magenta2',
+            'magenta3',
+            'magenta4',
+            'maroon',
+            'maroon1',
+            'maroon2',
+            'maroon3',
+            'maroon4',
+            'MediumAquamarine',
+            'MediumBlue',
+            'MediumOrchid',
+            'MediumOrchid1',
+            'MediumOrchid2',
+            'MediumOrchid3',
+            'MediumOrchid4',
+            'MediumPurple',
+            'MediumPurple1',
+            'MediumPurple2',
+            'MediumPurple3',
+            'MediumPurple4',
+            'MediumSeaGreen',
+            'MediumSlateBlue',
+            'MediumSpringGreen',
+            'MediumTurquoise',
+            'MediumVioletRed',
+            'MidnightBlue',
+            'MintCream',
+            'MistyRose',
+            'MistyRose1',
+            'MistyRose2',
+            'MistyRose3',
+            'MistyRose4',
+            'moccasin',
+            'NavajoWhite',
+            'NavajoWhite1',
+            'NavajoWhite2',
+            'NavajoWhite3',
+            'NavajoWhite4',
+            'navy',
+            'NavyBlue',
+            'OldLace',
+            'olive',
+            'OliveDrab',
+            'OliveDrab1',
+            'OliveDrab2',
+            'OliveDrab3',
+            'OliveDrab4',
+            'orange',
+            'orange1',
+            'orange2',
+            'orange3',
+            'orange4',
+            'OrangeRed',
+            'OrangeRed1',
+            'OrangeRed2',
+            'OrangeRed3',
+            'OrangeRed4',
+            'orchid',
+            'orchid1',
+            'orchid2',
+            'orchid3',
+            'orchid4',
+            'PaleGoldenrod',
+            'PaleGreen',
+            'PaleGreen1',
+            'PaleGreen2',
+            'PaleGreen3',
+            'PaleGreen4',
+            'PaleTurquoise',
+            'PaleTurquoise1',
+            'PaleTurquoise2',
+            'PaleTurquoise3',
+            'PaleTurquoise4',
+            'PaleVioletRed',
+            'PaleVioletRed1',
+            'PaleVioletRed2',
+            'PaleVioletRed3',
+            'PaleVioletRed4',
+            'PapayaWhip',
+            'PeachPuff',
+            'PeachPuff1',
+            'PeachPuff2',
+            'PeachPuff3',
+            'PeachPuff4',
+            'peru',
+            'pink',
+            'pink1',
+            'pink2',
+            'pink3',
+            'pink4',
+            'plum',
+            'plum1',
+            'plum2',
+            'plum3',
+            'plum4',
+            'PowderBlue',
+            'purple',
+            'purple1',
+            'purple2',
+            'purple3',
+            'purple4',
+            'red',
+            'red1',
+            'red2',
+            'red3',
+            'red4',
+            'RosyBrown',
+            'RosyBrown1',
+            'RosyBrown2',
+            'RosyBrown3',
+            'RosyBrown4',
+            'RoyalBlue',
+            'RoyalBlue1',
+            'RoyalBlue2',
+            'RoyalBlue3',
+            'RoyalBlue4',
+            'SaddleBrown',
+            'salmon',
+            'salmon1',
+            'salmon2',
+            'salmon3',
+            'salmon4',
+            'SandyBrown',
+            'SeaGreen',
+            'SeaGreen1',
+            'SeaGreen2',
+            'SeaGreen3',
+            'SeaGreen4',
+            'seashell',
+            'seashell1',
+            'seashell2',
+            'seashell3',
+            'seashell4',
+            'sienna',
+            'sienna1',
+            'sienna2',
+            'sienna3',
+            'sienna4',
+            'silver',
+            'SkyBlue',
+            'SkyBlue1',
+            'SkyBlue2',
+            'SkyBlue3',
+            'SkyBlue4',
+            'SlateBlue',
+            'SlateBlue1',
+            'SlateBlue2',
+            'SlateBlue3',
+            'SlateBlue4',
+            'SlateGray',
+            'SlateGray1',
+            'SlateGray2',
+            'SlateGray3',
+            'SlateGray4',
+            'SlateGrey',
+            'snow',
+            'snow1',
+            'snow2',
+            'snow3',
+            'snow4',
+            'SpringGreen',
+            'SpringGreen1',
+            'SpringGreen2',
+            'SpringGreen3',
+            'SpringGreen4',
+            'SteelBlue',
+            'SteelBlue1',
+            'SteelBlue2',
+            'SteelBlue3',
+            'SteelBlue4',
+            'tan',
+            'tan1',
+            'tan2',
+            'tan3',
+            'tan4',
+            'teal',
+            'thistle',
+            'thistle1',
+            'thistle2',
+            'thistle3',
+            'thistle4',
+            'tomato',
+            'tomato1',
+            'tomato2',
+            'tomato3',
+            'tomato4',
+            'turquoise',
+            'turquoise1',
+            'turquoise2',
+            'turquoise3',
+            'turquoise4',
+            'violet',
+            'VioletRed',
+            'VioletRed1',
+            'VioletRed2',
+            'VioletRed3',
+            'VioletRed4',
+            'wheat',
+            'wheat1',
+            'wheat2',
+            'wheat3',
+            'wheat4',
+            'white',
+            'WhiteSmoke',
+            'yellow',
+            'yellow1',
+            'yellow2',
+            'yellow3',
+            'yellow4',
+            'YellowGreen',
+        ]
+
+        for color in colors:
+            button = self.color_swatch_new(color)
+            flowbox.add(button)
+
+
+def main(demoapp=None):
+    FlowBoxApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/images.py b/examples/demo/demos/images.py
new file mode 100644 (file)
index 0000000..80c99af
--- /dev/null
@@ -0,0 +1,305 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Images"
+description = """GtkImage is used to display an image; the image can be in a
+number of formats. Typically, you load an image into a GdkPixbuf, then display
+the pixbuf. This demo code shows some of the more obscure cases, in the simple
+case a call to gtk_image_new_from_file() is all you need.
+"""
+
+import os
+from os import path
+
+from gi.repository import Gtk, Gdk, GdkPixbuf, GLib, Gio, GObject
+
+
+class ImagesApp:
+    def __init__(self):
+        self.pixbuf_loader = None
+        self.image_stream = None
+
+        self.window = Gtk.Window(title="Images")
+        self.window.connect('destroy', self.cleanup_cb)
+        self.window.set_border_width(8)
+
+        vbox = Gtk.VBox(spacing=8)
+        vbox.set_border_width(8)
+        self.window.add(vbox)
+
+        label = Gtk.Label()
+        label.set_markup('<u>Image loaded from file</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+
+        # The alignment keeps the frame from growing when users resize
+        # the window
+        align = Gtk.Alignment(xalign=0.5,
+                              yalign=0.5,
+                              xscale=0,
+                              yscale=0)
+        align.add(frame)
+        vbox.pack_start(align, False, False, 0)
+
+        self.base_path = os.path.abspath(os.path.dirname(__file__))
+        self.base_path = os.path.join(self.base_path, 'data')
+        filename = os.path.join(self.base_path, 'gtk-logo-rgb.gif')
+        pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
+        transparent = pixbuf.add_alpha(True, 0xff, 0xff, 0xff)
+        image = Gtk.Image.new_from_pixbuf(transparent)
+        frame.add(image)
+
+        # Animation
+
+        label = Gtk.Label()
+        label.set_markup('<u>Animation loaded from file</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+
+        # The alignment keeps the frame from growing when users resize
+        # the window
+        align = Gtk.Alignment(xalign=0.5,
+                              yalign=0.5,
+                              xscale=0,
+                              yscale=0)
+        align.add(frame)
+        vbox.pack_start(align, False, False, 0)
+
+        img_path = path.join(self.base_path, 'floppybuddy.gif')
+        image = Gtk.Image.new_from_file(img_path)
+        frame.add(image)
+
+        # Symbolic icon
+
+        label = Gtk.Label()
+        label.set_markup('<u>Symbolic themed icon</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+
+        # The alignment keeps the frame from growing when users resize
+        # the window
+        align = Gtk.Alignment(xalign=0.5,
+                              yalign=0.5,
+                              xscale=0,
+                              yscale=0)
+        align.add(frame)
+        vbox.pack_start(align, False, False, 0)
+
+        gicon = Gio.ThemedIcon.new_with_default_fallbacks('battery-caution-charging-symbolic')
+        image = Gtk.Image.new_from_gicon(gicon, Gtk.IconSize.DIALOG)
+        frame.add(image)
+
+        # progressive
+
+        label = Gtk.Label()
+        label.set_markup('<u>Progressive image loading</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+
+        # The alignment keeps the frame from growing when users resize
+        # the window
+        align = Gtk.Alignment(xalign=0.5,
+                              yalign=0.5,
+                              xscale=0,
+                              yscale=0)
+        align.add(frame)
+        vbox.pack_start(align, False, False, 0)
+
+        image = Gtk.Image.new_from_pixbuf(None)
+        frame.add(image)
+
+        self.start_progressive_loading(image)
+
+        # Sensistivity control
+        button = Gtk.ToggleButton.new_with_mnemonic('_Insensitive')
+        button.connect('toggled', self.toggle_sensitivity_cb, vbox)
+        vbox.pack_start(button, False, False, 0)
+
+        self.window.show_all()
+
+    def toggle_sensitivity_cb(self, button, container):
+        widget_list = container.get_children()
+        for w in widget_list:
+            if w != button:
+                w.set_sensitive(not button.get_active())
+
+        return True
+
+    def progressive_timeout(self, image):
+        # This shows off fully-paranoid error handling, so looks scary.
+        # You could factor out the error handling code into a nice separate
+        # function to make things nicer.
+
+        if self.image_stream:
+            try:
+                buf = self.image_stream.read(256)
+            except IOError as e:
+                dialog = Gtk.MessageDialog(self.window,
+                                           Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                           Gtk.MessageType.ERROR,
+                                           Gtk.ButtonsType.CLOSE,
+                                           "Failure reading image file 'alphatest.png': %s" % (str(e), ))
+
+                self.image_stream.close()
+                self.image_stream = None
+                self.load_timeout = 0
+
+                dialog.show()
+                dialog.connect('response', lambda x, y: dialog.destroy())
+
+                return False  # uninstall the timeout
+
+            try:
+                self.pixbuf_loader.write(buf)
+
+            except GObject.GError as e:
+                dialog = Gtk.MessageDialog(self.window,
+                                           Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                           Gtk.MessageType.ERROR,
+                                           Gtk.ButtonsType.CLOSE,
+                                           e.message)
+
+                self.image_stream.close()
+                self.image_stream = None
+                self.load_timeout = 0
+
+                dialog.show()
+                dialog.connect('response', lambda x, y: dialog.destroy())
+
+                return False  # uninstall the timeout
+
+            if len(buf) < 256:  # eof
+                self.image_stream.close()
+                self.image_stream = None
+
+                # Errors can happen on close, e.g. if the image
+                # file was truncated we'll know on close that
+                # it was incomplete.
+                try:
+                    self.pixbuf_loader.close()
+                except GObject.GError as e:
+                    dialog = Gtk.MessageDialog(self.window,
+                                               Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                               Gtk.MessageType.ERROR,
+                                               Gtk.ButtonsType.CLOSE,
+                                               'Failed to load image: %s' % e.message)
+
+                    self.load_timeout = 0
+
+                    dialog.show()
+                    dialog.connect('response', lambda x, y: dialog.destroy())
+
+                    return False  # uninstall the timeout
+        else:
+            img_path = path.join(self.base_path, 'alphatest.png')
+            try:
+                self.image_stream = open(img_path, 'rb')
+            except IOError as e:
+                dialog = Gtk.MessageDialog(self.window,
+                                           Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                           Gtk.MessageType.ERROR,
+                                           Gtk.ButtonsType.CLOSE,
+                                           str(e))
+                self.load_timeout = 0
+                dialog.show()
+                dialog.connect('response', lambda x, y: dialog.destroy())
+
+                return False  # uninstall the timeout
+
+            if self.pixbuf_loader:
+                try:
+                    self.pixbuf_loader.close()
+                except GObject.GError:
+                    pass
+                self.pixbuf_loader = None
+
+            self.pixbuf_loader = GdkPixbuf.PixbufLoader()
+
+            self.pixbuf_loader.connect('area-prepared',
+                                       self.progressive_prepared_callback,
+                                       image)
+
+            self.pixbuf_loader.connect('area-updated',
+                                       self.progressive_updated_callback,
+                                       image)
+        # leave timeout installed
+        return True
+
+    def progressive_prepared_callback(self, loader, image):
+        pixbuf = loader.get_pixbuf()
+        # Avoid displaying random memory contents, since the pixbuf
+        # isn't filled in yet.
+        pixbuf.fill(0xaaaaaaff)
+        image.set_from_pixbuf(pixbuf)
+
+    def progressive_updated_callback(self, loader, x, y, width, height, image):
+        # We know the pixbuf inside the GtkImage has changed, but the image
+        # itself doesn't know this; so queue a redraw.  If we wanted to be
+        # really efficient, we could use a drawing area or something
+        # instead of a GtkImage, so we could control the exact position of
+        # the pixbuf on the display, then we could queue a draw for only
+        # the updated area of the image.
+        image.queue_draw()
+
+    def start_progressive_loading(self, image):
+        # This is obviously totally contrived (we slow down loading
+        # on purpose to show how incremental loading works).
+        # The real purpose of incremental loading is the case where
+        # you are reading data from a slow source such as the network.
+        # The timeout simply simulates a slow data source by inserting
+        # pauses in the reading process.
+
+        self.load_timeout = Gdk.threads_add_timeout(150,
+                                                    GLib.PRIORITY_DEFAULT_IDLE,
+                                                    self.progressive_timeout,
+                                                    image)
+
+    def cleanup_cb(self, widget):
+        if self.load_timeout:
+            GLib.source_remove(self.load_timeout)
+
+        if self.pixbuf_loader:
+            try:
+                self.pixbuf_loader.close()
+            except GObject.GError:
+                pass
+
+        if self.image_stream:
+            self.image_stream.close()
+
+        Gtk.main_quit()
+
+
+def main(demoapp=None):
+    ImagesApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/infobars.py b/examples/demo/demos/infobars.py
new file mode 100644 (file)
index 0000000..b8a1dc7
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Info Bars"
+description = """
+Info bar widgets are used to report important messages to the user.
+"""
+
+from gi.repository import Gtk
+
+
+class InfobarApp:
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Info Bars')
+        self.window.set_border_width(8)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        vbox = Gtk.VBox(spacing=0)
+        self.window.add(vbox)
+
+        bar = Gtk.InfoBar()
+        vbox.pack_start(bar, False, False, 0)
+        bar.set_message_type(Gtk.MessageType.INFO)
+        label = Gtk.Label(label='This is an info bar with message type Gtk.MessageType.INFO')
+        bar.get_content_area().pack_start(label, False, False, 0)
+
+        bar = Gtk.InfoBar()
+        vbox.pack_start(bar, False, False, 0)
+        bar.set_message_type(Gtk.MessageType.WARNING)
+        label = Gtk.Label(label='This is an info bar with message type Gtk.MessageType.WARNING')
+        bar.get_content_area().pack_start(label, False, False, 0)
+
+        bar = Gtk.InfoBar()
+        bar.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
+        bar.connect('response', self.on_bar_response)
+        vbox.pack_start(bar, False, False, 0)
+        bar.set_message_type(Gtk.MessageType.QUESTION)
+        label = Gtk.Label(label='This is an info bar with message type Gtk.MessageType.QUESTION')
+        bar.get_content_area().pack_start(label, False, False, 0)
+
+        bar = Gtk.InfoBar()
+        vbox.pack_start(bar, False, False, 0)
+        bar.set_message_type(Gtk.MessageType.ERROR)
+        label = Gtk.Label(label='This is an info bar with message type Gtk.MessageType.ERROR')
+        bar.get_content_area().pack_start(label, False, False, 0)
+
+        bar = Gtk.InfoBar()
+        vbox.pack_start(bar, False, False, 0)
+        bar.set_message_type(Gtk.MessageType.OTHER)
+        label = Gtk.Label(label='This is an info bar with message type Gtk.MessageType.OTHER')
+        bar.get_content_area().pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame(label="Info bars")
+        vbox.pack_start(frame, False, False, 8)
+
+        vbox2 = Gtk.VBox(spacing=8)
+        vbox2.set_border_width(8)
+        frame.add(vbox2)
+
+        # Standard message dialog
+        label = Gtk.Label(label='An example of different info bars')
+        vbox2.pack_start(label, False, False, 0)
+
+        self.window.show_all()
+
+    def on_bar_response(self, info_bar, response_id):
+        dialog = Gtk.MessageDialog(transient_for=self.window,
+                                   modal=True,
+                                   destroy_with_parent=True,
+                                   message_type=Gtk.MessageType.INFO,
+                                   buttons=Gtk.ButtonsType.OK,
+                                   text='You clicked on an info bar')
+        dialog.format_secondary_text('Your response has id %d' % response_id)
+        dialog.run()
+        dialog.destroy()
+
+
+def main(demoapp=None):
+    InfobarApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/links.py b/examples/demo/demos/links.py
new file mode 100644 (file)
index 0000000..c6d331d
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Links"
+description = """
+GtkLabel can show hyperlinks. The default action is to call gtk_show_uri() on
+their URI, but it is possible to override this with a custom handler.
+"""
+
+from gi.repository import Gtk
+
+
+class LinksApp:
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Links')
+        self.window.set_border_width(12)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        label = Gtk.Label(label="""Some <a href="http://en.wikipedia.org/wiki/Text"
+title="plain text">text</a> may be marked up
+as hyperlinks, which can be clicked
+or activated via <a href="keynav">keynav</a>""")
+
+        label.set_use_markup(True)
+        label.connect("activate-link", self.activate_link)
+        self.window.add(label)
+        label.show()
+
+        self.window.show()
+
+    def activate_link(self, label, uri):
+        if uri == 'keynav':
+            parent = label.get_toplevel()
+            markup = """The term <i>keynav</i> is a shorthand for
+keyboard navigation and refers to the process of using
+a program (exclusively) via keyboard input."""
+            dialog = Gtk.MessageDialog(transient_for=parent,
+                                       destroy_with_parent=True,
+                                       message_type=Gtk.MessageType.INFO,
+                                       buttons=Gtk.ButtonsType.OK,
+                                       text=markup,
+                                       use_markup=True)
+            dialog.present()
+            dialog.connect('response', self.response_cb)
+
+            return True
+
+    def response_cb(self, dialog, response_id):
+        dialog.destroy()
+
+
+def main(demoapp=None):
+    LinksApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/menus.py b/examples/demo/demos/menus.py
new file mode 100644 (file)
index 0000000..7cf2e57
--- /dev/null
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Menus"
+description = """There are several widgets involved in displaying menus. The
+GtkMenuBar widget is a menu bar, which normally appears horizontally at the top
+of an application, but can also be layed out vertically. The GtkMenu widget is
+the actual menu that pops up. Both GtkMenuBar and GtkMenu are subclasses of
+GtkMenuShell; a GtkMenuShell contains menu items (GtkMenuItem). Each menu item
+contains text and/or images and can be selected by the user. There are several
+kinds of menu item, including plain GtkMenuItem, GtkCheckMenuItem which can be
+checked/unchecked, GtkRadioMenuItem which is a check menu item that's in a
+mutually exclusive group, GtkSeparatorMenuItem which is a separator bar,
+GtkTearoffMenuItem which allows a GtkMenu to be torn off, and GtkImageMenuItem
+which can place a GtkImage or other widget next to the menu text. A GtkMenuItem
+can have a submenu, which is simply a GtkMenu to pop up when the menu item is
+selected. Typically, all menu items in a menu bar have submenus. GtkUIManager
+provides a higher-level interface for creating menu bars and menus; while you
+can construct menus manually, most people don't do that. There's a separate demo
+for GtkUIManager.
+"""
+
+from gi.repository import Gtk
+
+
+class MenusApp:
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Menus')
+        self.window.connect('destroy', Gtk.main_quit)
+
+        accel_group = Gtk.AccelGroup()
+        self.window.add_accel_group(accel_group)
+        self.window.set_border_width(0)
+
+        box = Gtk.HBox()
+        self.window.add(box)
+
+        box1 = Gtk.VBox()
+        box.add(box1)
+
+        menubar = Gtk.MenuBar()
+        box1.pack_start(menubar, False, True, 0)
+
+        menuitem = Gtk.MenuItem(label='test\nline2')
+        menuitem.set_submenu(self.create_menu(3, True))
+        menubar.append(menuitem)
+
+        menuitem = Gtk.MenuItem(label='foo')
+        menuitem.set_submenu(self.create_menu(4, True))
+        menuitem.set_right_justified(True)
+        menubar.append(menuitem)
+
+        box2 = Gtk.VBox(spacing=10)
+        box2.set_border_width(10)
+        box1.pack_start(box2, False, True, 0)
+
+        button = Gtk.Button(label='Flip')
+        button.connect('clicked', self.change_orientation, menubar)
+        box2.pack_start(button, True, True, 0)
+
+        button = Gtk.Button(label='Close')
+        button.connect('clicked', lambda x: self.window.destroy())
+        box2.pack_start(button, True, True, 0)
+        button.set_can_default(True)
+
+        self.window.show_all()
+
+    def create_menu(self, depth, tearoff):
+        if depth < 1:
+            return None
+
+        menu = Gtk.Menu()
+
+        if tearoff:
+            menuitem = Gtk.TearoffMenuItem()
+            menu.append(menuitem)
+
+        i = 0
+        j = 1
+        while i < 5:
+            label = 'item %2d - %d' % (depth, j)
+            # we should be adding this to a group but the group API
+            # isn't bindable - we need something more like the
+            # Gtk.RadioAction API
+            menuitem = Gtk.RadioMenuItem(label=label)
+            menu.append(menuitem)
+
+            if i == 3:
+                menuitem.set_sensitive(False)
+
+            menuitem.set_submenu(self.create_menu(depth - 1, True))
+
+            i += 1
+            j += 1
+
+        return menu
+
+    def change_orientation(self, button, menubar):
+        parent = menubar.get_parent()
+        orientation = parent.get_orientation()
+        parent.set_orientation(1 - orientation)
+
+        if orientation == Gtk.Orientation.VERTICAL:
+            menubar.props.pack_direction = Gtk.PackDirection.TTB
+        else:
+            menubar.props.pack_direction = Gtk.PackDirection.LTR
+
+
+def main(demoapp=None):
+    MenusApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/pickers.py b/examples/demo/demos/pickers.py
new file mode 100644 (file)
index 0000000..d8a9c75
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Pickers"
+description = """These widgets are mainly intended for use in preference
+dialogs. They allow to select colors, fonts, files and directories.
+"""
+
+from gi.repository import Gtk
+
+
+class PickersApp:
+    def __init__(self):
+        self.window = Gtk.Window(title='Pickers')
+        self.window.connect('destroy', Gtk.main_quit)
+        self.window.set_border_width(10)
+
+        table = Gtk.Table(n_rows=4, n_columns=2, homogeneous=False)
+        table.set_col_spacing(0, 10)
+        table.set_row_spacings(3)
+        self.window.add(table)
+        table.set_border_width(10)
+
+        label = Gtk.Label(label='Color:')
+        label.set_alignment(0.0, 0.5)
+        picker = Gtk.ColorButton()
+        table.attach_defaults(label, 0, 1, 0, 1)
+        table.attach_defaults(picker, 1, 2, 0, 1)
+
+        label = Gtk.Label(label='Font:')
+        label.set_alignment(0.0, 0.5)
+        picker = Gtk.FontButton()
+        table.attach_defaults(label, 0, 1, 1, 2)
+        table.attach_defaults(picker, 1, 2, 1, 2)
+
+        label = Gtk.Label(label='File:')
+        label.set_alignment(0.0, 0.5)
+        picker = Gtk.FileChooserButton.new('Pick a File',
+                                           Gtk.FileChooserAction.OPEN)
+        table.attach_defaults(label, 0, 1, 2, 3)
+        table.attach_defaults(picker, 1, 2, 2, 3)
+
+        label = Gtk.Label(label='Folder:')
+        label.set_alignment(0.0, 0.5)
+        picker = Gtk.FileChooserButton.new('Pick a Folder',
+                                           Gtk.FileChooserAction.SELECT_FOLDER)
+        table.attach_defaults(label, 0, 1, 3, 4)
+        table.attach_defaults(picker, 1, 2, 3, 4)
+
+        self.window.show_all()
+
+
+def main(demoapp=None):
+    PickersApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/pixbuf.py b/examples/demo/demos/pixbuf.py
new file mode 100644 (file)
index 0000000..c213584
--- /dev/null
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Pixbufs"
+description = """A GdkPixbuf represents an image, normally in RGB or RGBA
+format. Pixbufs are normally used to load files from disk and perform image
+scaling. It also shows off how to use GtkDrawingArea to do a simple animation.
+Look at the Image demo for additional pixbuf usage examples.
+"""
+
+from gi.repository import Gtk, Gdk, GdkPixbuf, GLib
+import os
+import math
+
+
+class PixbufApp:
+    FRAME_DELAY = 50
+    BACKGROUND_NAME = "background.jpg"
+    CYCLE_LEN = 60
+
+    def __init__(self):
+        self.background_width = 0
+        self.background_height = 0
+        self.background_pixbuf = None
+        self.frame = None
+        self.frame_num = 0
+        self.pixbufs = []
+        self.image_names = [
+            "apple-red.png",
+            "gnome-applets.png",
+            "gnome-calendar.png",
+            "gnome-foot.png",
+            "gnome-gmush.png",
+            "gnome-gimp.png",
+            "gnome-gsame.png",
+            "gnu-keys.png"
+        ]
+
+        self.window = Gtk.Window(title="Pixbufs")
+        self.window.set_resizable(False)
+        self.window.connect('destroy', self.cleanup_cb)
+
+        try:
+            self.load_pixbufs()
+        except GLib.Error as e:
+            dialog = Gtk.MessageDialog(None,
+                                       0,
+                                       Gtk.MessageType.ERROR,
+                                       Gtk.ButtonsType.CLOSE,
+                                       e.message)
+
+            dialog.run()
+            dialog.destroy()
+            Gtk.main_quit()
+
+        self.window.set_size_request(self.background_width,
+                                     self.background_height)
+        self.frame = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB,
+                                          False,
+                                          8,
+                                          self.background_width,
+                                          self.background_height)
+        self.da = Gtk.DrawingArea()
+        self.da.connect('draw', self.draw_cb)
+        self.window.add(self.da)
+        self.timeout_id = GLib.timeout_add(self.FRAME_DELAY, self.timeout_cb)
+        self.window.show_all()
+
+    def load_pixbufs(self):
+        base_path = os.path.abspath(os.path.dirname(__file__))
+        base_path = os.path.join(base_path, 'data')
+        img_path = os.path.join(base_path, self.BACKGROUND_NAME)
+        self.background_pixbuf = GdkPixbuf.Pixbuf.new_from_file(img_path)
+        self.background_height = self.background_pixbuf.get_height()
+        self.background_width = self.background_pixbuf.get_width()
+
+        for img_name in self.image_names:
+            img_path = os.path.join(base_path, img_name)
+            self.pixbufs.append(GdkPixbuf.Pixbuf.new_from_file(img_path))
+
+    def draw_cb(self, da, cairo_ctx):
+        Gdk.cairo_set_source_pixbuf(cairo_ctx, self.frame, 0, 0)
+        cairo_ctx.paint()
+
+        return True
+
+    def timeout_cb(self):
+        self.background_pixbuf.copy_area(0, 0,
+                                         self.background_width,
+                                         self.background_height,
+                                         self.frame,
+                                         0, 0)
+
+        f = float(self.frame_num % self.CYCLE_LEN) / self.CYCLE_LEN
+
+        xmid = self.background_width / 2.0
+        ymid = self.background_height / 2.0
+        radius = min(xmid, ymid) / 2.0
+
+        r1 = Gdk.Rectangle()
+        r2 = Gdk.Rectangle()
+
+        i = 0
+        for pb in self.pixbufs:
+            i += 1
+            ang = 2.0 * math.pi * i / len(self.pixbufs) - f * 2.0 * math.pi
+
+            iw = pb.get_width()
+            ih = pb.get_height()
+
+            r = radius + (radius / 3.0) * math.sin(f * 2.0 * math.pi)
+
+            xpos = math.floor(xmid + r * math.cos(ang) - iw / 2.0 + 0.5)
+            ypos = math.floor(ymid + r * math.sin(ang) - ih / 2.0 + 0.5)
+
+            if i & 1:
+                k = math.sin(f * 2.0 * math.pi)
+            else:
+                k = math.cos(f * 2.0 * math.pi)
+
+            k = 2.0 * k * k
+            k = max(0.25, k)
+
+            r1.x = xpos
+            r1.y = ypos
+            r1.width = iw * k
+            r1.height = ih * k
+
+            r2.x = 0
+            r2.y = 0
+            r2.width = self.background_width
+            r2.height = self.background_height
+
+            success, dest = Gdk.rectangle_intersect(r1, r2)
+            if success:
+                alpha = 0
+                if i & 1:
+                    alpha = max(127, math.fabs(255 * math.sin(f * 2.0 * math.pi)))
+                else:
+                    alpha = max(127, math.fabs(255 * math.cos(f * 2.0 * math.pi)))
+
+                pb.composite(self.frame,
+                             dest.x, dest.y,
+                             dest.width, dest.height,
+                             xpos, ypos,
+                             k, k,
+                             GdkPixbuf.InterpType.NEAREST,
+                             alpha)
+
+        self.da.queue_draw()
+
+        self.frame_num += 1
+        return True
+
+    def cleanup_cb(self, widget):
+        GLib.source_remove(self.timeout_id)
+        Gtk.main_quit()
+
+
+def main(demoapp=None):
+    PixbufApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/printing.py b/examples/demo/demos/printing.py
new file mode 100644 (file)
index 0000000..6e68037
--- /dev/null
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Printing"
+description = """
+GtkPrintOperation offers a simple API to support printing in a cross-platform way.
+"""
+
+from gi.repository import Gtk, GLib, Pango, PangoCairo
+import math
+import os
+
+
+class PrintingApp:
+    HEADER_HEIGHT = 10 * 72 / 25.4
+    HEADER_GAP = 3 * 72 / 25.4
+
+    def __init__(self):
+        self.operation = Gtk.PrintOperation()
+        print_data = {'filename': os.path.abspath(__file__),
+                      'font_size': 12.0,
+                      'lines_per_page': 0,
+                      'lines': None,
+                      'num_lines': 0,
+                      'num_pages': 0
+                     }
+
+        self.operation.connect('begin-print', self.begin_print, print_data)
+        self.operation.connect('draw-page', self.draw_page, print_data)
+        self.operation.connect('end-print', self.end_print, print_data)
+
+        self.operation.set_use_full_page(False)
+        self.operation.set_unit(Gtk.Unit.POINTS)
+        self.operation.set_embed_page_setup(True)
+
+        settings = Gtk.PrintSettings()
+
+        dir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOCUMENTS)
+        if dir is None:
+            dir = GLib.get_home_dir()
+        if settings.get(Gtk.PRINT_SETTINGS_OUTPUT_FILE_FORMAT) == 'ps':
+            ext = '.ps'
+        elif settings.get(Gtk.PRINT_SETTINGS_OUTPUT_FILE_FORMAT) == 'svg':
+            ext = '.svg'
+        else:
+            ext = '.pdf'
+
+        uri = "file://%s/gtk-demo%s" % (dir, ext)
+        settings.set(Gtk.PRINT_SETTINGS_OUTPUT_URI, uri)
+        self.operation.set_print_settings(settings)
+
+    def run(self, parent=None):
+        result = self.operation.run(Gtk.PrintOperationAction.PRINT_DIALOG, parent)
+
+        if result == Gtk.PrintOperationResult.ERROR:
+            message = self.operation.get_error()
+
+            dialog = Gtk.MessageDialog(parent,
+                                       0,
+                                       Gtk.MessageType.ERROR,
+                                       Gtk.ButtonsType.CLOSE,
+                                       message)
+
+            dialog.run()
+            dialog.destroy()
+
+        Gtk.main_quit()
+
+    def begin_print(self, operation, print_ctx, print_data):
+        height = print_ctx.get_height() - self.HEADER_HEIGHT - self.HEADER_GAP
+        print_data['lines_per_page'] = \
+            math.floor(height / print_data['font_size'])
+
+        file_path = print_data['filename']
+        if not os.path.isfile(file_path):
+            file_path = os.path.join('demos', file_path)
+            if not os.path.isfile:
+                raise Exception("file not found: " % (file_path, ))
+
+        # in reality you should most likely not read the entire
+        # file into a buffer
+        source_file = open(file_path, 'r')
+        s = source_file.read()
+        print_data['lines'] = s.split('\n')
+
+        print_data['num_lines'] = len(print_data['lines'])
+        print_data['num_pages'] = \
+            (print_data['num_lines'] - 1) / print_data['lines_per_page'] + 1
+
+        operation.set_n_pages(print_data['num_pages'])
+
+    def draw_page(self, operation, print_ctx, page_num, print_data):
+        cr = print_ctx.get_cairo_context()
+        width = print_ctx.get_width()
+
+        cr.rectangle(0, 0, width, self.HEADER_HEIGHT)
+        cr.set_source_rgb(0.8, 0.8, 0.8)
+        cr.fill_preserve()
+
+        cr.set_source_rgb(0, 0, 0)
+        cr.set_line_width(1)
+        cr.stroke()
+
+        layout = print_ctx.create_pango_layout()
+        desc = Pango.FontDescription('sans 14')
+        layout.set_font_description(desc)
+
+        layout.set_text(print_data['filename'], -1)
+        (text_width, text_height) = layout.get_pixel_size()
+
+        if text_width > width:
+            layout.set_width(width)
+            layout.set_ellipsize(Pango.EllipsizeMode.START)
+            (text_width, text_height) = layout.get_pixel_size()
+
+        cr.move_to((width - text_width) / 2,
+                   (self.HEADER_HEIGHT - text_height) / 2)
+        PangoCairo.show_layout(cr, layout)
+
+        page_str = "%d/%d" % (page_num + 1, print_data['num_pages'])
+        layout.set_text(page_str, -1)
+
+        layout.set_width(-1)
+        (text_width, text_height) = layout.get_pixel_size()
+        cr.move_to(width - text_width - 4,
+                   (self.HEADER_HEIGHT - text_height) / 2)
+        PangoCairo.show_layout(cr, layout)
+
+        layout = print_ctx.create_pango_layout()
+
+        desc = Pango.FontDescription('monospace')
+        desc.set_size(print_data['font_size'] * Pango.SCALE)
+        layout.set_font_description(desc)
+
+        cr.move_to(0, self.HEADER_HEIGHT + self.HEADER_GAP)
+        lines_pp = int(print_data['lines_per_page'])
+        num_lines = print_data['num_lines']
+        data_lines = print_data['lines']
+        font_size = print_data['font_size']
+        line = page_num * lines_pp
+
+        for i in range(lines_pp):
+            if line >= num_lines:
+                break
+
+            layout.set_text(data_lines[line], -1)
+            PangoCairo.show_layout(cr, layout)
+            cr.rel_move_to(0, font_size)
+            line += 1
+
+    def end_print(self, operation, print_ctx, print_data):
+        pass
+
+
+def main(demoapp=None):
+    app = PrintingApp()
+    GLib.idle_add(app.run, demoapp.window)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/rotatedtext.py b/examples/demo/demos/rotatedtext.py
new file mode 100644 (file)
index 0000000..806c149
--- /dev/null
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+# coding=utf-8
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Rotated Text"
+description = """This demo shows how to use PangoCairo to draw rotated and
+transformed text.  The right pane shows a rotated GtkLabel widget. In both
+cases, a custom PangoCairo shape renderer is installed to draw a red heard using
+cairo drawing operations instead of the Unicode heart character.
+"""
+
+from gi.repository import Gtk, Pango, PangoCairo, Gdk
+import cairo
+import math
+
+
+UTF8_TEXT = u"I â™¥ GTK+"
+HEART = u"♥"
+BYTES_TEXT = UTF8_TEXT.encode("utf-8")
+BYTES_HEART = HEART.encode("utf-8")
+
+
+class RotatedTextApp:
+    RADIUS = 150
+    N_WORDS = 5
+    FONT = "Serif 18"
+
+    def __init__(self):
+
+        white = Gdk.RGBA()
+        white.red = 1.0
+        white.green = 1.0
+        white.blue = 1.0
+        white.alpha = 1.0
+
+        self.window = Gtk.Window(title="Rotated Text")
+        self.window.set_default_size(4 * self.RADIUS, 2 * self.RADIUS)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        box = Gtk.HBox()
+        box.set_homogeneous(True)
+        self.window.add(box)
+
+        # add a drawing area
+        da = Gtk.DrawingArea()
+        box.add(da)
+
+        # override the background color from the theme
+        da.override_background_color(0, white)
+
+        da.connect('draw', self.rotated_text_draw)
+
+        label = Gtk.Label(label=UTF8_TEXT)
+        box.add(label)
+        label.set_angle(45)
+
+        # Setup some fancy stuff on the label
+        layout = label.get_layout()
+
+        PangoCairo.context_set_shape_renderer(layout.get_context(),
+                                              self.fancy_shape_renderer,
+                                              None)
+        attrs = self.create_fancy_attr_list_for_layout(layout)
+        label.set_attributes(attrs)
+
+        self.window.show_all()
+
+    def fancy_shape_renderer(self, cairo_ctx, attr, do_path):
+        x, y = cairo_ctx.get_current_point()
+        cairo_ctx.translate(x, y)
+
+        cairo_ctx.scale(float(attr.inc_rect.width) / Pango.SCALE,
+                        float(attr.inc_rect.height) / Pango.SCALE)
+
+        if int(attr.data) == 0x2665:  # U+2665 BLACK HEART SUIT
+            cairo_ctx.move_to(0.5, 0.0)
+            cairo_ctx.line_to(0.9, -0.4)
+            cairo_ctx.curve_to(1.1, -0.8, 0.5, -0.9, 0.5, -0.5)
+            cairo_ctx.curve_to(0.5, -0.9, -0.1, -0.8, 0.1, -0.4)
+            cairo_ctx.close_path()
+
+        if not do_path:
+            cairo_ctx.set_source_rgb(1.0, 0.0, 0.0)
+            cairo_ctx.fill()
+
+    def create_fancy_attr_list_for_layout(self, layout):
+        pango_ctx = layout.get_context()
+        metrics = pango_ctx.get_metrics(layout.get_font_description(),
+                                        None)
+        ascent = metrics.get_ascent()
+
+        logical_rect = Pango.Rectangle()
+        logical_rect.x = 0
+        logical_rect.width = ascent
+        logical_rect.y = -ascent
+        logical_rect.height = ascent
+
+        # Set fancy shape attributes for all hearts
+        attrs = Pango.AttrList()
+
+        # FIXME: attr_shape_new_with_data isn't introspectable
+        '''
+        ink_rect = logical_rect
+        p = BYTES_TEXT.find(BYTES_HEART)
+        while (p != -1):
+            attr = Pango.attr_shape_new_with_data(ink_rect,
+                                                  logical_rect,
+                                                  ord(HEART),
+                                                  None)
+            attr.start_index = p
+            attr.end_index = p + len(BYTES_HEART)
+            p = UTF8_TEXT.find(HEART, attr.end_index)
+
+        attrs.insert(attr)
+        '''
+        return attrs
+
+    def rotated_text_draw(self, da, cairo_ctx):
+        # Create a cairo context and set up a transformation matrix so that the user
+        # space coordinates for the centered square where we draw are [-RADIUS, RADIUS],
+        # [-RADIUS, RADIUS].
+        # We first center, then change the scale.
+        width = da.get_allocated_width()
+        height = da.get_allocated_height()
+        device_radius = min(width, height) / 2.0
+        cairo_ctx.translate(
+            device_radius + (width - 2 * device_radius) / 2,
+            device_radius + (height - 2 * device_radius) / 2)
+        cairo_ctx.scale(device_radius / self.RADIUS,
+                        device_radius / self.RADIUS)
+
+        # Create a subtle gradient source and use it.
+        pattern = cairo.LinearGradient(-self.RADIUS, -self.RADIUS, self.RADIUS, self.RADIUS)
+        pattern.add_color_stop_rgb(0.0, 0.5, 0.0, 0.0)
+        pattern.add_color_stop_rgb(1.0, 0.0, 0.0, 0.5)
+        cairo_ctx.set_source(pattern)
+
+        # Create a PangoContext and set up our shape renderer
+        context = da.create_pango_context()
+        PangoCairo.context_set_shape_renderer(context,
+                                              self.fancy_shape_renderer,
+                                              None)
+
+        # Create a PangoLayout, set the text, font, and attributes */
+        layout = Pango.Layout(context=context)
+        layout.set_text(UTF8_TEXT, -1)
+        desc = Pango.FontDescription(self.FONT)
+        layout.set_font_description(desc)
+
+        attrs = self.create_fancy_attr_list_for_layout(layout)
+        layout.set_attributes(attrs)
+
+        # Draw the layout N_WORDS times in a circle */
+        for i in range(self.N_WORDS):
+            # Inform Pango to re-layout the text with the new transformation matrix
+            PangoCairo.update_layout(cairo_ctx, layout)
+
+            width, height = layout.get_pixel_size()
+            cairo_ctx.move_to(-width / 2, -self.RADIUS * 0.9)
+            PangoCairo.show_layout(cairo_ctx, layout)
+
+            # Rotate for the next turn
+            cairo_ctx.rotate(math.pi * 2 / self.N_WORDS)
+
+        return False
+
+
+def main(demoapp=None):
+    RotatedTextApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/test.py b/examples/demo/demos/test.py
new file mode 100644 (file)
index 0000000..4bd7684
--- /dev/null
@@ -0,0 +1,16 @@
+title = "Test Demo"
+description = "Dude this is a test"
+
+
+from gi.repository import Gtk
+
+
+def _quit(*args):
+    Gtk.main_quit()
+
+
+def main(demoapp=None):
+    window = Gtk.Window()
+    window.connect_after('destroy', _quit)
+    window.show_all()
+    Gtk.main()
diff --git a/examples/option.py b/examples/option.py
new file mode 100644 (file)
index 0000000..73860f5
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+from gi.repository import GLib
+
+group = GLib.option.OptionGroup(
+    "example", "OptionGroup Example", "Shows all example options",
+    option_list=[GLib.option.make_option("--example",
+                                         action="store_true",
+                                         dest="example",
+                                         help="An example option."),
+                ])
+
+parser = GLib.option.OptionParser(
+    "NAMES ...", description="A simple gobject.option example.",
+    option_list=[GLib.option.make_option("--file", "-f",
+                                         type="filename",
+                                         action="store",
+                                         dest="file",
+                                         help="A filename option"),
+                 # ...
+                ])
+
+parser.add_option_group(group)
+
+parser.parse_args()
+
+print("group: example " + str(group.values.example))
+print("parser: file " + str(parser.values.file))
diff --git a/examples/properties.py b/examples/properties.py
new file mode 100644 (file)
index 0000000..257e80a
--- /dev/null
@@ -0,0 +1,33 @@
+from gi.repository import GObject
+
+
+class MyObject(GObject.GObject):
+
+    foo = GObject.Property(type=str, default='bar')
+    boolprop = GObject.Property(type=bool, default=False)
+
+    def __init__(self):
+        GObject.GObject.__init__(self)
+
+    @GObject.Property
+    def readonly(self):
+        return 'readonly'
+
+
+GObject.type_register(MyObject)
+
+print("MyObject properties: ", list(MyObject.props))
+
+obj = MyObject()
+
+print("obj.foo ==", obj.foo)
+
+obj.foo = 'spam'
+print("obj.foo = spam")
+
+print("obj.foo == ", obj.foo)
+
+print("obj.boolprop == ", obj.boolprop)
+
+print(obj.readonly)
+obj.readonly = 'does-not-work'
diff --git a/examples/signal.py b/examples/signal.py
new file mode 100644 (file)
index 0000000..69c1d62
--- /dev/null
@@ -0,0 +1,42 @@
+from __future__ import print_function
+
+from gi.repository import GObject
+
+
+class C(GObject.GObject):
+    @GObject.Signal(arg_types=(int,))
+    def my_signal(self, arg):
+        """Decorator style signal which uses the method name as signal name and
+        the method as the closure.
+
+        Note that with python3 annotations can be used for argument types as follows:
+            @GObject.Signal
+            def my_signal(self, arg:int):
+                pass
+        """
+        print("C: class closure for `my_signal' called with argument", arg)
+
+    @GObject.Signal
+    def noarg_signal(self):
+        """Decoration of a signal using all defaults and no arguments."""
+        print("C: class closure for `noarg_signal' called")
+
+
+class D(C):
+    def do_my_signal(self, arg):
+        print("D: class closure for `my_signal' called.  Chaining up to C")
+        C.my_signal(self, arg)
+
+
+def my_signal_handler(obj, arg, *extra):
+    print("handler for `my_signal' called with argument", arg, "and extra args", extra)
+
+
+inst = C()
+inst2 = D()
+
+inst.connect("my_signal", my_signal_handler, 1, 2, 3)
+inst.connect("noarg_signal", my_signal_handler, 1, 2, 3)
+inst.emit("my_signal", 42)
+inst.emit("noarg_signal")
+inst2.emit("my_signal", 42)
diff --git a/gi/__init__.py b/gi/__init__.py
new file mode 100644 (file)
index 0000000..212136e
--- /dev/null
@@ -0,0 +1,180 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from __future__ import absolute_import
+
+# support overrides in different directories than our gi module
+from pkgutil import extend_path
+__path__ = extend_path(__path__, __name__)
+
+import sys
+import os
+import importlib
+import types
+
+_static_binding_error = ('When using gi.repository you must not import static '
+                         'modules like "gobject". Please change all occurrences '
+                         'of "import gobject" to "from gi.repository import GObject". '
+                         'See: https://bugzilla.gnome.org/show_bug.cgi?id=709183')
+
+# we can't have pygobject 2 loaded at the same time we load the internal _gobject
+if 'gobject' in sys.modules:
+    raise ImportError(_static_binding_error)
+
+
+from . import _gi
+from ._gi import _API
+from ._gi import Repository
+from ._gi import PyGIDeprecationWarning
+from ._gi import PyGIWarning
+from ._compat import string_types
+
+_API = _API  # pyflakes
+PyGIDeprecationWarning = PyGIDeprecationWarning
+PyGIWarning = PyGIWarning
+
+_versions = {}
+_overridesdir = os.path.join(os.path.dirname(__file__), 'overrides')
+
+# Needed for compatibility with "pygobject.h"/pygobject_init()
+_gobject = types.ModuleType("gi._gobject")
+sys.modules[_gobject.__name__] = _gobject
+_gobject._PyGObject_API = _gi._PyGObject_API
+_gobject.pygobject_version = _gi.pygobject_version
+
+version_info = _gi.pygobject_version[:]
+__version__ = "{0}.{1}.{2}".format(*version_info)
+
+
+class _DummyStaticModule(types.ModuleType):
+    __path__ = None
+
+    def __getattr__(self, name):
+        raise AttributeError(_static_binding_error)
+
+
+sys.modules['glib'] = _DummyStaticModule('glib', _static_binding_error)
+sys.modules['gobject'] = _DummyStaticModule('gobject', _static_binding_error)
+sys.modules['gio'] = _DummyStaticModule('gio', _static_binding_error)
+sys.modules['gtk'] = _DummyStaticModule('gtk', _static_binding_error)
+sys.modules['gtk.gdk'] = _DummyStaticModule('gtk.gdk', _static_binding_error)
+
+
+def check_version(version):
+    if isinstance(version, str):
+        version_list = tuple(map(int, version.split(".")))
+    else:
+        version_list = version
+
+    if version_list > version_info:
+        raise ValueError((
+            "pygobject's version %s required, and available version "
+            "%s is not recent enough") % (version, __version__)
+        )
+
+
+def require_version(namespace, version):
+    """ Ensures the correct versions are loaded when importing `gi` modules.
+
+    :param namespace: The name of module to require.
+    :type namespace: str
+    :param version: The version of module to require.
+    :type version: str
+    :raises ValueError: If module/version is already loaded, already required, or unavailable.
+
+    :Example:
+
+    .. code-block:: python
+
+        import gi
+        gi.require_version('Gtk', '3.0')
+
+    """
+    repository = Repository.get_default()
+
+    if not isinstance(version, string_types):
+        raise ValueError('Namespace version needs to be a string.')
+
+    if namespace in repository.get_loaded_namespaces():
+        loaded_version = repository.get_version(namespace)
+        if loaded_version != version:
+            raise ValueError('Namespace %s is already loaded with version %s' %
+                             (namespace, loaded_version))
+
+    if namespace in _versions and _versions[namespace] != version:
+        raise ValueError('Namespace %s already requires version %s' %
+                         (namespace, _versions[namespace]))
+
+    available_versions = repository.enumerate_versions(namespace)
+    if not available_versions:
+        raise ValueError('Namespace %s not available' % namespace)
+
+    if version not in available_versions:
+        raise ValueError('Namespace %s not available for version %s' %
+                         (namespace, version))
+
+    _versions[namespace] = version
+
+
+def require_versions(requires):
+    """ Utility function for consolidating multiple `gi.require_version()` calls.
+
+    :param requires: The names and versions of modules to require.
+    :type requires: dict
+
+    :Example:
+
+    .. code-block:: python
+
+        import gi
+        gi.require_versions({'Gtk': '3.0', 'GLib': '2.0', 'Gio': '2.0'})
+    """
+    for module_name, module_version in requires.items():
+        require_version(module_name, module_version)
+
+
+def get_required_version(namespace):
+    return _versions.get(namespace, None)
+
+
+def require_foreign(namespace, symbol=None):
+    """Ensure the given foreign marshaling module is available and loaded.
+
+    :param str namespace:
+        Introspection namespace of the foreign module (e.g. "cairo")
+    :param symbol:
+        Optional symbol typename to ensure a converter exists.
+    :type symbol: str or None
+    :raises: ImportError
+
+    :Example:
+
+    .. code-block:: python
+
+        import gi
+        import cairo
+        gi.require_foreign('cairo')
+
+    """
+    try:
+        _gi.require_foreign(namespace, symbol)
+    except Exception as e:
+        raise ImportError(str(e))
+    importlib.import_module('gi.repository', namespace)
diff --git a/gi/_compat.py b/gi/_compat.py
new file mode 100644 (file)
index 0000000..00f5fbb
--- /dev/null
@@ -0,0 +1,56 @@
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+PY2 = PY3 = False
+if sys.version_info[0] == 2:
+    PY2 = True
+
+    from StringIO import StringIO
+    StringIO
+
+    from UserList import UserList
+    UserList
+
+    long_ = eval("long")
+    integer_types = eval("(int, long)")
+    string_types = eval("(basestring,)")
+    text_type = eval("unicode")
+
+    reload = eval("reload")
+    xrange = eval("xrange")
+    cmp = eval("cmp")
+
+    exec("def reraise(tp, value, tb):\n raise tp, value, tb")
+else:
+    PY3 = True
+
+    from io import StringIO
+    StringIO
+
+    from collections import UserList
+    UserList
+
+    long_ = int
+    integer_types = (int,)
+    string_types = (str,)
+    text_type = str
+
+    from importlib import reload
+    reload
+    xrange = range
+    cmp = lambda a, b: (a > b) - (a < b)
+
+    def reraise(tp, value, tb):
+        raise tp(value).with_traceback(tb)
diff --git a/gi/_constants.py b/gi/_constants.py
new file mode 100644 (file)
index 0000000..6fca96b
--- /dev/null
@@ -0,0 +1,47 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# pygobject - Python bindings for the GObject library
+# Copyright (C) 2006-2007 Johan Dahlin
+#
+#   gi/_constants.py: GObject type constants
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+from . import _gi
+
+TYPE_INVALID = _gi.TYPE_INVALID
+TYPE_NONE = _gi.GType.from_name('void')
+TYPE_INTERFACE = _gi.GType.from_name('GInterface')
+TYPE_CHAR = _gi.GType.from_name('gchar')
+TYPE_UCHAR = _gi.GType.from_name('guchar')
+TYPE_BOOLEAN = _gi.GType.from_name('gboolean')
+TYPE_INT = _gi.GType.from_name('gint')
+TYPE_UINT = _gi.GType.from_name('guint')
+TYPE_LONG = _gi.GType.from_name('glong')
+TYPE_ULONG = _gi.GType.from_name('gulong')
+TYPE_INT64 = _gi.GType.from_name('gint64')
+TYPE_UINT64 = _gi.GType.from_name('guint64')
+TYPE_ENUM = _gi.GType.from_name('GEnum')
+TYPE_FLAGS = _gi.GType.from_name('GFlags')
+TYPE_FLOAT = _gi.GType.from_name('gfloat')
+TYPE_DOUBLE = _gi.GType.from_name('gdouble')
+TYPE_STRING = _gi.GType.from_name('gchararray')
+TYPE_POINTER = _gi.GType.from_name('gpointer')
+TYPE_BOXED = _gi.GType.from_name('GBoxed')
+TYPE_PARAM = _gi.GType.from_name('GParam')
+TYPE_OBJECT = _gi.GType.from_name('GObject')
+TYPE_PYOBJECT = _gi.GType.from_name('PyObject')
+TYPE_GTYPE = _gi.GType.from_name('GType')
+TYPE_STRV = _gi.GType.from_name('GStrv')
+TYPE_VARIANT = _gi.GType.from_name('GVariant')
+TYPE_UNICHAR = TYPE_UINT
diff --git a/gi/_error.py b/gi/_error.py
new file mode 100644 (file)
index 0000000..6440ef7
--- /dev/null
@@ -0,0 +1,55 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+#
+#   _error.py: GError Python implementation
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+
+# NOTE: This file should not have any dependencies on introspection libs
+# like gi.repository.GLib because it would cause a circular dependency.
+# Developers wanting to use the GError class in their applications should
+# use gi.repository.GLib.GError
+
+
+class GError(RuntimeError):
+    def __init__(self, message='unknown error', domain='pygi-error', code=0):
+        super(GError, self).__init__(message)
+        self.message = message
+        self.domain = domain
+        self.code = code
+
+    def __str__(self):
+        return "%s: %s (%d)" % (self.domain, self.message, self.code)
+
+    def __repr__(self):
+        return "%s.%s('%s', '%s', %d)" % (
+            GError.__module__.rsplit(".", 1)[-1], GError.__name__,
+            self.message, self.domain, self.code)
+
+    def copy(self):
+        return GError(self.message, self.domain, self.code)
+
+    def matches(self, domain, code):
+        """Placeholder that will be monkey patched in GLib overrides."""
+        raise NotImplementedError
+
+    @staticmethod
+    def new_literal(domain, message, code):
+        """Placeholder that will be monkey patched in GLib overrides."""
+        raise NotImplementedError
diff --git a/gi/_gtktemplate.py b/gi/_gtktemplate.py
new file mode 100644 (file)
index 0000000..efaca33
--- /dev/null
@@ -0,0 +1,232 @@
+# Copyright 2015 Dustin Spicuzza <dustin@virtualroadside.com>
+#           2018 Nikita Churaev <lamefun.x0r@gmail.com>
+#           2018 Christoph Reiter <reiter.christoph@gmail.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from gi.repository import GLib, GObject, Gio
+
+
+def connect_func(builder, obj, signal_name, handler_name,
+                 connect_object, flags, cls):
+
+    if handler_name not in cls.__gtktemplate_methods__:
+        return
+
+    method_name = cls.__gtktemplate_methods__[handler_name]
+    template_inst = builder.get_object(cls.__gtype_name__)
+    template_inst.__gtktemplate_handlers__.add(handler_name)
+    handler = getattr(template_inst, method_name)
+
+    after = int(flags & GObject.ConnectFlags.AFTER)
+    swapped = int(flags & GObject.ConnectFlags.SWAPPED)
+    if swapped:
+        raise RuntimeError(
+            "%r not supported" % GObject.ConnectFlags.SWAPPED)
+
+    if connect_object is not None:
+        if after:
+            func = obj.connect_object_after
+        else:
+            func = obj.connect_object
+        func(signal_name, handler, connect_object)
+    else:
+        if after:
+            func = obj.connect_after
+        else:
+            func = obj.connect
+        func(signal_name, handler)
+
+
+def register_template(cls):
+    bound_methods = {}
+    bound_widgets = {}
+
+    for attr_name, obj in list(cls.__dict__.items()):
+        if isinstance(obj, CallThing):
+            setattr(cls, attr_name, obj._func)
+            handler_name = obj._name
+            if handler_name is None:
+                handler_name = attr_name
+
+            if handler_name in bound_methods:
+                old_attr_name = bound_methods[handler_name]
+                raise RuntimeError(
+                    "Error while exposing handler %r as %r, "
+                    "already available as %r" % (
+                        handler_name, attr_name, old_attr_name))
+            else:
+                bound_methods[handler_name] = attr_name
+        elif isinstance(obj, Child):
+            widget_name = obj._name
+            if widget_name is None:
+                widget_name = attr_name
+
+            if widget_name in bound_widgets:
+                old_attr_name = bound_widgets[widget_name]
+                raise RuntimeError(
+                    "Error while exposing child %r as %r, "
+                    "already available as %r" % (
+                        widget_name, attr_name, old_attr_name))
+            else:
+                bound_widgets[widget_name] = attr_name
+                cls.bind_template_child_full(widget_name, obj._internal, 0)
+
+    cls.__gtktemplate_methods__ = bound_methods
+    cls.__gtktemplate_widgets__ = bound_widgets
+
+    cls.set_connect_func(connect_func, cls)
+
+    base_init_template = cls.init_template
+    cls.__dontuse_ginstance_init__ = \
+        lambda s: init_template(s, cls, base_init_template)
+    # To make this file work with older PyGObject we expose our init code
+    # as init_template() but make it a noop when we call it ourselves first
+    cls.init_template = cls.__dontuse_ginstance_init__
+
+
+def init_template(self, cls, base_init_template):
+    self.init_template = lambda s: None
+
+    if self.__class__ is not cls:
+        raise TypeError(
+            "Inheritance from classes with @Gtk.Template decorators "
+            "is not allowed at this time")
+
+    self.__gtktemplate_handlers__ = set()
+
+    base_init_template(self)
+
+    for widget_name, attr_name in self.__gtktemplate_widgets__.items():
+        self.__dict__[attr_name] = self.get_template_child(cls, widget_name)
+
+    for handler_name, attr_name in self.__gtktemplate_methods__.items():
+        if handler_name not in self.__gtktemplate_handlers__:
+            raise RuntimeError(
+                "Handler '%s' was declared with @Gtk.Template.Callback "
+                "but was not present in template" % handler_name)
+
+
+class Child(object):
+
+    def __init__(self, name=None, **kwargs):
+        self._name = name
+        self._internal = kwargs.pop("internal", False)
+        if kwargs:
+            raise TypeError("Unhandled arguments: %r" % kwargs)
+
+
+class CallThing(object):
+
+    def __init__(self, name, func):
+        self._name = name
+        self._func = func
+
+
+class Callback(object):
+
+    def __init__(self, name=None):
+        self._name = name
+
+    def __call__(self, func):
+        return CallThing(self._name, func)
+
+
+def validate_resource_path(path):
+    """Raises GLib.Error in case the resource doesn't exist"""
+
+    try:
+        Gio.resources_get_info(path, Gio.ResourceLookupFlags.NONE)
+    except GLib.Error:
+        # resources_get_info() doesn't handle overlays but we keep using it
+        # as a fast path.
+        # https://gitlab.gnome.org/GNOME/pygobject/issues/230
+        Gio.resources_lookup_data(path, Gio.ResourceLookupFlags.NONE)
+
+
+class Template(object):
+
+    def __init__(self, **kwargs):
+        self.string = None
+        self.filename = None
+        self.resource_path = None
+        if "string" in kwargs:
+            self.string = kwargs.pop("string")
+        elif "filename" in kwargs:
+            self.filename = kwargs.pop("filename")
+        elif "resource_path" in kwargs:
+            self.resource_path = kwargs.pop("resource_path")
+        else:
+            raise TypeError(
+                "Requires one of the following arguments: "
+                "string, filename, resource_path")
+
+        if kwargs:
+            raise TypeError("Unhandled keyword arguments %r" % kwargs)
+
+    @classmethod
+    def from_file(cls, filename):
+        return cls(filename=filename)
+
+    @classmethod
+    def from_string(cls, string):
+        return cls(string=string)
+
+    @classmethod
+    def from_resource(cls, resource_path):
+        return cls(resource_path=resource_path)
+
+    Callback = Callback
+
+    Child = Child
+
+    def __call__(self, cls):
+        from gi.repository import Gtk
+
+        if not isinstance(cls, type) or not issubclass(cls, Gtk.Widget):
+            raise TypeError("Can only use @Gtk.Template on Widgets")
+
+        if "__gtype_name__" not in cls.__dict__:
+            raise TypeError(
+                "%r does not have a __gtype_name__. Set it to the name "
+                "of the class in your template" % cls.__name__)
+
+        if hasattr(cls, "__gtktemplate_methods__"):
+            raise TypeError("Cannot nest template classes")
+
+        if self.string is not None:
+            data = self.string
+            if not isinstance(data, bytes):
+                data = data.encode("utf-8")
+            bytes_ = GLib.Bytes.new(data)
+            cls.set_template(bytes_)
+            register_template(cls)
+            return cls
+        elif self.resource_path is not None:
+            validate_resource_path(self.resource_path)
+            cls.set_template_from_resource(self.resource_path)
+            register_template(cls)
+            return cls
+        else:
+            assert self.filename is not None
+            file_ = Gio.File.new_for_path(self.filename)
+            bytes_ = GLib.Bytes.new(file_.load_contents()[1])
+            cls.set_template(bytes_)
+            register_template(cls)
+            return cls
+
+
+__all__ = ["Template"]
diff --git a/gi/_option.py b/gi/_option.py
new file mode 100644 (file)
index 0000000..9babb2a
--- /dev/null
@@ -0,0 +1,367 @@
+# -*- Mode: Python -*-
+# pygobject - Python bindings for the GObject library
+# Copyright (C) 2006  Johannes Hoelzl
+#
+#   glib/option.py: GOption command line parser
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+"""GOption command line parser
+
+Extends optparse to use the GOptionGroup, GOptionEntry and GOptionContext
+objects. So it is possible to use the gtk, gnome_program and gstreamer command
+line groups and contexts.
+
+Use this interface instead of the raw wrappers of GOptionContext and
+GOptionGroup in glib.
+"""
+
+import sys
+import optparse
+from optparse import OptParseError, OptionError, OptionValueError, \
+    BadOptionError, OptionConflictError
+from .module import get_introspection_module
+from ._compat import string_types
+
+from gi import _gi
+from gi._error import GError
+GLib = get_introspection_module('GLib')
+
+OPTION_CONTEXT_ERROR_QUARK = GLib.quark_to_string(GLib.option_error_quark())
+
+__all__ = [
+    "OptParseError",
+    "OptionError",
+    "OptionValueError",
+    "BadOptionError",
+    "OptionConflictError",
+    "Option",
+    "OptionGroup",
+    "OptionParser",
+    "make_option",
+]
+
+
+class Option(optparse.Option):
+    """Represents a command line option
+
+    To use the extended possibilities of the GOption API Option
+    (and make_option) are extended with new types and attributes.
+
+    Types:
+        filename   The supplied arguments are read as filename, GOption
+                   parses this type in with the GLib filename encoding.
+
+    :ivar optional_arg:
+        This does not need a arguement, but it can be supplied.
+    :ivar hidden:
+        The help list does not show this option
+    :ivar in_main:
+        This option apears in the main group, this should only
+        be used for backwards compatibility.
+
+    Use Option.REMAINING as option name to get all positional arguments.
+
+    .. NOTE::
+        Every argument to an option is passed as utf-8 coded string, the only
+        exception are options which use the 'filename' type, its arguments
+        are passed as strings in the GLib filename encoding.
+
+    For further help, see optparse.Option.
+    """
+    TYPES = optparse.Option.TYPES + (
+        'filename',
+    )
+
+    ATTRS = optparse.Option.ATTRS + [
+        'hidden',
+        'in_main',
+        'optional_arg',
+    ]
+
+    REMAINING = '--' + GLib.OPTION_REMAINING
+
+    def __init__(self, *args, **kwargs):
+        optparse.Option.__init__(self, *args, **kwargs)
+        if not self._long_opts:
+            raise ValueError("%s at least one long option name.")
+
+        if len(self._long_opts) < len(self._short_opts):
+            raise ValueError(
+                "%s at least more long option names than short option names.")
+
+        if not self.help:
+            raise ValueError("%s needs a help message.", self._long_opts[0])
+
+    def _set_opt_string(self, opts):
+        if self.REMAINING in opts:
+            self._long_opts.append(self.REMAINING)
+        optparse.Option._set_opt_string(self, opts)
+        if len(self._short_opts) > len(self._long_opts):
+            raise OptionError("goption.Option needs more long option names "
+                              "than short option names")
+
+    def _to_goptionentries(self):
+        flags = 0
+
+        if self.hidden:
+            flags |= GLib.OptionFlags.HIDDEN
+
+        if self.in_main:
+            flags |= GLib.OptionFlags.IN_MAIN
+
+        if self.takes_value():
+            if self.optional_arg:
+                flags |= GLib.OptionFlags.OPTIONAL_ARG
+        else:
+            flags |= GLib.OptionFlags.NO_ARG
+
+        if self.type == 'filename':
+            flags |= GLib.OptionFlags.FILENAME
+
+        for (long_name, short_name) in zip(self._long_opts, self._short_opts):
+            short_bytes = short_name[1]
+            if not isinstance(short_bytes, bytes):
+                short_bytes = short_bytes.encode("utf-8")
+            yield (long_name[2:], short_bytes, flags, self.help, self.metavar)
+
+        for long_name in self._long_opts[len(self._short_opts):]:
+            yield (long_name[2:], b'\0', flags, self.help, self.metavar)
+
+
+class OptionGroup(optparse.OptionGroup):
+    """A group of command line options.
+
+    :param str name:
+        The groups name, used to create the --help-{name} option
+    :param str description:
+        Shown as title of the groups help view
+    :param str help_description:
+        Shown as help to the --help-{name} option
+    :param list option_list:
+        The options used in this group, must be option.Option()
+    :param dict defaults:
+        A dicitionary of default values
+    :param translation_domain:
+           Sets the translation domain for gettext().
+
+    .. NOTE::
+        This OptionGroup does not exactly map the optparse.OptionGroup
+        interface. There is no parser object to supply, but it is possible
+        to set default values and option_lists. Also the default values and
+        values are not shared with the OptionParser.
+
+    To pass a OptionGroup into a function which expects a GOptionGroup (e.g.
+    gnome_program_init() ). OptionGroup.get_option_group() can be used.
+
+    For further help, see optparse.OptionGroup.
+    """
+    def __init__(self, name, description, help_description="",
+                 option_list=None, defaults=None,
+                 translation_domain=None):
+        optparse.OptionContainer.__init__(self, Option, 'error', description)
+        self.name = name
+        self.parser = None
+        self.help_description = help_description
+        if defaults:
+            self.defaults = defaults
+
+        self.values = None
+
+        self.translation_domain = translation_domain
+
+        if option_list:
+            for option in option_list:
+                self.add_option(option)
+
+    def _create_option_list(self):
+        self.option_list = []
+        self._create_option_mappings()
+
+    def _to_goptiongroup(self, parser):
+        def callback(option_name, option_value, group):
+            if option_name.startswith('--'):
+                opt = self._long_opt[option_name]
+            else:
+                opt = self._short_opt[option_name]
+
+            try:
+                opt.process(option_name, option_value, self.values, parser)
+            except OptionValueError:
+                error = sys.exc_info()[1]
+                gerror = GError(str(error))
+                gerror.domain = OPTION_CONTEXT_ERROR_QUARK
+                gerror.code = GLib.OptionError.BAD_VALUE
+                gerror.message = str(error)
+                raise gerror
+
+        group = _gi.OptionGroup(self.name, self.description,
+                                self.help_description, callback)
+        if self.translation_domain:
+            group.set_translation_domain(self.translation_domain)
+
+        entries = []
+        for option in self.option_list:
+            entries.extend(option._to_goptionentries())
+
+        group.add_entries(entries)
+
+        return group
+
+    def get_option_group(self, parser=None):
+        """ Returns the corresponding GOptionGroup object.
+
+        Can be used as parameter for gnome_program_init(), gtk_init().
+        """
+        self.set_values_to_defaults()
+        return self._to_goptiongroup(parser)
+
+    def set_values_to_defaults(self):
+        for option in self.option_list:
+            default = self.defaults.get(option.dest)
+            if isinstance(default, string_types):
+                opt_str = option.get_opt_string()
+                self.defaults[option.dest] = option.check_value(
+                    opt_str, default)
+        self.values = optparse.Values(self.defaults)
+
+
+class OptionParser(optparse.OptionParser):
+    """Command line parser with GOption support.
+
+    :param bool help_enabled:
+        The --help, --help-all and --help-{group} options are enabled (default).
+    :param bool ignore_unknown_options:
+        Do not throw a exception when a option is not knwon, the option
+        will be in the result list.
+
+    .. NOTE::
+        The OptionParser interface is not the exactly the same as the
+        optparse.OptionParser interface. Especially the usage parameter
+        is only used to show the metavar of the arguements.
+
+    OptionParser.add_option_group() does not only accept OptionGroup instances
+    but also glib.OptionGroup, which is returned by gtk_get_option_group().
+
+    Only glib.option.OptionGroup and glib.option.Option instances should
+    be passed as groups and options.
+
+    For further help, see optparse.OptionParser.
+    """
+
+    def __init__(self, *args, **kwargs):
+        if 'option_class' not in kwargs:
+            kwargs['option_class'] = Option
+        self.help_enabled = kwargs.pop('help_enabled', True)
+        self.ignore_unknown_options = kwargs.pop('ignore_unknown_options',
+                                                 False)
+        optparse.OptionParser.__init__(self, add_help_option=False,
+                                       *args, **kwargs)
+
+    def set_usage(self, usage):
+        if usage is None:
+            self.usage = ''
+        elif usage.startswith("%prog"):
+            self.usage = usage[len("%prog"):]
+        else:
+            self.usage = usage
+
+    def _to_goptioncontext(self, values):
+        if self.description:
+            parameter_string = self.usage + " - " + self.description
+        else:
+            parameter_string = self.usage
+        context = _gi.OptionContext(parameter_string)
+        context.set_help_enabled(self.help_enabled)
+        context.set_ignore_unknown_options(self.ignore_unknown_options)
+
+        for option_group in self.option_groups:
+            if isinstance(option_group, _gi.OptionGroup):
+                g_group = option_group
+            else:
+                g_group = option_group.get_option_group(self)
+            context.add_group(g_group)
+
+        def callback(option_name, option_value, group):
+            if option_name.startswith('--'):
+                opt = self._long_opt[option_name]
+            else:
+                opt = self._short_opt[option_name]
+            opt.process(option_name, option_value, values, self)
+
+        main_group = _gi.OptionGroup(None, None, None, callback)
+        main_entries = []
+        for option in self.option_list:
+            main_entries.extend(option._to_goptionentries())
+        main_group.add_entries(main_entries)
+        context.set_main_group(main_group)
+
+        return context
+
+    def add_option_group(self, *args, **kwargs):
+        if isinstance(args[0], string_types):
+            optparse.OptionParser.add_option_group(self,
+                                                   OptionGroup(self, *args, **kwargs))
+            return
+        elif len(args) == 1 and not kwargs:
+            if isinstance(args[0], OptionGroup):
+                if not args[0].parser:
+                    args[0].parser = self
+                if args[0].parser is not self:
+                    raise ValueError("invalid OptionGroup (wrong parser)")
+            if isinstance(args[0], _gi.OptionGroup):
+                self.option_groups.append(args[0])
+                return
+        optparse.OptionParser.add_option_group(self, *args, **kwargs)
+
+    def _get_all_options(self):
+        options = self.option_list[:]
+        for group in self.option_groups:
+            if isinstance(group, optparse.OptionGroup):
+                options.extend(group.option_list)
+        return options
+
+    def _process_args(self, largs, rargs, values):
+        context = self._to_goptioncontext(values)
+
+        # _process_args() returns the remaining parameters in rargs.
+        # The prepended program name is used to all g_set_prgname()
+        # The program name is cut away so it doesn't appear in the result.
+        rargs[:] = context.parse([sys.argv[0]] + rargs)[1:]
+
+    def parse_args(self, args=None, values=None):
+        try:
+            options, args = optparse.OptionParser.parse_args(
+                self, args, values)
+        except GError:
+            error = sys.exc_info()[1]
+            if error.domain != OPTION_CONTEXT_ERROR_QUARK:
+                raise
+            if error.code == GLib.OptionError.BAD_VALUE:
+                raise OptionValueError(error.message)
+            elif error.code == GLib.OptionError.UNKNOWN_OPTION:
+                raise BadOptionError(error.message)
+            elif error.code == GLib.OptionError.FAILED:
+                raise OptParseError(error.message)
+            else:
+                raise
+
+        for group in self.option_groups:
+            for key, value in group.values.__dict__.items():
+                options.ensure_value(key, value)
+
+        return options, args
+
+
+make_option = Option
diff --git a/gi/_ossighelper.py b/gi/_ossighelper.py
new file mode 100644 (file)
index 0000000..213f096
--- /dev/null
@@ -0,0 +1,253 @@
+# -*- coding: utf-8 -*-
+# Copyright 2017 Christoph Reiter
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+import os
+import sys
+import socket
+import signal
+import threading
+from contextlib import closing, contextmanager
+
+from . import _gi
+
+
+def ensure_socket_not_inheritable(sock):
+    """Ensures that the socket is not inherited by child processes
+
+    Raises:
+        EnvironmentError
+        NotImplementedError: With Python <3.4 on Windows
+    """
+
+    if hasattr(sock, "set_inheritable"):
+        sock.set_inheritable(False)
+    else:
+        try:
+            import fcntl
+        except ImportError:
+            raise NotImplementedError(
+                "Not implemented for older Python on Windows")
+        else:
+            fd = sock.fileno()
+            flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+            fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
+
+
+_wakeup_fd_is_active = False
+"""Since we can't check if set_wakeup_fd() is already used for nested event
+loops without introducing a race condition we keep track of it globally.
+"""
+
+
+@contextmanager
+def wakeup_on_signal():
+    """A decorator for functions which create a glib event loop to keep
+    Python signal handlers working while the event loop is idling.
+
+    In case an OS signal is received will wake the default event loop up
+    shortly so that any registered Python signal handlers registered through
+    signal.signal() can run.
+
+    Works on Windows but needs Python 3.5+.
+
+    In case the wrapped function is not called from the main thread it will be
+    called as is and it will not wake up the default loop for signals.
+    """
+
+    global _wakeup_fd_is_active
+
+    if _wakeup_fd_is_active:
+        yield
+        return
+
+    from gi.repository import GLib
+
+    # On Windows only Python 3.5+ supports passing sockets to set_wakeup_fd
+    set_wakeup_fd_supports_socket = (
+        os.name != "nt" or sys.version_info[:2] >= (3, 5))
+    # On Windows only Python 3 has an implementation of socketpair()
+    has_socketpair = hasattr(socket, "socketpair")
+
+    if not has_socketpair or not set_wakeup_fd_supports_socket:
+        yield
+        return
+
+    read_socket, write_socket = socket.socketpair()
+    with closing(read_socket), closing(write_socket):
+
+        for sock in [read_socket, write_socket]:
+            sock.setblocking(False)
+            ensure_socket_not_inheritable(sock)
+
+        try:
+            orig_fd = signal.set_wakeup_fd(write_socket.fileno())
+        except ValueError:
+            # Raised in case this is not the main thread -> give up.
+            yield
+            return
+        else:
+            _wakeup_fd_is_active = True
+
+        def signal_notify(source, condition):
+            if condition & GLib.IO_IN:
+                try:
+                    return bool(read_socket.recv(1))
+                except EnvironmentError as e:
+                    print(e)
+                    return False
+                return True
+            else:
+                return False
+
+        try:
+            if os.name == "nt":
+                channel = GLib.IOChannel.win32_new_socket(
+                    read_socket.fileno())
+            else:
+                channel = GLib.IOChannel.unix_new(read_socket.fileno())
+
+            source_id = GLib.io_add_watch(
+                channel,
+                GLib.PRIORITY_DEFAULT,
+                (GLib.IOCondition.IN | GLib.IOCondition.HUP |
+                 GLib.IOCondition.NVAL | GLib.IOCondition.ERR),
+                signal_notify)
+            try:
+                yield
+            finally:
+                GLib.source_remove(source_id)
+        finally:
+            write_fd = signal.set_wakeup_fd(orig_fd)
+            if write_fd != write_socket.fileno():
+                # Someone has called set_wakeup_fd while func() was active,
+                # so let's re-revert again.
+                signal.set_wakeup_fd(write_fd)
+            _wakeup_fd_is_active = False
+
+
+PyOS_getsig = _gi.pyos_getsig
+
+# We save the signal pointer so we can detect if glib has changed the
+# signal handler behind Python's back (GLib.unix_signal_add)
+if signal.getsignal(signal.SIGINT) is signal.default_int_handler:
+    startup_sigint_ptr = PyOS_getsig(signal.SIGINT)
+else:
+    # Something has set the handler before import, we can't get a ptr
+    # for the default handler so make sure the pointer will never match.
+    startup_sigint_ptr = -1
+
+
+def sigint_handler_is_default():
+    """Returns if on SIGINT the default Python handler would be called"""
+
+    return (signal.getsignal(signal.SIGINT) is signal.default_int_handler and
+            PyOS_getsig(signal.SIGINT) == startup_sigint_ptr)
+
+
+@contextmanager
+def sigint_handler_set_and_restore_default(handler):
+    """Context manager for saving/restoring the SIGINT handler default state.
+
+    Will only restore the default handler again if the handler is not changed
+    while the context is active.
+    """
+
+    assert sigint_handler_is_default()
+
+    signal.signal(signal.SIGINT, handler)
+    sig_ptr = PyOS_getsig(signal.SIGINT)
+    try:
+        yield
+    finally:
+        if signal.getsignal(signal.SIGINT) is handler and \
+                PyOS_getsig(signal.SIGINT) == sig_ptr:
+            signal.signal(signal.SIGINT, signal.default_int_handler)
+
+
+def is_main_thread():
+    """Returns True in case the function is called from the main thread"""
+
+    return threading.current_thread().name == "MainThread"
+
+
+_callback_stack = []
+_sigint_called = False
+
+
+@contextmanager
+def register_sigint_fallback(callback):
+    """Installs a SIGINT signal handler in case the default Python one is
+    active which calls 'callback' in case the signal occurs.
+
+    Only does something if called from the main thread.
+
+    In case of nested context managers the signal handler will be only
+    installed once and the callbacks will be called in the reverse order
+    of their registration.
+
+    The old signal handler will be restored in case no signal handler is
+    registered while the context is active.
+    """
+
+    # To handle multiple levels of event loops we need to call the last
+    # callback first, wait until the inner most event loop returns control
+    # and only then call the next callback, and so on... until we
+    # reach the outer most which manages the signal handler and raises
+    # in the end
+
+    global _callback_stack, _sigint_called
+
+    if not is_main_thread():
+        yield
+        return
+
+    if not sigint_handler_is_default():
+        if _callback_stack:
+            # This is an inner event loop, append our callback
+            # to the stack so the parent context can call it.
+            _callback_stack.append(callback)
+            try:
+                yield
+            finally:
+                cb = _callback_stack.pop()
+                if _sigint_called:
+                    cb()
+        else:
+            # There is a signal handler set by the user, just do nothing
+            yield
+        return
+
+    _sigint_called = False
+
+    def sigint_handler(sig_num, frame):
+        global _callback_stack, _sigint_called
+
+        if _sigint_called:
+            return
+        _sigint_called = True
+        _callback_stack.pop()()
+
+    _callback_stack.append(callback)
+    try:
+        with sigint_handler_set_and_restore_default(sigint_handler):
+            yield
+    finally:
+        if _sigint_called:
+            signal.default_int_handler(signal.SIGINT, None)
+        else:
+            _callback_stack.pop()
diff --git a/gi/_propertyhelper.py b/gi/_propertyhelper.py
new file mode 100644 (file)
index 0000000..6f82ab6
--- /dev/null
@@ -0,0 +1,405 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# pygobject - Python bindings for the GObject library
+# Copyright (C) 2007 Johan Dahlin
+#
+#   gi/_propertyhelper.py: GObject property wrapper/helper
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+from . import _gi
+from ._compat import string_types, long_
+from ._constants import \
+    TYPE_NONE, TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR, \
+    TYPE_BOOLEAN, TYPE_INT, TYPE_UINT, TYPE_LONG, \
+    TYPE_ULONG, TYPE_INT64, TYPE_UINT64, TYPE_ENUM, TYPE_FLAGS, \
+    TYPE_FLOAT, TYPE_DOUBLE, TYPE_STRING, \
+    TYPE_POINTER, TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, \
+    TYPE_PYOBJECT, TYPE_GTYPE, TYPE_STRV, TYPE_VARIANT
+
+G_MAXFLOAT = _gi.G_MAXFLOAT
+G_MAXDOUBLE = _gi.G_MAXDOUBLE
+G_MININT = _gi.G_MININT
+G_MAXINT = _gi.G_MAXINT
+G_MAXUINT = _gi.G_MAXUINT
+G_MINLONG = _gi.G_MINLONG
+G_MAXLONG = _gi.G_MAXLONG
+G_MAXULONG = _gi.G_MAXULONG
+
+
+class Property(object):
+    """Creates a new Property which when used in conjunction with
+    GObject subclass will create a Python property accessor for the
+    GObject ParamSpec.
+
+    :param callable getter:
+        getter to get the value of the property
+    :param callable setter:
+        setter to set the value of the property
+    :param type type:
+        type of property
+    :param default:
+        default value, must match the property type.
+    :param str nick:
+        short description
+    :param str blurb:
+        long description
+    :param GObject.ParamFlags flags:
+        parameter flags
+    :keyword minimum:
+        minimum allowed value (int, float, long only)
+    :keyword maximum:
+        maximum allowed value (int, float, long only)
+
+    .. code-block:: python
+
+         class MyObject(GObject.Object):
+             prop = GObject.Property(type=str)
+
+         obj = MyObject()
+         obj.prop = 'value'
+
+         obj.prop  # now is 'value'
+
+    The API is similar to the builtin :py:func:`property`:
+
+    .. code-block:: python
+
+        class AnotherObject(GObject.Object):
+            value = 0
+
+            @GObject.Property
+            def prop(self):
+                'Read only property.'
+                return 1
+
+            @GObject.Property(type=int)
+            def propInt(self):
+                'Read-write integer property.'
+                return self.value
+
+            @propInt.setter
+            def propInt(self, value):
+                self.value = value
+    """
+    _type_from_pytype_lookup = {
+        # Put long_ first in case long_ and int are the same so int clobbers long_
+        long_: TYPE_LONG,
+        int: TYPE_INT,
+        bool: TYPE_BOOLEAN,
+        float: TYPE_DOUBLE,
+        str: TYPE_STRING,
+        object: TYPE_PYOBJECT,
+    }
+
+    _min_value_lookup = {
+        TYPE_UINT: 0,
+        TYPE_ULONG: 0,
+        TYPE_UINT64: 0,
+        # Remember that G_MINFLOAT and G_MINDOUBLE are something different.
+        TYPE_FLOAT: -G_MAXFLOAT,
+        TYPE_DOUBLE: -G_MAXDOUBLE,
+        TYPE_INT: G_MININT,
+        TYPE_LONG: G_MINLONG,
+        TYPE_INT64: -2 ** 63,
+    }
+
+    _max_value_lookup = {
+        TYPE_UINT: G_MAXUINT,
+        TYPE_ULONG: G_MAXULONG,
+        TYPE_INT64: 2 ** 63 - 1,
+        TYPE_UINT64: 2 ** 64 - 1,
+        TYPE_FLOAT: G_MAXFLOAT,
+        TYPE_DOUBLE: G_MAXDOUBLE,
+        TYPE_INT: G_MAXINT,
+        TYPE_LONG: G_MAXLONG,
+    }
+
+    _default_lookup = {
+        TYPE_INT: 0,
+        TYPE_UINT: 0,
+        TYPE_LONG: 0,
+        TYPE_ULONG: 0,
+        TYPE_INT64: 0,
+        TYPE_UINT64: 0,
+        TYPE_STRING: '',
+        TYPE_FLOAT: 0.0,
+        TYPE_DOUBLE: 0.0,
+    }
+
+    class __metaclass__(type):
+        def __repr__(self):
+            return "<class 'GObject.Property'>"
+
+    def __init__(self, getter=None, setter=None, type=None, default=None,
+                 nick='', blurb='', flags=_gi.PARAM_READWRITE,
+                 minimum=None, maximum=None):
+        self.name = None
+
+        if type is None:
+            type = object
+        self.type = self._type_from_python(type)
+        self.default = self._get_default(default)
+        self._check_default()
+
+        if not isinstance(nick, string_types):
+            raise TypeError("nick must be a string")
+        self.nick = nick
+
+        if not isinstance(blurb, string_types):
+            raise TypeError("blurb must be a string")
+        self.blurb = blurb
+        # Always clobber __doc__ with blurb even if blurb is empty because
+        # we don't want the lengthy Property class documentation showing up
+        # on instances.
+        self.__doc__ = blurb
+        self.flags = flags
+
+        # Call after setting blurb for potential __doc__ usage.
+        if getter and not setter:
+            setter = self._readonly_setter
+        elif setter and not getter:
+            getter = self._writeonly_getter
+        elif not setter and not getter:
+            getter = self._default_getter
+            setter = self._default_setter
+        self.getter(getter)
+        # do not call self.setter() here, as this defines the property name
+        # already
+        self.fset = setter
+
+        if minimum is not None:
+            if minimum < self._get_minimum():
+                raise TypeError(
+                    "Minimum for type %s cannot be lower than %d" %
+                    (self.type, self._get_minimum()))
+        else:
+            minimum = self._get_minimum()
+        self.minimum = minimum
+        if maximum is not None:
+            if maximum > self._get_maximum():
+                raise TypeError(
+                    "Maximum for type %s cannot be higher than %d" %
+                    (self.type, self._get_maximum()))
+        else:
+            maximum = self._get_maximum()
+        self.maximum = maximum
+
+        self._exc = None
+
+    def __repr__(self):
+        return '<GObject Property %s (%s)>' % (
+            self.name or '(uninitialized)',
+            self.type.name)
+
+    def __get__(self, instance, klass):
+        if instance is None:
+            return self
+
+        self._exc = None
+        value = self.fget(instance)
+        if self._exc:
+            exc = self._exc
+            self._exc = None
+            raise exc
+
+        return value
+
+    def __set__(self, instance, value):
+        if instance is None:
+            raise TypeError
+
+        self._exc = None
+        instance.set_property(self.name, value)
+        if self._exc:
+            exc = self._exc
+            self._exc = None
+            raise exc
+
+    def __call__(self, fget):
+        """Allows application of the getter along with init arguments."""
+        return self.getter(fget)
+
+    def getter(self, fget):
+        """Set the getter function to fget. For use as a decorator."""
+        if fget.__doc__:
+            # Always clobber docstring and blurb with the getter docstring.
+            self.blurb = fget.__doc__
+            self.__doc__ = fget.__doc__
+        self.fget = fget
+        return self
+
+    def setter(self, fset):
+        """Set the setter function to fset. For use as a decorator."""
+        self.fset = fset
+        # with a setter decorator, we must ignore the name of the method in
+        # install_properties, as this does not need to be a valid property name
+        # and does not define the property name. So set the name here.
+        if not self.name:
+            self.name = self.fget.__name__
+        return self
+
+    def _type_from_python(self, type_):
+        if type_ in self._type_from_pytype_lookup:
+            return self._type_from_pytype_lookup[type_]
+        elif (isinstance(type_, type) and
+              issubclass(type_, (_gi.GObject,
+                                 _gi.GEnum,
+                                 _gi.GFlags,
+                                 _gi.GBoxed,
+                                 _gi.GInterface))):
+            return type_.__gtype__
+        elif type_ in (TYPE_NONE, TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR,
+                       TYPE_INT, TYPE_UINT, TYPE_BOOLEAN, TYPE_LONG,
+                       TYPE_ULONG, TYPE_INT64, TYPE_UINT64,
+                       TYPE_FLOAT, TYPE_DOUBLE, TYPE_POINTER,
+                       TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, TYPE_STRING,
+                       TYPE_PYOBJECT, TYPE_GTYPE, TYPE_STRV, TYPE_VARIANT):
+            return type_
+        else:
+            raise TypeError("Unsupported type: %r" % (type_,))
+
+    def _get_default(self, default):
+        if default is not None:
+            return default
+        return self._default_lookup.get(self.type, None)
+
+    def _check_default(self):
+        ptype = self.type
+        default = self.default
+        if (ptype == TYPE_BOOLEAN and (default not in (True, False))):
+            raise TypeError(
+                "default must be True or False, not %r" % (default,))
+        elif ptype == TYPE_PYOBJECT:
+            if default is not None:
+                raise TypeError("object types does not have default values")
+        elif ptype == TYPE_GTYPE:
+            if default is not None:
+                raise TypeError("GType types does not have default values")
+        elif ptype.is_a(TYPE_ENUM):
+            if default is None:
+                raise TypeError("enum properties needs a default value")
+            elif not _gi.GType(default).is_a(ptype):
+                raise TypeError("enum value %s must be an instance of %r" %
+                                (default, ptype))
+        elif ptype.is_a(TYPE_FLAGS):
+            if not _gi.GType(default).is_a(ptype):
+                raise TypeError("flags value %s must be an instance of %r" %
+                                (default, ptype))
+        elif ptype.is_a(TYPE_STRV) and default is not None:
+            if not isinstance(default, list):
+                raise TypeError("Strv value %s must be a list" % repr(default))
+            for val in default:
+                if type(val) not in (str, bytes):
+                    raise TypeError("Strv value %s must contain only strings" % str(default))
+        elif ptype.is_a(TYPE_VARIANT) and default is not None:
+            if not hasattr(default, '__gtype__') or not _gi.GType(default).is_a(TYPE_VARIANT):
+                raise TypeError("variant value %s must be an instance of %r" %
+                                (default, ptype))
+
+    def _get_minimum(self):
+        return self._min_value_lookup.get(self.type, None)
+
+    def _get_maximum(self):
+        return self._max_value_lookup.get(self.type, None)
+
+    #
+    # Getter and Setter
+    #
+
+    def _default_setter(self, instance, value):
+        setattr(instance, '_property_helper_' + self.name, value)
+
+    def _default_getter(self, instance):
+        return getattr(instance, '_property_helper_' + self.name, self.default)
+
+    def _readonly_setter(self, instance, value):
+        self._exc = TypeError("%s property of %s is read-only" % (
+            self.name, type(instance).__name__))
+
+    def _writeonly_getter(self, instance):
+        self._exc = TypeError("%s property of %s is write-only" % (
+            self.name, type(instance).__name__))
+
+    #
+    # Public API
+    #
+
+    def get_pspec_args(self):
+        ptype = self.type
+        if ptype in (TYPE_INT, TYPE_UINT, TYPE_LONG, TYPE_ULONG,
+                     TYPE_INT64, TYPE_UINT64, TYPE_FLOAT, TYPE_DOUBLE):
+            args = self.minimum, self.maximum, self.default
+        elif (ptype == TYPE_STRING or ptype == TYPE_BOOLEAN or
+              ptype.is_a(TYPE_ENUM) or ptype.is_a(TYPE_FLAGS) or
+              ptype.is_a(TYPE_VARIANT)):
+            args = (self.default,)
+        elif ptype in (TYPE_PYOBJECT, TYPE_GTYPE):
+            args = ()
+        elif ptype.is_a(TYPE_OBJECT) or ptype.is_a(TYPE_BOXED):
+            args = ()
+        else:
+            raise NotImplementedError(ptype)
+
+        return (self.type, self.nick, self.blurb) + args + (self.flags,)
+
+
+def install_properties(cls):
+    """
+    Scans the given class for instances of Property and merges them
+    into the classes __gproperties__ dict if it exists or adds it if not.
+    """
+    gproperties = cls.__dict__.get('__gproperties__', {})
+
+    props = []
+    for name, prop in cls.__dict__.items():
+        if isinstance(prop, Property):  # not same as the built-in
+            # if a property was defined with a decorator, it may already have
+            # a name; if it was defined with an assignment (prop = Property(...))
+            # we set the property's name to the member name
+            if not prop.name:
+                prop.name = name
+            # we will encounter the same property multiple times in case of
+            # custom setter methods
+            if prop.name in gproperties:
+                if gproperties[prop.name] == prop.get_pspec_args():
+                    continue
+                raise ValueError('Property %s was already found in __gproperties__' % prop.name)
+            gproperties[prop.name] = prop.get_pspec_args()
+            props.append(prop)
+
+    if not props:
+        return
+
+    cls.__gproperties__ = gproperties
+
+    if 'do_get_property' in cls.__dict__ or 'do_set_property' in cls.__dict__:
+        for prop in props:
+            if prop.fget != prop._default_getter or prop.fset != prop._default_setter:
+                raise TypeError(
+                    "GObject subclass %r defines do_get/set_property"
+                    " and it also uses a property with a custom setter"
+                    " or getter. This is not allowed" %
+                    (cls.__name__,))
+
+    def obj_get_property(self, pspec):
+        name = pspec.name.replace('-', '_')
+        return getattr(self, name, None)
+    cls.do_get_property = obj_get_property
+
+    def obj_set_property(self, pspec, value):
+        name = pspec.name.replace('-', '_')
+        prop = getattr(cls, name, None)
+        if prop:
+            prop.fset(self, value)
+    cls.do_set_property = obj_set_property
diff --git a/gi/_signalhelper.py b/gi/_signalhelper.py
new file mode 100644 (file)
index 0000000..8a1f0a4
--- /dev/null
@@ -0,0 +1,249 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# pygobject - Python bindings for the GObject library
+# Copyright (C) 2012 Simon Feltman
+#
+#   gi/_signalhelper.py: GObject signal binding decorator object
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+from . import _gi
+
+
+class Signal(str):
+    """Object which gives a nice API for creating and binding signals.
+
+    :param name:
+        Name of signal or callable closure when used as a decorator.
+    :type name: str or callable
+    :param callable func:
+        Callable closure method.
+    :param GObject.SignalFlags flags:
+        Flags specifying when to run closure.
+    :param type return_type:
+        Return type of the Signal.
+    :param list arg_types:
+        List of argument types specifying the signals function signature
+    :param str doc:
+        Documentation of signal object.
+    :param callable accumulator:
+        Accumulator method with the signature:
+        func(ihint, return_accu, handler_return, accu_data) -> boolean
+    :param object accu_data:
+        User data passed to the accumulator.
+
+    :Example:
+
+    .. code-block:: python
+
+        class Spam(GObject.Object):
+            velocity = 0
+
+            @GObject.Signal
+            def pushed(self):
+                self.velocity += 1
+
+            @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
+            def pulled(self):
+                self.velocity -= 1
+
+            stomped = GObject.Signal('stomped', arg_types=(int,))
+
+            @GObject.Signal
+            def annotated_signal(self, a:int, b:str):
+                "Python3 annotation support for parameter types.
+
+        def on_pushed(obj):
+            print(obj)
+
+        spam = Spam()
+        spam.pushed.connect(on_pushed)
+        spam.pushed.emit()
+    """
+    class BoundSignal(str):
+        """
+        Temporary binding object which can be used for connecting signals
+        without specifying the signal name string to connect.
+        """
+        def __new__(cls, name, *args, **kargs):
+            return str.__new__(cls, name)
+
+        def __init__(self, signal, gobj):
+            str.__init__(self)
+            self.signal = signal
+            self.gobj = gobj
+
+        def __repr__(self):
+            return 'BoundSignal("%s")' % self
+
+        def __call__(self, *args, **kargs):
+            """Call the signals closure."""
+            return self.signal.func(self.gobj, *args, **kargs)
+
+        def connect(self, callback, *args, **kargs):
+            """Same as GObject.Object.connect except there is no need to specify
+            the signal name."""
+            return self.gobj.connect(self, callback, *args, **kargs)
+
+        def connect_detailed(self, callback, detail, *args, **kargs):
+            """Same as GObject.Object.connect except there is no need to specify
+            the signal name. In addition concats "::<detail>" to the signal name
+            when connecting; for use with notifications like "notify" when a property
+            changes.
+            """
+            return self.gobj.connect(self + '::' + detail, callback, *args, **kargs)
+
+        def disconnect(self, handler_id):
+            """Same as GObject.Object.disconnect."""
+            self.gobj.disconnect(handler_id)
+
+        def emit(self, *args, **kargs):
+            """Same as GObject.Object.emit except there is no need to specify
+            the signal name."""
+            return self.gobj.emit(str(self), *args, **kargs)
+
+    def __new__(cls, name='', *args, **kargs):
+        if callable(name):
+            name = name.__name__
+        return str.__new__(cls, name)
+
+    def __init__(self, name='', func=None, flags=_gi.SIGNAL_RUN_FIRST,
+                 return_type=None, arg_types=None, doc='', accumulator=None, accu_data=None):
+        if func is None and callable(name):
+            func = name
+
+        if func and not doc:
+            doc = func.__doc__
+
+        str.__init__(self)
+
+        if func and not (return_type or arg_types):
+            return_type, arg_types = get_signal_annotations(func)
+        if arg_types is None:
+            arg_types = tuple()
+
+        self.func = func
+        self.flags = flags
+        self.return_type = return_type
+        self.arg_types = arg_types
+        self.__doc__ = doc
+        self.accumulator = accumulator
+        self.accu_data = accu_data
+
+    def __get__(self, instance, owner=None):
+        """Returns a BoundSignal when accessed on an object instance."""
+        if instance is None:
+            return self
+        return self.BoundSignal(self, instance)
+
+    def __call__(self, obj, *args, **kargs):
+        """Allows for instantiated Signals to be used as a decorator or calling
+        of the underlying signal method."""
+
+        # If obj is a GObject, than we call this signal as a closure otherwise
+        # it is used as a re-application of a decorator.
+        if isinstance(obj, _gi.GObject):
+            self.func(obj, *args, **kargs)
+        else:
+            # If self is already an allocated name, use it otherwise create a new named
+            # signal using the closure name as the name.
+            if str(self):
+                name = str(self)
+            else:
+                name = obj.__name__
+            # Return a new value of this type since it is based on an immutable string.
+            return type(self)(name=name, func=obj, flags=self.flags,
+                              return_type=self.return_type, arg_types=self.arg_types,
+                              doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data)
+
+    def copy(self, newName=None):
+        """Returns a renamed copy of the Signal."""
+
+        return type(self)(name=newName, func=self.func, flags=self.flags,
+                          return_type=self.return_type, arg_types=self.arg_types,
+                          doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data)
+
+    def get_signal_args(self):
+        """Returns a tuple of: (flags, return_type, arg_types, accumulator, accu_data)"""
+        return (self.flags, self.return_type, self.arg_types, self.accumulator, self.accu_data)
+
+
+class SignalOverride(Signal):
+    """Specialized sub-class of Signal which can be used as a decorator for overriding
+    existing signals on GObjects.
+
+    :Example:
+
+    .. code-block:: python
+
+        class MyWidget(Gtk.Widget):
+            @GObject.SignalOverride
+            def configure_event(self):
+                pass
+    """
+    def get_signal_args(self):
+        """Returns the string 'override'."""
+        return 'override'
+
+
+def get_signal_annotations(func):
+    """Attempt pulling python 3 function annotations off of 'func' for
+    use as a signals type information. Returns an ordered nested tuple
+    of (return_type, (arg_type1, arg_type2, ...)). If the given function
+    does not have annotations then (None, tuple()) is returned.
+    """
+    arg_types = tuple()
+    return_type = None
+
+    if hasattr(func, '__annotations__'):
+        # import inspect only when needed because it takes ~10 msec to load
+        import inspect
+        spec = inspect.getfullargspec(func)
+        arg_types = tuple(spec.annotations[arg] for arg in spec.args
+                          if arg in spec.annotations)
+        if 'return' in spec.annotations:
+            return_type = spec.annotations['return']
+
+    return return_type, arg_types
+
+
+def install_signals(cls):
+    """Adds Signal instances on a GObject derived class into the '__gsignals__'
+    dictionary to be picked up and registered as real GObject signals.
+    """
+    gsignals = cls.__dict__.get('__gsignals__', {})
+    newsignals = {}
+    for name, signal in cls.__dict__.items():
+        if isinstance(signal, Signal):
+            signalName = str(signal)
+            # Fixup a signal which is unnamed by using the class variable name.
+            # Since Signal is based on string which immutable,
+            # we must copy and replace the class variable.
+            if not signalName:
+                signalName = name
+                signal = signal.copy(name)
+                setattr(cls, name, signal)
+            if signalName in gsignals:
+                raise ValueError('Signal "%s" has already been registered.' % name)
+            newsignals[signalName] = signal
+            gsignals[signalName] = signal.get_signal_args()
+
+    cls.__gsignals__ = gsignals
+
+    # Setup signal closures by adding the specially named
+    # method to the class in the form of "do_<signal_name>".
+    for name, signal in newsignals.items():
+        if signal.func is not None:
+            funcName = 'do_' + name.replace('-', '_')
+            if not hasattr(cls, funcName):
+                setattr(cls, funcName, signal.func)
diff --git a/gi/docstring.py b/gi/docstring.py
new file mode 100644 (file)
index 0000000..fec5f63
--- /dev/null
@@ -0,0 +1,205 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2013 Simon Feltman <sfeltman@gnome.org>
+#
+#   docstring.py: documentation string generator for gi.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from ._gi import \
+    VFuncInfo, \
+    FunctionInfo, \
+    CallableInfo, \
+    ObjectInfo, \
+    StructInfo, \
+    Direction, \
+    TypeTag
+
+
+#: Module storage for currently registered doc string generator function.
+_generate_doc_string_func = None
+
+
+def set_doc_string_generator(func):
+    """Set doc string generator function
+
+    :param callable func:
+        Callable which takes a GIInfoStruct and returns documentation for it.
+    """
+    global _generate_doc_string_func
+    _generate_doc_string_func = func
+
+
+def get_doc_string_generator():
+    """Returns the currently registered doc string generator."""
+    return _generate_doc_string_func
+
+
+def generate_doc_string(info):
+    """Generate a doc string given a GIInfoStruct.
+
+    :param gi.types.BaseInfo info:
+        GI info instance to generate documentation for.
+    :returns:
+        Generated documentation as a string.
+    :rtype: str
+
+    This passes the info struct to the currently registered doc string
+    generator and returns the result.
+    """
+    return _generate_doc_string_func(info)
+
+
+_type_tag_to_py_type = {TypeTag.BOOLEAN: bool,
+                        TypeTag.INT8: int,
+                        TypeTag.UINT8: int,
+                        TypeTag.INT16: int,
+                        TypeTag.UINT16: int,
+                        TypeTag.INT32: int,
+                        TypeTag.UINT32: int,
+                        TypeTag.INT64: int,
+                        TypeTag.UINT64: int,
+                        TypeTag.FLOAT: float,
+                        TypeTag.DOUBLE: float,
+                        TypeTag.GLIST: list,
+                        TypeTag.GSLIST: list,
+                        TypeTag.ARRAY: list,
+                        TypeTag.GHASH: dict,
+                        TypeTag.UTF8: str,
+                        TypeTag.FILENAME: str,
+                        TypeTag.UNICHAR: str,
+                        TypeTag.INTERFACE: None,
+                        TypeTag.GTYPE: None,
+                        TypeTag.ERROR: None,
+                        TypeTag.VOID: None,
+                        }
+
+
+def _get_pytype_hint(gi_type):
+    type_tag = gi_type.get_tag()
+    py_type = _type_tag_to_py_type.get(type_tag, None)
+
+    if py_type and hasattr(py_type, '__name__'):
+        return py_type.__name__
+    elif type_tag == TypeTag.INTERFACE:
+        iface = gi_type.get_interface()
+
+        info_name = iface.get_name()
+        if not info_name:
+            return gi_type.get_tag_as_string()
+
+        return '%s.%s' % (iface.get_namespace(), info_name)
+
+    return gi_type.get_tag_as_string()
+
+
+def _generate_callable_info_doc(info):
+    in_args_strs = []
+    if isinstance(info, VFuncInfo):
+        in_args_strs = ['self']
+    elif isinstance(info, FunctionInfo):
+        if info.is_method():
+            in_args_strs = ['self']
+
+    args = info.get_arguments()
+    hint_blacklist = ('void',)
+
+    # Build lists of indices prior to adding the docs because it is possible
+    # the index retrieved comes before input arguments being used.
+    ignore_indices = set()
+    user_data_indices = set()
+    for arg in args:
+        ignore_indices.add(arg.get_destroy())
+        ignore_indices.add(arg.get_type().get_array_length())
+        user_data_indices.add(arg.get_closure())
+
+    # Build input argument strings
+    for i, arg in enumerate(args):
+        if arg.get_direction() == Direction.OUT:
+            continue  # skip exclusively output args
+        if i in ignore_indices:
+            continue
+        argstr = arg.get_name()
+        hint = _get_pytype_hint(arg.get_type())
+        if hint not in hint_blacklist:
+            argstr += ':' + hint
+        if arg.may_be_null() or i in user_data_indices:
+            # allow-none or user_data from a closure
+            argstr += '=None'
+        elif arg.is_optional():
+            argstr += '=<optional>'
+        in_args_strs.append(argstr)
+    in_args_str = ', '.join(in_args_strs)
+
+    # Build return + output argument strings
+    out_args_strs = []
+    return_hint = _get_pytype_hint(info.get_return_type())
+    if not info.skip_return() and return_hint and return_hint not in hint_blacklist:
+        argstr = return_hint
+        if info.may_return_null():
+            argstr += ' or None'
+        out_args_strs.append(argstr)
+
+    for i, arg in enumerate(args):
+        if arg.get_direction() == Direction.IN:
+            continue  # skip exclusively input args
+        if i in ignore_indices:
+            continue
+        argstr = arg.get_name()
+        hint = _get_pytype_hint(arg.get_type())
+        if hint not in hint_blacklist:
+            argstr += ':' + hint
+        out_args_strs.append(argstr)
+
+    if out_args_strs:
+        return '%s(%s) -> %s' % (info.__name__, in_args_str, ', '.join(out_args_strs))
+    else:
+        return '%s(%s)' % (info.__name__, in_args_str)
+
+
+def _generate_class_info_doc(info):
+    header = '\n:Constructors:\n\n::\n\n'  # start with \n to avoid auto indent of other lines
+    doc = ''
+
+    if isinstance(info, StructInfo):
+        # Don't show default constructor for disguised (0 length) structs
+        if info.get_size() > 0:
+            doc += '    ' + info.get_name() + '()\n'
+    else:
+        doc += '    ' + info.get_name() + '(**properties)\n'
+
+    for method_info in info.get_methods():
+        if method_info.is_constructor():
+            doc += '    ' + _generate_callable_info_doc(method_info) + '\n'
+
+    if doc:
+        return header + doc
+    else:
+        return ''
+
+
+def _generate_doc_dispatch(info):
+    if isinstance(info, (ObjectInfo, StructInfo)):
+        return _generate_class_info_doc(info)
+
+    elif isinstance(info, CallableInfo):
+        return _generate_callable_info_doc(info)
+
+    return ''
+
+
+set_doc_string_generator(_generate_doc_dispatch)
diff --git a/gi/gimodule.c b/gi/gimodule.c
new file mode 100644 (file)
index 0000000..e46af02
--- /dev/null
@@ -0,0 +1,2561 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+ *
+ *   gimodule.c: wrapper for the gobject-introspection library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+#include <Python.h>
+#include <glib-object.h>
+
+#include "config.h"
+#include "pyginterface.h"
+#include "pygi-repository.h"
+#include "pygi-type.h"
+#include "pygenum.h"
+#include "pygboxed.h"
+#include "pygflags.h"
+#include "pygi-error.h"
+#include "pygi-foreign.h"
+#include "pygi-resulttuple.h"
+#include "pygi-source.h"
+#include "pygi-ccallback.h"
+#include "pygi-closure.h"
+#include "pygi-type.h"
+#include "pygi-boxed.h"
+#include "pygi-info.h"
+#include "pygi-struct.h"
+#include "pygobject-object.h"
+#include "pygoptioncontext.h"
+#include "pygoptiongroup.h"
+#include "pygspawn.h"
+#include "pygparamspec.h"
+#include "pygpointer.h"
+#include "pygobject-internal.h"
+#include "pygi-value.h"
+#include "pygi-property.h"
+#include "pygi-util.h"
+#include "gimodule.h"
+#include "pygi-python-compat.h"
+#include "pygi-basictype.h"
+
+PyObject *PyGIWarning;
+PyObject *PyGIDeprecationWarning;
+PyObject *_PyGIDefaultArgPlaceholder;
+
+/* Returns a new flag/enum type or %NULL */
+static PyObject *
+flags_enum_from_gtype (GType g_type,
+                       PyObject * (add_func) (PyObject *, const char *,
+                                              const char *, GType))
+{
+    PyObject *new_type;
+    GIRepository *repository;
+    GIBaseInfo *info;
+    const gchar *type_name;
+
+    repository = g_irepository_get_default ();
+    info = g_irepository_find_by_gtype (repository, g_type);
+    if (info != NULL) {
+        type_name = g_base_info_get_name (info);
+        new_type = add_func (NULL, type_name, NULL, g_type);
+        g_base_info_unref (info);
+    } else {
+        type_name = g_type_name (g_type);
+        new_type = add_func (NULL, type_name, NULL, g_type);
+    }
+
+    return new_type;
+}
+
+static void pyg_flags_add_constants(PyObject *module, GType flags_type,
+                                   const gchar *strip_prefix);
+
+/**
+ * pyg_enum_add_constants:
+ * @module: a Python module
+ * @enum_type: the GType of the enumeration.
+ * @strip_prefix: the prefix to strip from the constant names.
+ *
+ * Adds constants to the given Python module for each value name of
+ * the enumeration.  A prefix will be stripped from each enum name.
+ */
+static void
+pyg_enum_add_constants(PyObject *module, GType enum_type,
+                      const gchar *strip_prefix)
+{
+    GEnumClass *eclass;
+    guint i;
+
+    if (!G_TYPE_IS_ENUM(enum_type)) {
+       if (G_TYPE_IS_FLAGS(enum_type)) /* See bug #136204 */
+           pyg_flags_add_constants(module, enum_type, strip_prefix);
+       else
+           g_warning("`%s' is not an enum type", g_type_name(enum_type));
+       return;
+    }
+    g_return_if_fail (strip_prefix != NULL);
+
+    eclass = G_ENUM_CLASS(g_type_class_ref(enum_type));
+
+    for (i = 0; i < eclass->n_values; i++) {
+       const gchar *name = eclass->values[i].value_name;
+       gint value = eclass->values[i].value;
+
+       PyModule_AddIntConstant(module,
+                               (char*) pyg_constant_strip_prefix(name, strip_prefix),
+                               (long) value);
+    }
+
+    g_type_class_unref(eclass);
+}
+
+/**
+ * pyg_flags_add_constants:
+ * @module: a Python module
+ * @flags_type: the GType of the flags type.
+ * @strip_prefix: the prefix to strip from the constant names.
+ *
+ * Adds constants to the given Python module for each value name of
+ * the flags set.  A prefix will be stripped from each flag name.
+ */
+static void
+pyg_flags_add_constants(PyObject *module, GType flags_type,
+                       const gchar *strip_prefix)
+{
+    GFlagsClass *fclass;
+    guint i;
+
+    if (!G_TYPE_IS_FLAGS(flags_type)) {
+       if (G_TYPE_IS_ENUM(flags_type)) /* See bug #136204 */
+           pyg_enum_add_constants(module, flags_type, strip_prefix);
+       else
+           g_warning("`%s' is not an flags type", g_type_name(flags_type));
+       return;
+    }
+    g_return_if_fail (strip_prefix != NULL);
+
+    fclass = G_FLAGS_CLASS(g_type_class_ref(flags_type));
+
+    for (i = 0; i < fclass->n_values; i++) {
+       const gchar *name = fclass->values[i].value_name;
+       guint value = fclass->values[i].value;
+
+       PyModule_AddIntConstant(module,
+                               (char*) pyg_constant_strip_prefix(name, strip_prefix),
+                               (long) value);
+    }
+
+    g_type_class_unref(fclass);
+}
+
+/**
+ * pyg_set_thread_block_funcs:
+ * Deprecated, only available for ABI compatibility.
+ */
+static void
+_pyg_set_thread_block_funcs (PyGThreadBlockFunc block_threads_func,
+                            PyGThreadBlockFunc unblock_threads_func)
+{
+    PyGILState_STATE state = PyGILState_Ensure ();
+    PyErr_Warn (PyExc_DeprecationWarning,
+                "Using pyg_set_thread_block_funcs is not longer needed. "
+                "PyGObject always uses Py_BLOCK/UNBLOCK_THREADS.");
+    PyGILState_Release (state);
+}
+
+static GParamSpec *
+create_property (const gchar  *prop_name,
+                GType         prop_type,
+                const gchar  *nick,
+                const gchar  *blurb,
+                PyObject     *args,
+                GParamFlags   flags)
+{
+    GParamSpec *pspec = NULL;
+
+    switch (G_TYPE_FUNDAMENTAL(prop_type)) {
+    case G_TYPE_CHAR:
+       {
+           gchar minimum, maximum, default_value;
+
+           if (!PyArg_ParseTuple(args, "ccc", &minimum, &maximum,
+                                 &default_value))
+               return NULL;
+           pspec = g_param_spec_char (prop_name, nick, blurb, minimum,
+                                      maximum, default_value, flags);
+       }
+       break;
+    case G_TYPE_UCHAR:
+       {
+           gchar minimum, maximum, default_value;
+
+           if (!PyArg_ParseTuple(args, "ccc", &minimum, &maximum,
+                                 &default_value))
+               return NULL;
+           pspec = g_param_spec_uchar (prop_name, nick, blurb, minimum,
+                                       maximum, default_value, flags);
+       }
+       break;
+    case G_TYPE_BOOLEAN:
+       {
+           gboolean default_value;
+
+           if (!PyArg_ParseTuple(args, "i", &default_value))
+               return NULL;
+           pspec = g_param_spec_boolean (prop_name, nick, blurb,
+                                         default_value, flags);
+       }
+       break;
+    case G_TYPE_INT:
+       {
+           gint minimum, maximum, default_value;
+
+           if (!PyArg_ParseTuple(args, "iii", &minimum, &maximum,
+                                 &default_value))
+               return NULL;
+           pspec = g_param_spec_int (prop_name, nick, blurb, minimum,
+                                     maximum, default_value, flags);
+       }
+       break;
+    case G_TYPE_UINT:
+       {
+           guint minimum, maximum, default_value;
+
+           if (!PyArg_ParseTuple(args, "III", &minimum, &maximum,
+                                 &default_value))
+               return NULL;
+           pspec = g_param_spec_uint (prop_name, nick, blurb, minimum,
+                                      maximum, default_value, flags);
+       }
+       break;
+    case G_TYPE_LONG:
+       {
+           glong minimum, maximum, default_value;
+
+           if (!PyArg_ParseTuple(args, "lll", &minimum, &maximum,
+                                 &default_value))
+               return NULL;
+           pspec = g_param_spec_long (prop_name, nick, blurb, minimum,
+                                      maximum, default_value, flags);
+       }
+       break;
+    case G_TYPE_ULONG:
+       {
+           gulong minimum, maximum, default_value;
+
+           if (!PyArg_ParseTuple(args, "kkk", &minimum, &maximum,
+                                 &default_value))
+               return NULL;
+           pspec = g_param_spec_ulong (prop_name, nick, blurb, minimum,
+                                       maximum, default_value, flags);
+       }
+       break;
+    case G_TYPE_INT64:
+       {
+           gint64 minimum, maximum, default_value;
+
+           if (!PyArg_ParseTuple(args, "LLL", &minimum, &maximum,
+                                 &default_value))
+               return NULL;
+           pspec = g_param_spec_int64 (prop_name, nick, blurb, minimum,
+                                       maximum, default_value, flags);
+       }
+       break;
+    case G_TYPE_UINT64:
+       {
+           guint64 minimum, maximum, default_value;
+
+           if (!PyArg_ParseTuple(args, "KKK", &minimum, &maximum,
+                                 &default_value))
+               return NULL;
+           pspec = g_param_spec_uint64 (prop_name, nick, blurb, minimum,
+                                        maximum, default_value, flags);
+       }
+       break;
+    case G_TYPE_ENUM:
+       {
+           gint default_value;
+           PyObject *pydefault;
+
+           if (!PyArg_ParseTuple(args, "O", &pydefault))
+               return NULL;
+
+           if (pyg_enum_get_value(prop_type, pydefault,
+                                  (gint *)&default_value))
+               return NULL;
+
+           pspec = g_param_spec_enum (prop_name, nick, blurb,
+                                      prop_type, default_value, flags);
+       }
+       break;
+    case G_TYPE_FLAGS:
+       {
+           guint default_value;
+           PyObject *pydefault;
+
+           if (!PyArg_ParseTuple(args, "O", &pydefault))
+               return NULL;
+
+           if (pyg_flags_get_value(prop_type, pydefault,
+                                   &default_value))
+               return NULL;
+
+           pspec = g_param_spec_flags (prop_name, nick, blurb,
+                                       prop_type, default_value, flags);
+       }
+       break;
+    case G_TYPE_FLOAT:
+       {
+           gfloat minimum, maximum, default_value;
+
+           if (!PyArg_ParseTuple(args, "fff", &minimum, &maximum,
+                                 &default_value))
+               return NULL;
+           pspec = g_param_spec_float (prop_name, nick, blurb, minimum,
+                                       maximum, default_value, flags);
+       }
+       break;
+    case G_TYPE_DOUBLE:
+       {
+           gdouble minimum, maximum, default_value;
+
+           if (!PyArg_ParseTuple(args, "ddd", &minimum, &maximum,
+                                 &default_value))
+               return NULL;
+           pspec = g_param_spec_double (prop_name, nick, blurb, minimum,
+                                        maximum, default_value, flags);
+       }
+       break;
+    case G_TYPE_STRING:
+       {
+           const gchar *default_value;
+
+           if (!PyArg_ParseTuple(args, "z", &default_value))
+               return NULL;
+           pspec = g_param_spec_string (prop_name, nick, blurb,
+                                        default_value, flags);
+       }
+       break;
+    case G_TYPE_PARAM:
+       if (!PyArg_ParseTuple(args, ""))
+           return NULL;
+       pspec = g_param_spec_param (prop_name, nick, blurb, prop_type, flags);
+       break;
+    case G_TYPE_BOXED:
+       if (!PyArg_ParseTuple(args, ""))
+           return NULL;
+       pspec = g_param_spec_boxed (prop_name, nick, blurb, prop_type, flags);
+       break;
+    case G_TYPE_POINTER:
+       if (!PyArg_ParseTuple(args, ""))
+           return NULL;
+       if (prop_type == G_TYPE_GTYPE)
+           pspec = g_param_spec_gtype (prop_name, nick, blurb, G_TYPE_NONE, flags);
+       else
+           pspec = g_param_spec_pointer (prop_name, nick, blurb, flags);
+       break;
+    case G_TYPE_OBJECT:
+    case G_TYPE_INTERFACE:
+       if (!PyArg_ParseTuple(args, ""))
+           return NULL;
+       pspec = g_param_spec_object (prop_name, nick, blurb, prop_type, flags);
+       break;
+    case G_TYPE_VARIANT:
+       {
+           PyObject *pydefault;
+            GVariant *default_value = NULL;
+
+           if (!PyArg_ParseTuple(args, "O", &pydefault))
+               return NULL;
+            if (pydefault != Py_None)
+                default_value = pyg_boxed_get (pydefault, GVariant);
+           pspec = g_param_spec_variant (prop_name, nick, blurb, G_VARIANT_TYPE_ANY, default_value, flags);
+       }
+       break;
+    default:
+       /* unhandled pspec type ... */
+       break;
+    }
+
+    if (!pspec) {
+       char buf[128];
+
+       g_snprintf(buf, sizeof(buf), "could not create param spec for type %s",
+                  g_type_name(prop_type));
+       PyErr_SetString(PyExc_TypeError, buf);
+       return NULL;
+    }
+
+    return pspec;
+}
+
+static GParamSpec *
+pyg_param_spec_from_object (PyObject *tuple)
+{
+    Py_ssize_t val_length;
+    const gchar *prop_name;
+    GType prop_type;
+    const gchar *nick, *blurb;
+    PyObject *slice, *item, *py_prop_type;
+    GParamSpec *pspec;
+    gint intvalue;
+
+    val_length = PyTuple_Size(tuple);
+    if (val_length < 4) {
+       PyErr_SetString(PyExc_TypeError,
+                       "paramspec tuples must be at least 4 elements long");
+       return NULL;
+    }
+
+    slice = PySequence_GetSlice(tuple, 0, 4);
+    if (!slice) {
+       return NULL;
+    }
+
+    if (!PyArg_ParseTuple(slice, "sOzz", &prop_name, &py_prop_type, &nick, &blurb)) {
+       Py_DECREF(slice);
+       return NULL;
+    }
+
+    Py_DECREF(slice);
+
+    prop_type = pyg_type_from_object(py_prop_type);
+    if (!prop_type) {
+       return NULL;
+    }
+
+    item = PyTuple_GetItem(tuple, val_length-1);
+    if (!PYGLIB_PyLong_Check(item)) {
+       PyErr_SetString(PyExc_TypeError,
+                       "last element in tuple must be an int");
+       return NULL;
+    }
+
+    if (!pygi_gint_from_py (item, &intvalue))
+       return NULL;
+
+    /* slice is the extra items in the tuple */
+    slice = PySequence_GetSlice(tuple, 4, val_length-1);
+    pspec = create_property(prop_name, prop_type,
+                           nick, blurb, slice,
+                           intvalue);
+
+    return pspec;
+}
+
+/**
+ * pyg_parse_constructor_args: helper function for PyGObject constructors
+ * @obj_type: GType of the GObject, for parameter introspection
+ * @arg_names: %NULL-terminated array of constructor argument names
+ * @prop_names: %NULL-terminated array of property names, with direct
+ * correspondence to @arg_names
+ * @params: GParameter array where parameters will be placed; length
+ * of this array must be at least equal to the number of
+ * arguments/properties
+ * @nparams: output parameter to contain actual number of arguments found
+ * @py_args: array of PyObject* containing the actual constructor arguments
+ *
+ * Parses an array of PyObject's and creates a GParameter array
+ *
+ * Return value: %TRUE if all is successful, otherwise %FALSE and
+ * python exception set.
+ **/
+static gboolean
+pyg_parse_constructor_args(GType        obj_type,
+                           char       **arg_names,
+                           char       **prop_names,
+                           GParameter  *params,
+                           guint       *nparams,
+                           PyObject   **py_args)
+{
+    guint arg_i, param_i;
+    GObjectClass *oclass;
+
+    oclass = g_type_class_ref(obj_type);
+    g_return_val_if_fail(oclass, FALSE);
+
+    for (param_i = arg_i = 0; arg_names[arg_i]; ++arg_i) {
+        GParamSpec *spec;
+        if (!py_args[arg_i])
+            continue;
+        spec = g_object_class_find_property(oclass, prop_names[arg_i]);
+        params[param_i].name = prop_names[arg_i];
+        g_value_init(&params[param_i].value, spec->value_type);
+        if (pyg_value_from_pyobject(&params[param_i].value, py_args[arg_i]) == -1) {
+            guint i;
+            PyErr_Format(PyExc_TypeError, "could not convert parameter '%s' of type '%s'",
+                         arg_names[arg_i], g_type_name(spec->value_type));
+            g_type_class_unref(oclass);
+            for (i = 0; i < param_i; ++i)
+                g_value_unset(&params[i].value);
+            return FALSE;
+        }
+        ++param_i;
+    }
+    g_type_class_unref(oclass);
+    *nparams = param_i;
+    return TRUE;
+}
+
+/* Only for backwards compatibility */
+static int
+pygobject_enable_threads(void)
+{
+    return 0;
+}
+
+static int
+pygobject_gil_state_ensure (void)
+{
+    return PyGILState_Ensure ();
+}
+
+static void
+pygobject_gil_state_release (int flag)
+{
+    PyGILState_Release(flag);
+}
+
+static void
+pyg_register_class_init(GType gtype, PyGClassInitFunc class_init)
+{
+    GSList *list;
+
+    list = g_type_get_qdata(gtype, pygobject_class_init_key);
+    list = g_slist_prepend(list, class_init);
+    g_type_set_qdata(gtype, pygobject_class_init_key, list);
+}
+
+static gboolean
+add_properties (GObjectClass *klass, PyObject *properties)
+{
+    gboolean ret = TRUE;
+    Py_ssize_t pos = 0;
+    PyObject *key, *value;
+
+    while (PyDict_Next(properties, &pos, &key, &value)) {
+       const gchar *prop_name;
+       GType prop_type;
+       const gchar *nick, *blurb;
+       GParamFlags flags;
+       Py_ssize_t val_length;
+       PyObject *slice, *item, *py_prop_type;
+       GParamSpec *pspec;
+
+       /* values are of format (type,nick,blurb, type_specific_args, flags) */
+
+       if (!PYGLIB_PyUnicode_Check(key)) {
+           PyErr_SetString(PyExc_TypeError,
+                           "__gproperties__ keys must be strings");
+           ret = FALSE;
+           break;
+       }
+       prop_name = PYGLIB_PyUnicode_AsString (key);
+
+       if (!PyTuple_Check(value)) {
+           PyErr_SetString(PyExc_TypeError,
+                           "__gproperties__ values must be tuples");
+           ret = FALSE;
+           break;
+       }
+       val_length = PyTuple_Size(value);
+       if (val_length < 4) {
+           PyErr_SetString(PyExc_TypeError,
+                           "__gproperties__ values must be at least 4 elements long");
+           ret = FALSE;
+           break;
+       }
+
+       slice = PySequence_GetSlice(value, 0, 3);
+       if (!slice) {
+           ret = FALSE;
+           break;
+       }
+       if (!PyArg_ParseTuple(slice, "Ozz", &py_prop_type, &nick, &blurb)) {
+           Py_DECREF(slice);
+           ret = FALSE;
+           break;
+       }
+       Py_DECREF(slice);
+       prop_type = pyg_type_from_object(py_prop_type);
+       if (!prop_type) {
+           ret = FALSE;
+           break;
+       }
+       item = PyTuple_GetItem(value, val_length-1);
+       if (!PYGLIB_PyLong_Check(item)) {
+           PyErr_SetString(PyExc_TypeError,
+               "last element in __gproperties__ value tuple must be an int");
+           ret = FALSE;
+           break;
+       }
+       if (!pygi_gint_from_py (item, &flags)) {
+           ret = FALSE;
+           break;
+       }
+
+       /* slice is the extra items in the tuple */
+       slice = PySequence_GetSlice(value, 3, val_length-1);
+       pspec = create_property(prop_name, prop_type, nick, blurb,
+                               slice, flags);
+       Py_DECREF(slice);
+
+       if (pspec) {
+           g_object_class_install_property(klass, 1, pspec);
+       } else {
+            PyObject *type, *pvalue, *traceback;
+           ret = FALSE;
+            PyErr_Fetch(&type, &pvalue, &traceback);
+            if (PYGLIB_PyUnicode_Check(pvalue)) {
+                char msg[256];
+                g_snprintf(msg, 256,
+                          "%s (while registering property '%s' for GType '%s')",
+               PYGLIB_PyUnicode_AsString(pvalue),
+                          prop_name, G_OBJECT_CLASS_NAME(klass));
+                Py_DECREF(pvalue);
+                value = PYGLIB_PyUnicode_FromString(msg);
+            }
+            PyErr_Restore(type, pvalue, traceback);
+           break;
+       }
+    }
+
+    return ret;
+}
+
+static gboolean
+override_signal(GType instance_type, const gchar *signal_name)
+{
+    guint signal_id;
+
+    signal_id = g_signal_lookup(signal_name, instance_type);
+    if (!signal_id) {
+       gchar buf[128];
+
+       g_snprintf(buf, sizeof(buf), "could not look up %s", signal_name);
+       PyErr_SetString(PyExc_TypeError, buf);
+       return FALSE;
+    }
+    g_signal_override_class_closure(signal_id, instance_type,
+                                   pyg_signal_class_closure_get());
+    return TRUE;
+}
+
+typedef struct _PyGSignalAccumulatorData {
+    PyObject *callable;
+    PyObject *user_data;
+} PyGSignalAccumulatorData;
+
+
+static gboolean
+_pyg_signal_accumulator(GSignalInvocationHint *ihint,
+                        GValue *return_accu,
+                        const GValue *handler_return,
+                        gpointer _data)
+{
+    PyObject *py_ihint, *py_return_accu, *py_handler_return, *py_detail;
+    PyObject *py_retval;
+    gboolean retval = FALSE;
+    PyGSignalAccumulatorData *data = _data;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+    if (ihint->detail)
+        py_detail = PYGLIB_PyUnicode_FromString(g_quark_to_string(ihint->detail));
+    else {
+        Py_INCREF(Py_None);
+        py_detail = Py_None;
+    }
+
+    py_ihint = Py_BuildValue("lNi", (long int) ihint->signal_id,
+                             py_detail, ihint->run_type);
+    py_handler_return = pyg_value_as_pyobject(handler_return, TRUE);
+    py_return_accu = pyg_value_as_pyobject(return_accu, FALSE);
+    if (data->user_data)
+        py_retval = PyObject_CallFunction(data->callable, "NNNO", py_ihint,
+                                          py_return_accu, py_handler_return,
+                                          data->user_data);
+    else
+        py_retval = PyObject_CallFunction(data->callable, "NNN", py_ihint,
+                                          py_return_accu, py_handler_return);
+    if (!py_retval)
+       PyErr_Print();
+    else {
+        if (!PyTuple_Check(py_retval) || PyTuple_Size(py_retval) != 2) {
+            PyErr_SetString(PyExc_TypeError, "accumulator function must return"
+                            " a (bool, object) tuple");
+            PyErr_Print();
+        } else {
+            retval = PyObject_IsTrue(PyTuple_GET_ITEM(py_retval, 0));
+            if (pyg_value_from_pyobject(return_accu, PyTuple_GET_ITEM(py_retval, 1))) {
+                PyErr_Print();
+            }
+        }
+        Py_DECREF(py_retval);
+    }
+    PyGILState_Release(state);
+    return retval;
+}
+
+static gboolean
+create_signal (GType instance_type, const gchar *signal_name, PyObject *tuple)
+{
+    GSignalFlags signal_flags;
+    PyObject *py_return_type, *py_param_types;
+    GType return_type;
+    guint n_params, i;
+    Py_ssize_t py_n_params;
+    GType *param_types;
+    guint signal_id;
+    GSignalAccumulator accumulator = NULL;
+    PyGSignalAccumulatorData *accum_data = NULL;
+    PyObject *py_accum = NULL, *py_accum_data = NULL;
+
+    if (!PyArg_ParseTuple(tuple, "iOO|OO", &signal_flags, &py_return_type,
+                         &py_param_types, &py_accum, &py_accum_data))
+    {
+       gchar buf[128];
+
+       PyErr_Clear();
+       g_snprintf(buf, sizeof(buf),
+                  "value for __gsignals__['%s'] not in correct format", signal_name);
+       PyErr_SetString(PyExc_TypeError, buf);
+       return FALSE;
+    }
+
+    if (py_accum && py_accum != Py_None && !PyCallable_Check(py_accum))
+    {
+       gchar buf[128];
+
+       g_snprintf(buf, sizeof(buf),
+                  "accumulator for __gsignals__['%s'] must be callable", signal_name);
+       PyErr_SetString(PyExc_TypeError, buf);
+       return FALSE;
+    }
+
+    return_type = pyg_type_from_object(py_return_type);
+    if (!return_type)
+       return FALSE;
+    if (!PySequence_Check(py_param_types)) {
+       gchar buf[128];
+
+       g_snprintf(buf, sizeof(buf),
+                  "third element of __gsignals__['%s'] tuple must be a sequence", signal_name);
+       PyErr_SetString(PyExc_TypeError, buf);
+       return FALSE;
+    }
+    py_n_params = PySequence_Length(py_param_types);
+    if (py_n_params < 0)
+        return FALSE;
+
+    if (!pygi_guint_from_pyssize (py_n_params, &n_params))
+        return FALSE;
+
+    param_types = g_new(GType, n_params);
+    for (i = 0; i < n_params; i++) {
+       PyObject *item = PySequence_GetItem(py_param_types, i);
+
+       param_types[i] = pyg_type_from_object(item);
+       if (param_types[i] == 0) {
+           Py_DECREF(item);
+           g_free(param_types);
+           return FALSE;
+       }
+       Py_DECREF(item);
+    }
+
+    if (py_accum != NULL && py_accum != Py_None) {
+        accum_data = g_new(PyGSignalAccumulatorData, 1);
+        accum_data->callable = py_accum;
+        Py_INCREF(py_accum);
+        accum_data->user_data = py_accum_data;
+        Py_XINCREF(py_accum_data);
+        accumulator = _pyg_signal_accumulator;
+    }
+
+    signal_id = g_signal_newv(signal_name, instance_type, signal_flags,
+                             pyg_signal_class_closure_get(),
+                             accumulator, accum_data,
+                             gi_cclosure_marshal_generic,
+                             return_type, n_params, param_types);
+    g_free(param_types);
+
+    if (signal_id == 0) {
+       gchar buf[128];
+
+       g_snprintf(buf, sizeof(buf), "could not create signal for %s",
+                  signal_name);
+       PyErr_SetString(PyExc_RuntimeError, buf);
+       return FALSE;
+    }
+    return TRUE;
+}
+
+
+static PyObject *
+add_signals (GObjectClass *klass, PyObject *signals)
+{
+    gboolean ret = TRUE;
+    Py_ssize_t pos = 0;
+    PyObject *key, *value, *overridden_signals = NULL;
+    GType instance_type = G_OBJECT_CLASS_TYPE (klass);
+
+    overridden_signals = PyDict_New();
+    while (PyDict_Next(signals, &pos, &key, &value)) {
+       const gchar *signal_name;
+        gchar *signal_name_canon, *c;
+
+       if (!PYGLIB_PyUnicode_Check(key)) {
+           PyErr_SetString(PyExc_TypeError,
+                           "__gsignals__ keys must be strings");
+           ret = FALSE;
+           break;
+       }
+       signal_name = PYGLIB_PyUnicode_AsString (key);
+
+       if (value == Py_None ||
+           (PYGLIB_PyUnicode_Check(value) &&
+            !strcmp(PYGLIB_PyUnicode_AsString(value), "override")))
+        {
+              /* canonicalize signal name, replacing '-' with '_' */
+            signal_name_canon = g_strdup(signal_name);
+            for (c = signal_name_canon; *c; ++c)
+                if (*c == '-')
+                    *c = '_';
+            if (PyDict_SetItemString(overridden_signals,
+                                    signal_name_canon, key)) {
+                g_free(signal_name_canon);
+                ret = FALSE;
+                break;
+            }
+            g_free(signal_name_canon);
+
+           ret = override_signal(instance_type, signal_name);
+       } else {
+           ret = create_signal(instance_type, signal_name, value);
+       }
+
+       if (!ret)
+           break;
+    }
+    if (ret)
+        return overridden_signals;
+    else {
+        Py_XDECREF(overridden_signals);
+        return NULL;
+    }
+}
+
+static void
+pyg_object_get_property (GObject *object, guint property_id,
+                        GValue *value, GParamSpec *pspec)
+{
+    PyObject *object_wrapper, *retval;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+
+    object_wrapper = g_object_get_qdata(object, pygobject_wrapper_key);
+
+    if (object_wrapper)
+      Py_INCREF (object_wrapper);
+    else
+      object_wrapper = pygobject_new(object);
+
+    if (object_wrapper == NULL) {
+       PyGILState_Release(state);
+       return;
+    }
+
+    retval = pygi_call_do_get_property (object_wrapper, pspec);
+    if (retval && pyg_value_from_pyobject (value, retval) < 0) {
+        PyErr_Print();
+    }
+    Py_DECREF(object_wrapper);
+    Py_XDECREF(retval);
+
+    PyGILState_Release(state);
+}
+
+static void
+pyg_object_set_property (GObject *object, guint property_id,
+                        const GValue *value, GParamSpec *pspec)
+{
+    PyObject *object_wrapper, *retval;
+    PyObject *py_pspec, *py_value;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+
+    object_wrapper = g_object_get_qdata(object, pygobject_wrapper_key);
+
+    if (object_wrapper)
+      Py_INCREF (object_wrapper);
+    else
+      object_wrapper = pygobject_new(object);
+
+    if (object_wrapper == NULL) {
+       PyGILState_Release(state);
+       return;
+    }
+
+    py_pspec = pyg_param_spec_new(pspec);
+    py_value = pyg_value_as_pyobject (value, TRUE);
+
+    retval = PyObject_CallMethod(object_wrapper, "do_set_property",
+                                "OO", py_pspec, py_value);
+    if (retval) {
+       Py_DECREF(retval);
+    } else {
+       PyErr_Print();
+    }
+
+    Py_DECREF(object_wrapper);
+    Py_DECREF(py_pspec);
+    Py_DECREF(py_value);
+
+    PyGILState_Release(state);
+}
+
+static void
+pyg_object_class_init(GObjectClass *class, PyObject *py_class)
+{
+    PyObject *gproperties, *gsignals, *overridden_signals;
+    PyObject *class_dict = ((PyTypeObject*) py_class)->tp_dict;
+
+    class->set_property = pyg_object_set_property;
+    class->get_property = pyg_object_get_property;
+
+    /* install signals */
+    /* we look this up in the instance dictionary, so we don't
+     * accidentally get a parent type's __gsignals__ attribute. */
+    gsignals = PyDict_GetItemString(class_dict, "__gsignals__");
+    if (gsignals) {
+       if (!PyDict_Check(gsignals)) {
+           PyErr_SetString(PyExc_TypeError,
+                           "__gsignals__ attribute not a dict!");
+           return;
+       }
+       if (!(overridden_signals = add_signals(class, gsignals))) {
+           return;
+       }
+        if (PyDict_SetItemString(class_dict, "__gsignals__",
+                                overridden_signals)) {
+            return;
+        }
+        Py_DECREF(overridden_signals);
+
+        PyDict_DelItemString(class_dict, "__gsignals__");
+    } else {
+       PyErr_Clear();
+    }
+
+    /* install properties */
+    /* we look this up in the instance dictionary, so we don't
+     * accidentally get a parent type's __gproperties__ attribute. */
+    gproperties = PyDict_GetItemString(class_dict, "__gproperties__");
+    if (gproperties) {
+       if (!PyDict_Check(gproperties)) {
+           PyErr_SetString(PyExc_TypeError,
+                           "__gproperties__ attribute not a dict!");
+           return;
+       }
+       if (!add_properties(class, gproperties)) {
+           return;
+       }
+       PyDict_DelItemString(class_dict, "__gproperties__");
+       /* Borrowed reference. Py_DECREF(gproperties); */
+    } else {
+       PyErr_Clear();
+    }
+}
+
+static GPrivate pygobject_construction_wrapper;
+
+static inline void
+pygobject_init_wrapper_set(PyObject *wrapper)
+{
+    g_private_set(&pygobject_construction_wrapper, wrapper);
+}
+
+static inline PyObject *
+pygobject_init_wrapper_get(void)
+{
+    return (PyObject *) g_private_get(&pygobject_construction_wrapper);
+}
+
+int
+pygobject_constructv(PyGObject  *self,
+                     guint       n_parameters,
+                     GParameter *parameters)
+{
+    GObject *obj;
+
+    g_assert (self->obj == NULL);
+    pygobject_init_wrapper_set((PyObject *) self);
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+    obj = g_object_newv(pyg_type_from_object((PyObject *) self),
+                        n_parameters, parameters);
+G_GNUC_END_IGNORE_DEPRECATIONS
+    if (g_object_is_floating (obj))
+        self->private_flags.flags |= PYGOBJECT_GOBJECT_WAS_FLOATING;
+    pygobject_sink (obj);
+
+    pygobject_init_wrapper_set(NULL);
+    self->obj = obj;
+    pygobject_register_wrapper((PyObject *) self);
+
+    return 0;
+}
+
+static void
+pygobject__g_instance_init(GTypeInstance   *instance,
+                           gpointer         g_class)
+{
+    GObject *object = (GObject *) instance;
+    PyObject *wrapper, *result;
+    PyGILState_STATE state;
+
+    wrapper = g_object_get_qdata(object, pygobject_wrapper_key);
+    if (wrapper == NULL) {
+        wrapper = pygobject_init_wrapper_get();
+        if (wrapper && ((PyGObject *) wrapper)->obj == NULL) {
+            ((PyGObject *) wrapper)->obj = object;
+            pygobject_register_wrapper(wrapper);
+        }
+    }
+    pygobject_init_wrapper_set(NULL);
+
+    state = PyGILState_Ensure();
+
+    if (wrapper == NULL) {
+          /* this looks like a python object created through
+           * g_object_new -> we have no python wrapper, so create it
+           * now */
+        wrapper = pygobject_new_full(object,
+                                     /*steal=*/ FALSE,
+                                     g_class);
+
+        /* float the wrapper ref here because we are going to orphan it
+         * so we don't destroy the wrapper. The next call to pygobject_new_full
+         * will take the ref */
+        pygobject_ref_float ((PyGObject *) wrapper);
+
+        result = PyObject_CallMethod (wrapper, "__init__", NULL);
+        if (result == NULL)
+            PyErr_Print ();
+        else
+            Py_DECREF (result);
+    }
+
+    /* XXX: used for Gtk.Template */
+    if (PyObject_HasAttrString (wrapper, "__dontuse_ginstance_init__")) {
+        result = PyObject_CallMethod (wrapper, "__dontuse_ginstance_init__", NULL);
+        if (result == NULL)
+            PyErr_Print ();
+        else
+            Py_DECREF (result);
+    }
+
+    PyGILState_Release(state);
+}
+
+/*  This implementation is bad, see bug 566571 for an example why.
+ *  Instead of scanning explicitly declared bases for interfaces, we
+ *  should automatically initialize all implemented interfaces to
+ *  prevent bugs like that one.  However, this will lead to
+ *  performance degradation as each virtual method in derived classes
+ *  will round-trip through do_*() stuff, *even* if it is not
+ *  overriden.  We need to teach codegen to retain parent method
+ *  instead of setting virtual to *_proxy_do_*() if corresponding
+ *  do_*() is not overriden.  Ok, that was a messy explanation.
+ */
+static void
+pyg_type_add_interfaces(PyTypeObject *class, GType instance_type,
+                        PyObject *bases,
+                        GType *parent_interfaces, guint n_parent_interfaces)
+{
+    int i;
+
+    if (!bases) {
+        g_warning("type has no bases");
+        return;
+    }
+
+    for (i = 0; i < PyTuple_GET_SIZE(bases); ++i) {
+        PyObject *base = PyTuple_GET_ITEM(bases, i);
+        GType itype;
+        const GInterfaceInfo *iinfo;
+        GInterfaceInfo iinfo_copy;
+
+        /* 'base' can also be a PyClassObject, see bug #566571. */
+        if (!PyType_Check(base))
+            continue;
+
+        if (!PyType_IsSubtype((PyTypeObject*) base, &PyGInterface_Type))
+            continue;
+
+        itype = pyg_type_from_object(base);
+
+        /* Happens for _implementations_ of an interface. */
+        if (!G_TYPE_IS_INTERFACE(itype))
+            continue;
+
+        iinfo = pyg_lookup_interface_info(itype);
+        if (!iinfo) {
+            gchar *error;
+            error = g_strdup_printf("Interface type %s "
+                                    "has no Python implementation support",
+                                    ((PyTypeObject *) base)->tp_name);
+            PyErr_Warn(PyExc_RuntimeWarning, error);
+            g_free(error);
+            continue;
+        }
+
+        iinfo_copy = *iinfo;
+        iinfo_copy.interface_data = class;
+        g_type_add_interface_static(instance_type, itype, &iinfo_copy);
+    }
+}
+
+static int
+pyg_run_class_init(GType gtype, gpointer gclass, PyTypeObject *pyclass)
+{
+    GSList *list;
+    PyGClassInitFunc class_init;
+    GType parent_type;
+    int rv;
+
+    parent_type = g_type_parent(gtype);
+    if (parent_type) {
+        rv = pyg_run_class_init(parent_type, gclass, pyclass);
+        if (rv)
+           return rv;
+    }
+
+    list = g_type_get_qdata(gtype, pygobject_class_init_key);
+    for (; list; list = list->next) {
+       class_init = list->data;
+        rv = class_init(gclass, pyclass);
+        if (rv)
+           return rv;
+    }
+
+    return 0;
+}
+
+static char *
+get_type_name_for_class(PyTypeObject *class)
+{
+    gint i, name_serial;
+    char name_serial_str[16];
+    PyObject *module;
+    char *type_name = NULL;
+
+    /* make name for new GType */
+    name_serial = 1;
+    /* give up after 1000 tries, just in case.. */
+    while (name_serial < 1000)
+    {
+       g_free(type_name);
+       g_snprintf(name_serial_str, 16, "-v%i", name_serial);
+       module = PyObject_GetAttrString((PyObject *)class, "__module__");
+       if (module && PYGLIB_PyUnicode_Check(module)) {
+           type_name = g_strconcat(PYGLIB_PyUnicode_AsString(module), ".",
+                                   class->tp_name,
+                                   name_serial > 1 ? name_serial_str : NULL,
+                                   NULL);
+           Py_DECREF(module);
+       } else {
+           if (module)
+               Py_DECREF(module);
+           else
+               PyErr_Clear();
+           type_name = g_strconcat(class->tp_name,
+                                   name_serial > 1 ? name_serial_str : NULL,
+                                   NULL);
+       }
+       /* convert '.' in type name to '+', which isn't banned (grumble) */
+       for (i = 0; type_name[i] != '\0'; i++)
+           if (type_name[i] == '.')
+               type_name[i] = '+';
+       if (g_type_from_name(type_name) == 0)
+           break;              /* we now have a unique name */
+       ++name_serial;
+    }
+
+    return type_name;
+}
+
+static int
+pyg_type_register(PyTypeObject *class, const char *type_name)
+{
+    PyObject *gtype;
+    GType parent_type, instance_type;
+    GType *parent_interfaces;
+    guint n_parent_interfaces;
+    GTypeQuery query;
+    gpointer gclass;
+    GTypeInfo type_info = {
+       0,    /* class_size */
+
+       (GBaseInitFunc) NULL,
+       (GBaseFinalizeFunc) NULL,
+
+       (GClassInitFunc) pyg_object_class_init,
+       (GClassFinalizeFunc) NULL,
+       NULL, /* class_data */
+
+       0,    /* instance_size */
+       0,    /* n_preallocs */
+       (GInstanceInitFunc) pygobject__g_instance_init
+    };
+    gchar *new_type_name;
+
+    /* find the GType of the parent */
+    parent_type = pyg_type_from_object((PyObject *)class);
+    if (!parent_type)
+       return -1;
+
+    parent_interfaces = g_type_interfaces(parent_type, &n_parent_interfaces);
+
+    if (type_name)
+       /* care is taken below not to free this */
+        new_type_name = (gchar *) type_name;
+    else
+       new_type_name = get_type_name_for_class(class);
+
+    /* set class_data that will be passed to the class_init function. */
+    type_info.class_data = class;
+
+    /* fill in missing values of GTypeInfo struct */
+    g_type_query(parent_type, &query);
+    type_info.class_size = (guint16)query.class_size;
+    type_info.instance_size = (guint16)query.instance_size;
+
+    /* create new typecode */
+    instance_type = g_type_register_static(parent_type, new_type_name,
+                                          &type_info, 0);
+    if (instance_type == 0) {
+       PyErr_Format(PyExc_RuntimeError,
+                    "could not create new GType: %s (subclass of %s)",
+                    new_type_name,
+                    g_type_name(parent_type));
+
+        if (type_name == NULL)
+            g_free(new_type_name);
+
+       return -1;
+    }
+
+    if (type_name == NULL)
+        g_free(new_type_name);
+
+    /* store pointer to the class with the GType */
+    Py_INCREF(class);
+    g_type_set_qdata(instance_type, g_quark_from_string("PyGObject::class"),
+                    class);
+
+    /* Mark this GType as a custom python type */
+    g_type_set_qdata(instance_type, pygobject_custom_key,
+                     GINT_TO_POINTER (1));
+
+    /* set new value of __gtype__ on class */
+    gtype = pyg_type_wrapper_new(instance_type);
+    PyObject_SetAttrString((PyObject *)class, "__gtype__", gtype);
+    Py_DECREF(gtype);
+
+    /* if no __doc__, set it to the auto doc descriptor */
+    if (PyDict_GetItemString(class->tp_dict, "__doc__") == NULL) {
+       PyDict_SetItemString(class->tp_dict, "__doc__",
+                            pyg_object_descr_doc_get());
+    }
+
+    /*
+     * Note, all interfaces need to be registered before the first
+     * g_type_class_ref(), see bug #686149.
+     *
+     * See also comment above pyg_type_add_interfaces().
+     */
+    pyg_type_add_interfaces(class, instance_type, class->tp_bases,
+                            parent_interfaces, n_parent_interfaces);
+
+
+    gclass = g_type_class_ref(instance_type);
+    if (PyErr_Occurred() != NULL) {
+        g_type_class_unref(gclass);
+        g_free(parent_interfaces);
+        return -1;
+    }
+
+    if (pyg_run_class_init(instance_type, gclass, class)) {
+        g_type_class_unref(gclass);
+        g_free(parent_interfaces);
+        return -1;
+    }
+    g_type_class_unref(gclass);
+    g_free(parent_interfaces);
+
+    if (PyErr_Occurred() != NULL)
+        return -1;
+    return 0;
+}
+
+static PyObject *
+_wrap_pyg_type_register(PyObject *self, PyObject *args)
+{
+    PyTypeObject *class;
+    char *type_name = NULL;
+
+    if (!PyArg_ParseTuple(args, "O!|z:gobject.type_register",
+                         &PyType_Type, &class, &type_name))
+       return NULL;
+    if (!PyType_IsSubtype(class, &PyGObject_Type)) {
+       PyErr_SetString(PyExc_TypeError,
+                       "argument must be a GObject subclass");
+       return NULL;
+    }
+
+      /* Check if type already registered */
+    if (pyg_type_from_object((PyObject *) class) ==
+        pyg_type_from_object((PyObject *) class->tp_base))
+    {
+        if (pyg_type_register(class, type_name))
+            return NULL;
+    }
+
+    Py_INCREF(class);
+    return (PyObject *) class;
+}
+
+static GHashTable *log_handlers = NULL;
+static gboolean log_handlers_disabled = FALSE;
+
+static void
+remove_handler(gpointer domain,
+               gpointer handler,
+              gpointer unused)
+{
+    g_log_remove_handler(domain, GPOINTER_TO_UINT(handler));
+}
+
+static void
+_log_func(const gchar *log_domain,
+          GLogLevelFlags log_level,
+          const gchar *message,
+          gpointer user_data)
+{
+    if (G_LIKELY(Py_IsInitialized()))
+    {
+       PyGILState_STATE state;
+       PyObject* warning = user_data;
+
+       state = PyGILState_Ensure();
+       PyErr_Warn(warning, (char *) message);
+       PyGILState_Release(state);
+    } else
+        g_log_default_handler(log_domain, log_level, message, user_data);
+}
+
+static void
+add_warning_redirection(const char *domain,
+                        PyObject   *warning)
+{
+    g_return_if_fail(domain != NULL);
+    g_return_if_fail(warning != NULL);
+
+    if (!log_handlers_disabled)
+    {
+       guint handler;
+       gpointer old_handler;
+
+       if (!log_handlers)
+           log_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+       if ((old_handler = g_hash_table_lookup(log_handlers, domain)))
+           g_log_remove_handler(domain, GPOINTER_TO_UINT(old_handler));
+
+       handler = g_log_set_handler(domain, G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING,
+                                   _log_func, warning);
+       g_hash_table_insert(log_handlers, g_strdup(domain), GUINT_TO_POINTER(handler));
+    }
+}
+
+static void
+disable_warning_redirections(void)
+{
+    log_handlers_disabled = TRUE;
+
+    if (log_handlers)
+    {
+       g_hash_table_foreach(log_handlers, remove_handler, NULL);
+       g_hash_table_destroy(log_handlers);
+       log_handlers = NULL;
+    }
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+static int
+pygi_register_warnings(PyObject *d)
+{
+    PyObject *warning;
+
+    warning = PyErr_NewException("gobject.Warning", PyExc_Warning, NULL);
+    if (warning == NULL)
+        return -1;
+    PyDict_SetItemString(d, "Warning", warning);
+    add_warning_redirection("GLib", warning);
+    add_warning_redirection("GLib-GObject", warning);
+    add_warning_redirection("GThread", warning);
+
+    return 0;
+}
+
+static PyObject *
+_wrap_pyg_enum_add (PyObject *self,
+                    PyObject *args,
+                    PyObject *kwargs)
+{
+    static char *kwlist[] = { "g_type", NULL };
+    PyObject *py_g_type;
+    GType g_type;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "O!:enum_add",
+                                      kwlist, &PyGTypeWrapper_Type, &py_g_type)) {
+        return NULL;
+    }
+
+    g_type = pyg_type_from_object (py_g_type);
+    if (g_type == G_TYPE_INVALID) {
+        return NULL;
+    }
+
+    return flags_enum_from_gtype (g_type, pyg_enum_add);
+}
+
+static PyObject *
+_wrap_pyg_enum_register_new_gtype_and_add (PyObject *self,
+                                           PyObject *args,
+                                           PyObject *kwargs)
+{
+    static char *kwlist[] = { "info", NULL };
+    PyGIBaseInfo *py_info;
+    GIEnumInfo *info;
+    gint n_values;
+    GEnumValue *g_enum_values;
+    int i;
+    const gchar *namespace;
+    const gchar *type_name;
+    gchar *full_name;
+    GType g_type;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "O:enum_add_make_new_gtype",
+                                      kwlist, (PyObject *)&py_info)) {
+        return NULL;
+    }
+
+    if (!GI_IS_ENUM_INFO (py_info->info) ||
+            g_base_info_get_type ((GIBaseInfo *) py_info->info) != GI_INFO_TYPE_ENUM) {
+        PyErr_SetString (PyExc_TypeError, "info must be an EnumInfo with info type GI_INFO_TYPE_ENUM");
+        return NULL;
+    }
+
+    info = (GIEnumInfo *)py_info->info;
+    n_values = g_enum_info_get_n_values (info);
+
+    /* The new memory is zero filled which fulfills the registration
+     * function requirement that the last item is zeroed out as a terminator.
+     */
+    g_enum_values = g_new0 (GEnumValue, n_values + 1);
+
+    for (i = 0; i < n_values; i++) {
+        GIValueInfo *value_info;
+        GEnumValue *enum_value;
+        const gchar *name;
+        const gchar *c_identifier;
+
+        value_info = g_enum_info_get_value (info, i);
+        name = g_base_info_get_name ((GIBaseInfo *) value_info);
+        c_identifier = g_base_info_get_attribute ((GIBaseInfo *) value_info,
+                                                  "c:identifier");
+
+        enum_value = &g_enum_values[i];
+        enum_value->value_nick = g_strdup (name);
+        enum_value->value = (gint)g_value_info_get_value (value_info);
+
+        if (c_identifier == NULL) {
+            enum_value->value_name = enum_value->value_nick;
+        } else {
+            enum_value->value_name = g_strdup (c_identifier);
+        }
+
+        g_base_info_unref ((GIBaseInfo *) value_info);
+    }
+
+    /* Obfuscate the full_name by prefixing it with "Py" to avoid conflicts
+     * with real GTypes. See: https://bugzilla.gnome.org/show_bug.cgi?id=692515
+     */
+    namespace = g_base_info_get_namespace ((GIBaseInfo *) info);
+    type_name = g_base_info_get_name ((GIBaseInfo *) info);
+    full_name = g_strconcat ("Py", namespace, type_name, NULL);
+
+    /* If enum registration fails, free all the memory allocated
+     * for the values array. This needs to leak when successful
+     * as GObject keeps a reference to the data as specified in the docs.
+     */
+    g_type = g_enum_register_static (full_name, g_enum_values);
+    if (g_type == G_TYPE_INVALID) {
+        for (i = 0; i < n_values; i++) {
+            GEnumValue *enum_value = &g_enum_values[i];
+
+            /* Only free value_name if it is different from value_nick to avoid
+             * a double free. The pointer might have been is re-used in the case
+             * c_identifier was NULL in the above loop.
+             */
+            if (enum_value->value_name != enum_value->value_nick)
+                g_free ((gchar *) enum_value->value_name);
+            g_free ((gchar *) enum_value->value_nick);
+        }
+
+        PyErr_Format (PyExc_RuntimeError, "Unable to register enum '%s'", full_name);
+
+        g_free (g_enum_values);
+        g_free (full_name);
+        return NULL;
+    }
+
+    g_free (full_name);
+    return pyg_enum_add (NULL, type_name, NULL, g_type);
+}
+
+static PyObject *
+_wrap_pyg_flags_add (PyObject *self,
+                     PyObject *args,
+                     PyObject *kwargs)
+{
+    static char *kwlist[] = { "g_type", NULL };
+    PyObject *py_g_type;
+    GType g_type;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "O!:flags_add",
+                                      kwlist, &PyGTypeWrapper_Type, &py_g_type)) {
+        return NULL;
+    }
+
+    g_type = pyg_type_from_object (py_g_type);
+    if (g_type == G_TYPE_INVALID) {
+        return NULL;
+    }
+
+    return flags_enum_from_gtype (g_type, pyg_flags_add);
+}
+
+static PyObject *
+_wrap_pyg_flags_register_new_gtype_and_add (PyObject *self,
+                                            PyObject *args,
+                                            PyObject *kwargs)
+{
+    static char *kwlist[] = { "info", NULL };
+    PyGIBaseInfo *py_info;
+    GIEnumInfo *info;
+    gint n_values;
+    GFlagsValue *g_flags_values;
+    int i;
+    const gchar *namespace;
+    const gchar *type_name;
+    gchar *full_name;
+    GType g_type;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "O:flags_add_make_new_gtype",
+                                      kwlist, (PyObject *)&py_info)) {
+        return NULL;
+    }
+
+    if (!GI_IS_ENUM_INFO (py_info->info) ||
+            g_base_info_get_type ((GIBaseInfo *) py_info->info) != GI_INFO_TYPE_FLAGS) {
+        PyErr_SetString (PyExc_TypeError, "info must be an EnumInfo with info type GI_INFO_TYPE_FLAGS");
+        return NULL;
+    }
+
+    info = (GIEnumInfo *)py_info->info;
+    n_values = g_enum_info_get_n_values (info);
+
+    /* The new memory is zero filled which fulfills the registration
+     * function requirement that the last item is zeroed out as a terminator.
+     */
+    g_flags_values = g_new0 (GFlagsValue, n_values + 1);
+
+    for (i = 0; i < n_values; i++) {
+        GIValueInfo *value_info;
+        GFlagsValue *flags_value;
+        const gchar *name;
+        const gchar *c_identifier;
+
+        value_info = g_enum_info_get_value (info, i);
+        name = g_base_info_get_name ((GIBaseInfo *) value_info);
+        c_identifier = g_base_info_get_attribute ((GIBaseInfo *) value_info,
+                                                  "c:identifier");
+
+        flags_value = &g_flags_values[i];
+        flags_value->value_nick = g_strdup (name);
+        flags_value->value = (guint)g_value_info_get_value (value_info);
+
+        if (c_identifier == NULL) {
+            flags_value->value_name = flags_value->value_nick;
+        } else {
+            flags_value->value_name = g_strdup (c_identifier);
+        }
+
+        g_base_info_unref ((GIBaseInfo *) value_info);
+    }
+
+    /* Obfuscate the full_name by prefixing it with "Py" to avoid conflicts
+     * with real GTypes. See: https://bugzilla.gnome.org/show_bug.cgi?id=692515
+     */
+    namespace = g_base_info_get_namespace ((GIBaseInfo *) info);
+    type_name = g_base_info_get_name ((GIBaseInfo *) info);
+    full_name = g_strconcat ("Py", namespace, type_name, NULL);
+
+    /* If enum registration fails, free all the memory allocated
+     * for the values array. This needs to leak when successful
+     * as GObject keeps a reference to the data as specified in the docs.
+     */
+    g_type = g_flags_register_static (full_name, g_flags_values);
+    if (g_type == G_TYPE_INVALID) {
+        for (i = 0; i < n_values; i++) {
+            GFlagsValue *flags_value = &g_flags_values[i];
+
+            /* Only free value_name if it is different from value_nick to avoid
+             * a double free. The pointer might have been is re-used in the case
+             * c_identifier was NULL in the above loop.
+             */
+            if (flags_value->value_name != flags_value->value_nick)
+                g_free ((gchar *) flags_value->value_name);
+            g_free ((gchar *) flags_value->value_nick);
+        }
+
+        PyErr_Format (PyExc_RuntimeError, "Unable to register flags '%s'", full_name);
+
+        g_free (g_flags_values);
+        g_free (full_name);
+        return NULL;
+    }
+
+    g_free (full_name);
+    return pyg_flags_add (NULL, type_name, NULL, g_type);
+}
+
+static void
+initialize_interface (GTypeInterface *iface, PyTypeObject *pytype)
+{
+    /* pygobject prints a warning if interface_init is NULL */
+}
+
+static PyObject *
+_wrap_pyg_register_interface_info (PyObject *self, PyObject *args)
+{
+    PyObject *py_g_type;
+    GType g_type;
+    GInterfaceInfo *info;
+
+    if (!PyArg_ParseTuple (args, "O!:register_interface_info",
+                           &PyGTypeWrapper_Type, &py_g_type)) {
+        return NULL;
+    }
+
+    g_type = pyg_type_from_object (py_g_type);
+    if (!g_type_is_a (g_type, G_TYPE_INTERFACE)) {
+        PyErr_SetString (PyExc_TypeError, "must be an interface");
+        return NULL;
+    }
+
+    info = g_new0 (GInterfaceInfo, 1);
+    info->interface_init = (GInterfaceInitFunc) initialize_interface;
+
+    pyg_register_interface_info (g_type, info);
+
+    Py_RETURN_NONE;
+}
+
+static void
+find_vfunc_info (GIBaseInfo *vfunc_info,
+                 GType implementor_gtype,
+                 gpointer *implementor_class_ret,
+                 gpointer *implementor_vtable_ret,
+                 GIFieldInfo **field_info_ret)
+{
+    GType ancestor_g_type = 0;
+    int length, i;
+    GIBaseInfo *ancestor_info;
+    GIStructInfo *struct_info;
+    gpointer implementor_class = NULL;
+    gboolean is_interface = FALSE;
+
+    ancestor_info = g_base_info_get_container (vfunc_info);
+    is_interface = g_base_info_get_type (ancestor_info) == GI_INFO_TYPE_INTERFACE;
+
+    ancestor_g_type = g_registered_type_info_get_g_type (
+                          (GIRegisteredTypeInfo *) ancestor_info);
+    implementor_class = g_type_class_ref (implementor_gtype);
+    if (is_interface) {
+        GTypeInstance *implementor_iface_class;
+        implementor_iface_class = g_type_interface_peek (implementor_class,
+                                                         ancestor_g_type);
+        if (implementor_iface_class == NULL) {
+            g_type_class_unref (implementor_class);
+            PyErr_Format (PyExc_RuntimeError,
+                          "Couldn't find GType of implementor of interface %s. "
+                          "Forgot to set __gtype_name__?",
+                          g_type_name (ancestor_g_type));
+            return;
+        }
+
+        *implementor_vtable_ret = implementor_iface_class;
+
+        struct_info = g_interface_info_get_iface_struct ( (GIInterfaceInfo*) ancestor_info);
+    } else {
+        struct_info = g_object_info_get_class_struct ( (GIObjectInfo*) ancestor_info);
+        *implementor_vtable_ret = implementor_class;
+    }
+
+    *implementor_class_ret = implementor_class;
+
+    length = g_struct_info_get_n_fields (struct_info);
+    for (i = 0; i < length; i++) {
+        GIFieldInfo *field_info;
+        GITypeInfo *type_info;
+
+        field_info = g_struct_info_get_field (struct_info, i);
+
+        if (strcmp (g_base_info_get_name ( (GIBaseInfo*) field_info),
+                    g_base_info_get_name ( (GIBaseInfo*) vfunc_info)) != 0) {
+            g_base_info_unref (field_info);
+            continue;
+        }
+
+        type_info = g_field_info_get_type (field_info);
+        if (g_type_info_get_tag (type_info) == GI_TYPE_TAG_INTERFACE) {
+            g_base_info_unref (type_info);
+            *field_info_ret = field_info;
+            break;
+        }
+
+        g_base_info_unref (type_info);
+        g_base_info_unref (field_info);
+    }
+
+    g_base_info_unref (struct_info);
+}
+
+static PyObject *
+_wrap_pyg_hook_up_vfunc_implementation (PyObject *self, PyObject *args)
+{
+    PyGIBaseInfo *py_info;
+    PyObject *py_type;
+    PyObject *py_function;
+    GType implementor_gtype = 0;
+    gpointer implementor_class = NULL;
+    gpointer implementor_vtable = NULL;
+    GIFieldInfo *field_info = NULL;
+    gpointer *method_ptr = NULL;
+    PyGICClosure *closure = NULL;
+    PyGIClosureCache *cache = NULL;
+
+    if (!PyArg_ParseTuple (args, "O!O!O:hook_up_vfunc_implementation",
+                           &PyGIBaseInfo_Type, &py_info,
+                           &PyGTypeWrapper_Type, &py_type,
+                           &py_function))
+        return NULL;
+
+    implementor_gtype = pyg_type_from_object (py_type);
+    g_assert (G_TYPE_IS_CLASSED (implementor_gtype));
+
+    find_vfunc_info (py_info->info, implementor_gtype, &implementor_class, &implementor_vtable, &field_info);
+    if (field_info != NULL) {
+        GITypeInfo *type_info;
+        GIBaseInfo *interface_info;
+        GICallbackInfo *callback_info;
+        gint offset;
+
+        type_info = g_field_info_get_type (field_info);
+
+        interface_info = g_type_info_get_interface (type_info);
+        g_assert (g_base_info_get_type (interface_info) == GI_INFO_TYPE_CALLBACK);
+
+        callback_info = (GICallbackInfo*) interface_info;
+        offset = g_field_info_get_offset (field_info);
+        method_ptr = G_STRUCT_MEMBER_P (implementor_vtable, offset);
+
+        cache = pygi_closure_cache_new (callback_info);
+        closure = _pygi_make_native_closure ( (GICallableInfo*) callback_info, cache,
+                                              GI_SCOPE_TYPE_NOTIFIED, py_function, NULL);
+
+        *method_ptr = closure->closure;
+
+        g_base_info_unref (interface_info);
+        g_base_info_unref (type_info);
+        g_base_info_unref (field_info);
+    }
+    g_type_class_unref (implementor_class);
+
+    Py_RETURN_NONE;
+}
+
+#if 0
+/* Not used, left around for future reference */
+static PyObject *
+_wrap_pyg_has_vfunc_implementation (PyObject *self, PyObject *args)
+{
+    PyGIBaseInfo *py_info;
+    PyObject *py_type;
+    PyObject *py_ret;
+    gpointer implementor_class = NULL;
+    gpointer implementor_vtable = NULL;
+    GType implementor_gtype = 0;
+    GIFieldInfo *field_info = NULL;
+
+    if (!PyArg_ParseTuple (args, "O!O!:has_vfunc_implementation",
+                           &PyGIBaseInfo_Type, &py_info,
+                           &PyGTypeWrapper_Type, &py_type))
+        return NULL;
+
+    implementor_gtype = pyg_type_from_object (py_type);
+    g_assert (G_TYPE_IS_CLASSED (implementor_gtype));
+
+    py_ret = Py_False;
+    find_vfunc_info (py_info->info, implementor_gtype, &implementor_class, &implementor_vtable, &field_info);
+    if (field_info != NULL) {
+        gpointer *method_ptr;
+        gint offset;
+
+        offset = g_field_info_get_offset (field_info);
+        method_ptr = G_STRUCT_MEMBER_P (implementor_vtable, offset);
+        if (*method_ptr != NULL) {
+            py_ret = Py_True;
+        }
+
+        g_base_info_unref (field_info);
+    }
+    g_type_class_unref (implementor_class);
+
+    Py_INCREF(py_ret);
+    return py_ret;
+}
+#endif
+
+static PyObject *
+_wrap_pyg_variant_type_from_string (PyObject *self, PyObject *args)
+{
+    char *type_string;
+    PyObject *py_type;
+    PyObject *py_variant = NULL;
+
+    if (!PyArg_ParseTuple (args, "s:variant_type_from_string",
+                           &type_string)) {
+        return NULL;
+    }
+
+    py_type = pygi_type_import_by_name ("GLib", "VariantType");
+
+    py_variant = pygi_boxed_new ( (PyTypeObject *) py_type, type_string, FALSE, 0);
+
+    return py_variant;
+}
+
+#define CHUNK_SIZE 8192
+
+static PyObject*
+pyg_channel_read(PyObject* self, PyObject *args, PyObject *kwargs)
+{
+    int max_count = -1;
+    PyObject *py_iochannel, *ret_obj = NULL;
+    gsize total_read = 0;
+    GError* error = NULL;
+    GIOStatus status = G_IO_STATUS_NORMAL;
+    GIOChannel *iochannel = NULL;
+
+    if (!PyArg_ParseTuple (args, "Oi:pyg_channel_read", &py_iochannel, &max_count)) {
+        return NULL;
+    }
+    if (!pyg_boxed_check (py_iochannel, G_TYPE_IO_CHANNEL)) {
+        PyErr_SetString(PyExc_TypeError, "first argument is not a GLib.IOChannel");
+        return NULL;
+    }
+       
+    if (max_count == 0)
+        return PYGLIB_PyBytes_FromString("");
+
+    iochannel = pyg_boxed_get (py_iochannel, GIOChannel);
+
+    while (status == G_IO_STATUS_NORMAL
+          && (max_count == -1 || total_read < (gsize)max_count)) {
+       gsize single_read;
+       char* buf;
+       gsize buf_size;
+       
+       if (max_count == -1) 
+           buf_size = CHUNK_SIZE;
+       else {
+           buf_size = max_count - total_read;
+           if (buf_size > CHUNK_SIZE)
+               buf_size = CHUNK_SIZE;
+        }
+       
+       if ( ret_obj == NULL ) {
+           ret_obj = PYGLIB_PyBytes_FromStringAndSize((char *)NULL, buf_size);
+           if (ret_obj == NULL)
+               goto failure;
+       }
+       else if (buf_size + total_read > (gsize)PYGLIB_PyBytes_Size(ret_obj)) {
+           if (PYGLIB_PyBytes_Resize(&ret_obj, buf_size + total_read) == -1)
+               goto failure;
+       }
+       
+        buf = PYGLIB_PyBytes_AsString(ret_obj) + total_read;
+
+        Py_BEGIN_ALLOW_THREADS;
+        status = g_io_channel_read_chars (iochannel, buf, buf_size, &single_read, &error);
+        Py_END_ALLOW_THREADS;
+
+        if (pygi_error_check (&error))
+           goto failure;
+       
+       total_read += single_read;
+    }
+       
+    if ( total_read != (gsize)PYGLIB_PyBytes_Size(ret_obj) ) {
+       if (PYGLIB_PyBytes_Resize(&ret_obj, total_read) == -1)
+           goto failure;
+    }
+
+    return ret_obj;
+
+  failure:
+    Py_XDECREF(ret_obj);
+    return NULL;
+}
+
+static gboolean
+marshal_emission_hook(GSignalInvocationHint *ihint,
+                     guint n_param_values,
+                     const GValue *param_values,
+                     gpointer user_data)
+{
+    PyGILState_STATE state;
+    gboolean retval = FALSE;
+    PyObject *func, *args;
+    PyObject *retobj;
+    PyObject *params;
+    guint i;
+
+    state = PyGILState_Ensure();
+
+    /* construct Python tuple for the parameter values */
+    params = PyTuple_New(n_param_values);
+
+    for (i = 0; i < n_param_values; i++) {
+       PyObject *item = pyg_value_as_pyobject(&param_values[i], FALSE);
+
+       /* error condition */
+       if (!item) {
+           goto out;
+       }
+       PyTuple_SetItem(params, i, item);
+    }
+
+    args = (PyObject *)user_data;
+    func = PyTuple_GetItem(args, 0);
+    args = PySequence_Concat(params, PyTuple_GetItem(args, 1));
+    Py_DECREF(params);
+
+    /* params passed to function may have extra arguments */
+
+    retobj = PyObject_CallObject(func, args);
+    Py_DECREF(args);
+    if (retobj == NULL) {
+        PyErr_Print();
+    }
+
+    retval = (retobj == Py_True ? TRUE : FALSE);
+    Py_XDECREF(retobj);
+out:
+    PyGILState_Release(state);
+    return retval;
+}
+
+/**
+ * pyg_destroy_notify:
+ * @user_data: a PyObject pointer.
+ *
+ * A function that can be used as a GDestroyNotify callback that will
+ * call Py_DECREF on the data.
+ */
+static void
+pyg_destroy_notify(gpointer user_data)
+{
+    PyObject *obj = (PyObject *)user_data;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+    Py_DECREF(obj);
+    PyGILState_Release(state);
+}
+
+static PyObject *
+pyg_add_emission_hook(PyGObject *self, PyObject *args)
+{
+    PyObject *first, *callback, *extra_args, *data, *repr;
+    gchar *name;
+    gulong hook_id;
+    guint sigid;
+    Py_ssize_t len;
+    GQuark detail = 0;
+    GType gtype;
+    PyObject *pygtype;
+
+    len = PyTuple_Size(args);
+    if (len < 3) {
+       PyErr_SetString(PyExc_TypeError,
+                       "gobject.add_emission_hook requires at least 3 arguments");
+       return NULL;
+    }
+    first = PySequence_GetSlice(args, 0, 3);
+    if (!PyArg_ParseTuple(first, "OsO:add_emission_hook",
+                         &pygtype, &name, &callback)) {
+       Py_DECREF(first);
+       return NULL;
+    }
+    Py_DECREF(first);
+
+    if ((gtype = pyg_type_from_object(pygtype)) == 0) {
+       return NULL;
+    }
+    if (!PyCallable_Check(callback)) {
+       PyErr_SetString(PyExc_TypeError, "third argument must be callable");
+       return NULL;
+    }
+
+    if (!g_signal_parse_name(name, gtype, &sigid, &detail, TRUE)) {
+       repr = PyObject_Repr((PyObject*)self);
+       PyErr_Format(PyExc_TypeError, "%s: unknown signal name: %s",
+                       PYGLIB_PyUnicode_AsString(repr),
+                    name);
+       Py_DECREF(repr);
+       return NULL;
+    }
+    extra_args = PySequence_GetSlice(args, 3, len);
+    if (extra_args == NULL)
+       return NULL;
+
+    data = Py_BuildValue("(ON)", callback, extra_args);
+    if (data == NULL)
+      return NULL;
+
+    hook_id = g_signal_add_emission_hook(sigid, detail,
+                                        marshal_emission_hook,
+                                        data,
+                                        (GDestroyNotify)pyg_destroy_notify);
+
+    return pygi_gulong_to_py (hook_id);
+}
+
+static PyObject *
+pyg_signal_new(PyObject *self, PyObject *args)
+{
+    gchar *signal_name;
+    PyObject *py_type;
+    GSignalFlags signal_flags;
+    GType return_type;
+    PyObject *py_return_type, *py_param_types;
+
+    GType instance_type = 0;
+    Py_ssize_t py_n_params;
+    guint n_params, i;
+    GType *param_types;
+
+    guint signal_id;
+
+    if (!PyArg_ParseTuple(args, "sOiOO:gobject.signal_new", &signal_name,
+                         &py_type, &signal_flags, &py_return_type,
+                         &py_param_types))
+       return NULL;
+
+    instance_type = pyg_type_from_object(py_type);
+    if (!instance_type)
+       return NULL;
+    if (!(G_TYPE_IS_INSTANTIATABLE(instance_type) || G_TYPE_IS_INTERFACE(instance_type))) {
+       PyErr_SetString(PyExc_TypeError,
+                       "argument 2 must be an object type or interface type");
+       return NULL;
+    }
+
+    return_type = pyg_type_from_object(py_return_type);
+    if (!return_type)
+       return NULL;
+
+    if (!PySequence_Check(py_param_types)) {
+       PyErr_SetString(PyExc_TypeError,
+                       "argument 5 must be a sequence of GType codes");
+       return NULL;
+    }
+
+    py_n_params = PySequence_Length(py_param_types);
+    if (py_n_params < 0)
+        return FALSE;
+
+    if (!pygi_guint_from_pyssize (py_n_params, &n_params))
+        return FALSE;
+
+    param_types = g_new(GType, n_params);
+    for (i = 0; i < n_params; i++) {
+       PyObject *item = PySequence_GetItem(py_param_types, i);
+
+       param_types[i] = pyg_type_from_object(item);
+       if (param_types[i] == 0) {
+           PyErr_Clear();
+           Py_DECREF(item);
+           PyErr_SetString(PyExc_TypeError,
+                           "argument 5 must be a sequence of GType codes");
+           g_free(param_types);
+           return NULL;
+       }
+       Py_DECREF(item);
+    }
+
+    signal_id = g_signal_newv(signal_name, instance_type, signal_flags,
+                             pyg_signal_class_closure_get(),
+                             (GSignalAccumulator)0, NULL,
+                             (GSignalCMarshaller)0,
+                             return_type, n_params, param_types);
+    g_free(param_types);
+    if (signal_id != 0)
+       return pygi_guint_to_py (signal_id);
+    PyErr_SetString(PyExc_RuntimeError, "could not create signal");
+    return NULL;
+}
+
+static PyObject *
+pyg_object_class_list_properties (PyObject *self, PyObject *args)
+{
+    GParamSpec **specs;
+    PyObject *py_itype, *list;
+    GType itype;
+    GObjectClass *class = NULL;
+    gpointer iface = NULL;
+    guint nprops;
+    guint i;
+
+    if (!PyArg_ParseTuple(args, "O:gobject.list_properties",
+                         &py_itype))
+       return NULL;
+    if ((itype = pyg_type_from_object(py_itype)) == 0)
+       return NULL;
+
+    if (G_TYPE_IS_INTERFACE(itype)) {
+        iface = g_type_default_interface_ref(itype);
+        if (!iface) {
+            PyErr_SetString(PyExc_RuntimeError,
+                            "could not get a reference to interface type");
+            return NULL;
+        }
+        specs = g_object_interface_list_properties(iface, &nprops);
+    } else if (g_type_is_a(itype, G_TYPE_OBJECT)) {
+        class = g_type_class_ref(itype);
+        if (!class) {
+            PyErr_SetString(PyExc_RuntimeError,
+                            "could not get a reference to type class");
+            return NULL;
+        }
+        specs = g_object_class_list_properties(class, &nprops);
+    } else {
+       PyErr_SetString(PyExc_TypeError,
+                        "type must be derived from GObject or an interface");
+       return NULL;
+    }
+
+    list = PyTuple_New(nprops);
+    if (list == NULL) {
+       g_free(specs);
+       g_type_class_unref(class);
+       return NULL;
+    }
+    for (i = 0; i < nprops; i++) {
+       PyTuple_SetItem(list, i, pyg_param_spec_new(specs[i]));
+    }
+    g_free(specs);
+    if (class)
+        g_type_class_unref(class);
+    else
+        g_type_default_interface_unref(iface);
+
+    return list;
+}
+
+static PyObject *
+pyg__install_metaclass(PyObject *dummy, PyTypeObject *metaclass)
+{
+    Py_INCREF(metaclass);
+    PyGObject_MetaType = metaclass;
+    Py_INCREF(metaclass);
+
+    Py_TYPE(&PyGObject_Type) = metaclass;
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+_wrap_pyig_pyos_getsig (PyObject *self, PyObject *args)
+{
+    int sig_num;
+
+    if (!PyArg_ParseTuple (args, "i:pyos_getsig", &sig_num))
+        return NULL;
+
+    return PyLong_FromVoidPtr ((void *)(PyOS_getsig (sig_num)));
+}
+
+static PyObject *
+_wrap_pygobject_new_full (PyObject *self, PyObject *args)
+{
+    PyObject *ptr_value, *long_value;
+    PyObject *steal;
+    GObject *obj;
+
+    if (!PyArg_ParseTuple (args, "OO", &ptr_value, &steal))
+        return NULL;
+
+    long_value = PyNumber_Long (ptr_value);
+    if (!long_value) {
+        PyErr_SetString (PyExc_TypeError, "first argument must be an integer");
+        return NULL;
+    }
+    obj = PyLong_AsVoidPtr (long_value);
+    Py_DECREF (long_value);
+
+    if (!G_IS_OBJECT (obj)) {
+        PyErr_SetString (PyExc_TypeError, "pointer is not a GObject");
+        return NULL;
+    }
+
+    return pygobject_new_full (obj, PyObject_IsTrue (steal), NULL);
+}
+
+static PyMethodDef _gi_functions[] = {
+    { "pygobject_new_full", (PyCFunction) _wrap_pygobject_new_full, METH_VARARGS },
+    { "enum_add", (PyCFunction) _wrap_pyg_enum_add, METH_VARARGS | METH_KEYWORDS },
+    { "enum_register_new_gtype_and_add", (PyCFunction) _wrap_pyg_enum_register_new_gtype_and_add, METH_VARARGS | METH_KEYWORDS },
+    { "flags_add", (PyCFunction) _wrap_pyg_flags_add, METH_VARARGS | METH_KEYWORDS },
+    { "flags_register_new_gtype_and_add", (PyCFunction) _wrap_pyg_flags_register_new_gtype_and_add, METH_VARARGS | METH_KEYWORDS },
+
+    { "register_interface_info", (PyCFunction) _wrap_pyg_register_interface_info, METH_VARARGS },
+    { "hook_up_vfunc_implementation", (PyCFunction) _wrap_pyg_hook_up_vfunc_implementation, METH_VARARGS },
+    { "variant_type_from_string", (PyCFunction) _wrap_pyg_variant_type_from_string, METH_VARARGS },
+    { "source_new", (PyCFunction) pygi_source_new, METH_NOARGS },
+    { "pyos_getsig", (PyCFunction) _wrap_pyig_pyos_getsig, METH_VARARGS },
+    { "source_set_callback", (PyCFunction) pygi_source_set_callback, METH_VARARGS },
+    { "io_channel_read", (PyCFunction) pyg_channel_read, METH_VARARGS },
+    { "require_foreign", (PyCFunction) pygi_require_foreign, METH_VARARGS | METH_KEYWORDS },
+    { "spawn_async",
+      (PyCFunction)pyglib_spawn_async, METH_VARARGS|METH_KEYWORDS,
+      "spawn_async(argv, envp=None, working_directory=None,\n"
+      "            flags=0, child_setup=None, user_data=None,\n"
+      "            standard_input=None, standard_output=None,\n"
+      "            standard_error=None) -> (pid, stdin, stdout, stderr)\n"
+      "\n"
+      "Execute a child program asynchronously within a glib.MainLoop()\n"
+      "See the reference manual for a complete reference.\n" },
+    { "type_register", _wrap_pyg_type_register, METH_VARARGS },
+    { "signal_new", pyg_signal_new, METH_VARARGS },
+    { "list_properties",
+      pyg_object_class_list_properties, METH_VARARGS },
+    { "new",
+      (PyCFunction)pyg_object_new, METH_VARARGS|METH_KEYWORDS },
+    { "add_emission_hook",
+      (PyCFunction)pyg_add_emission_hook, METH_VARARGS },
+    { "_install_metaclass",
+      (PyCFunction)pyg__install_metaclass, METH_O },
+    { "_gvalue_get",
+      (PyCFunction)pyg__gvalue_get, METH_O },
+    { "_gvalue_set",
+      (PyCFunction)pyg__gvalue_set, METH_VARARGS },
+    { NULL, NULL, 0 }
+};
+
+static struct PyGI_API CAPI = {
+  pygi_register_foreign_struct,
+};
+
+struct _PyGObject_Functions pygobject_api_functions = {
+  pygobject_register_class,
+  pygobject_register_wrapper,
+  pygobject_lookup_class,
+  pygobject_new,
+
+  pyg_closure_new,
+  pygobject_watch_closure,
+  pyg_destroy_notify,
+
+  pyg_type_from_object,
+  pyg_type_wrapper_new,
+  pyg_enum_get_value,
+  pyg_flags_get_value,
+  pyg_register_gtype_custom,
+  pyg_value_from_pyobject,
+  pyg_value_as_pyobject,
+
+  pyg_register_interface,
+
+  &PyGBoxed_Type,
+  pygi_register_gboxed,
+  pygi_gboxed_new,
+
+  &PyGPointer_Type,
+  pyg_register_pointer,
+  pyg_pointer_new,
+
+  pyg_enum_add_constants,
+  pyg_flags_add_constants,
+
+  pyg_constant_strip_prefix,
+
+  pygi_error_check,
+
+  _pyg_set_thread_block_funcs,
+  (PyGThreadBlockFunc)0, /* block_threads */
+  (PyGThreadBlockFunc)0, /* unblock_threads */
+
+  &PyGParamSpec_Type,
+  pyg_param_spec_new,
+  pyg_param_spec_from_object,
+
+  pyg_pyobj_to_unichar_conv,
+  pyg_parse_constructor_args,
+  pyg_param_gvalue_as_pyobject,
+  pyg_param_gvalue_from_pyobject,
+
+  &PyGEnum_Type,
+  pyg_enum_add,
+  pyg_enum_from_gtype,
+
+  &PyGFlags_Type,
+  pyg_flags_add,
+  pyg_flags_from_gtype,
+
+  TRUE, /* threads_enabled */
+
+  pygobject_enable_threads,
+  pygobject_gil_state_ensure,
+  pygobject_gil_state_release,
+  pyg_register_class_init,
+  pyg_register_interface_info,
+
+  pyg_closure_set_exception_handler,
+
+  add_warning_redirection,
+  disable_warning_redirections,
+
+  NULL, /* previously type_register_custom */
+
+  pygi_gerror_exception_check,
+
+  pyg_option_group_new,
+  pyg_type_from_object_strict,
+
+  pygobject_new_full,
+  &PyGObject_Type,
+
+  pyg_value_from_pyobject_with_error
+};
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+static int
+pygi_register_api(PyObject *d)
+{
+    PyObject *api;
+
+    api = PyCapsule_New (&pygobject_api_functions, "gobject._PyGObject_API", NULL);
+    if (api == NULL)
+        return -1;
+    PyDict_SetItemString(d, "_PyGObject_API", api);
+    Py_DECREF(api);
+    return 0;
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+static int
+pygi_register_constants(PyObject *m)
+{
+    /* PyFloat_ return a new ref, and add object takes the ref */
+    PyModule_AddObject(m,       "G_MINFLOAT", pygi_gfloat_to_py (G_MINFLOAT));
+    PyModule_AddObject(m,       "G_MAXFLOAT", pygi_gfloat_to_py (G_MAXFLOAT));
+    PyModule_AddObject(m,       "G_MINDOUBLE", pygi_gdouble_to_py (G_MINDOUBLE));
+    PyModule_AddObject(m,       "G_MAXDOUBLE", pygi_gdouble_to_py (G_MAXDOUBLE));
+    PyModule_AddIntConstant(m,  "G_MINSHORT", G_MINSHORT);
+    PyModule_AddIntConstant(m,  "G_MAXSHORT", G_MAXSHORT);
+    PyModule_AddIntConstant(m,  "G_MAXUSHORT", G_MAXUSHORT);
+    PyModule_AddIntConstant(m,  "G_MININT", G_MININT);
+    PyModule_AddIntConstant(m,  "G_MAXINT", G_MAXINT);
+    PyModule_AddObject(m,       "G_MAXUINT", pygi_guint_to_py (G_MAXUINT));
+    PyModule_AddObject(m,       "G_MINLONG", pygi_glong_to_py (G_MINLONG));
+    PyModule_AddObject(m,       "G_MAXLONG", pygi_glong_to_py (G_MAXLONG));
+    PyModule_AddObject(m,       "G_MAXULONG", pygi_gulong_to_py (G_MAXULONG));
+    PyModule_AddObject(m,       "G_MAXSIZE", pygi_gsize_to_py (G_MAXSIZE));
+    PyModule_AddObject(m,       "G_MAXSSIZE", pygi_gssize_to_py (G_MAXSSIZE));
+    PyModule_AddObject(m,       "G_MINSSIZE", pygi_gssize_to_py (G_MINSSIZE));
+    PyModule_AddObject(m,       "G_MINOFFSET", pygi_gint64_to_py (G_MINOFFSET));
+    PyModule_AddObject(m,       "G_MAXOFFSET", pygi_gint64_to_py (G_MAXOFFSET));
+
+    PyModule_AddIntConstant(m, "SIGNAL_RUN_FIRST", G_SIGNAL_RUN_FIRST);
+    PyModule_AddIntConstant(m, "PARAM_READWRITE", G_PARAM_READWRITE);
+
+    /* The rest of the types are set in __init__.py */
+    PyModule_AddObject(m, "TYPE_INVALID", pyg_type_wrapper_new(G_TYPE_INVALID));
+    PyModule_AddObject(m, "TYPE_GSTRING", pyg_type_wrapper_new(G_TYPE_GSTRING));
+
+    return 0;
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+static int
+pygi_register_version_tuples(PyObject *d)
+{
+    PyObject *tuple;
+
+    /* pygobject version */
+    tuple = Py_BuildValue ("(iii)",
+                           PYGOBJECT_MAJOR_VERSION,
+                           PYGOBJECT_MINOR_VERSION,
+                           PYGOBJECT_MICRO_VERSION);
+    PyDict_SetItemString(d, "pygobject_version", tuple);
+    Py_DECREF (tuple);
+    return 0;
+}
+
+PYGLIB_MODULE_START(_gi, "_gi")
+{
+    PyObject *api;
+    PyObject *module_dict = PyModule_GetDict (module);
+
+    /* Always enable Python threads since we cannot predict which GI repositories
+     * might accept Python callbacks run within non-Python threads or might trigger
+     * toggle ref notifications.
+     * See: https://bugzilla.gnome.org/show_bug.cgi?id=709223
+     */
+    PyEval_InitThreads ();
+
+    PyModule_AddStringConstant(module, "__package__", "gi._gi");
+
+    if (pygi_foreign_init () < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_error_register_types (module) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_repository_register_types (module) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_info_register_types (module) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_type_register_types (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_pointer_register_types (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_struct_register_types (module) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_gboxed_register_types (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_boxed_register_types (module) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_ccallback_register_types (module) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_resulttuple_register_types (module) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+
+    if (pygi_spawn_register_types (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_option_context_register_types (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_option_group_register_types (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+
+    if (pygi_register_api (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_register_constants (module) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_register_version_tuples (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_register_warnings (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pyi_object_register_types (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_interface_register_types (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_paramspec_register_types (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_enum_register_types (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+    if (pygi_flags_register_types (module_dict) < 0)
+        return PYGLIB_MODULE_ERROR_RETURN;
+
+    PyGIWarning = PyErr_NewException ("gi.PyGIWarning", PyExc_Warning, NULL);
+    if (PyGIWarning == NULL)
+        return PYGLIB_MODULE_ERROR_RETURN;
+
+    /* Use RuntimeWarning as the base class of PyGIDeprecationWarning
+     * for unstable (odd minor version) and use DeprecationWarning for
+     * stable (even minor version). This is so PyGObject deprecations
+     * behave the same as regular Python deprecations in stable releases.
+     */
+#if PYGOBJECT_MINOR_VERSION % 2
+    PyGIDeprecationWarning = PyErr_NewException("gi.PyGIDeprecationWarning",
+                                                PyExc_RuntimeWarning, NULL);
+#else
+    PyGIDeprecationWarning = PyErr_NewException("gi.PyGIDeprecationWarning",
+                                                PyExc_DeprecationWarning, NULL);
+#endif
+
+    /* Place holder object used to fill in "from Python" argument lists
+     * for values not supplied by the caller but support a GI default.
+     */
+    _PyGIDefaultArgPlaceholder = PyList_New(0);
+
+    Py_INCREF (PyGIWarning);
+    PyModule_AddObject (module, "PyGIWarning", PyGIWarning);
+
+    Py_INCREF(PyGIDeprecationWarning);
+    PyModule_AddObject(module, "PyGIDeprecationWarning", PyGIDeprecationWarning);
+
+    api = PyCapsule_New ( (void *) &CAPI, "gi._API", NULL);
+    if (api == NULL) {
+        return PYGLIB_MODULE_ERROR_RETURN;
+    }
+    PyModule_AddObject (module, "_API", api);
+}
+PYGLIB_MODULE_END
diff --git a/gi/gimodule.h b/gi/gimodule.h
new file mode 100644 (file)
index 0000000..7917ef6
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef _PYGOBJECT_GIMODULE_H_
+#define _PYGOBJECT_GIMODULE_H_
+
+#include "pygobject-internal.h"
+
+int           pygobject_constructv (PyGObject   *self,
+                                    guint        n_parameters,
+                                    GParameter  *parameters);
+
+#endif /*_PYGOBJECT_GIMODULE_H_*/
diff --git a/gi/importer.py b/gi/importer.py
new file mode 100644 (file)
index 0000000..e14d47b
--- /dev/null
@@ -0,0 +1,152 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+#               2015 Christoph Reiter
+#
+#   importer.py: dynamic importer for introspected libraries.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from __future__ import absolute_import
+import sys
+import warnings
+import importlib
+from contextlib import contextmanager
+
+import gi
+from ._gi import Repository, RepositoryError
+from ._gi import PyGIWarning
+from .module import get_introspection_module
+from .overrides import load_overrides
+
+
+repository = Repository.get_default()
+
+# only for backwards compatibility
+modules = {}
+
+
+@contextmanager
+def _check_require_version(namespace, stacklevel):
+    """A context manager which tries to give helpful warnings
+    about missing gi.require_version() which could potentially
+    break code if only an older version than expected is installed
+    or a new version gets introduced.
+
+    ::
+
+        with _check_require_version("Gtk", stacklevel):
+            load_namespace_and_overrides()
+    """
+
+    was_loaded = repository.is_registered(namespace)
+
+    yield
+
+    if was_loaded:
+        # it was loaded before by another import which depended on this
+        # namespace or by C code like libpeas
+        return
+
+    if namespace in ("GLib", "GObject", "Gio"):
+        # part of glib (we have bigger problems if versions change there)
+        return
+
+    if gi.get_required_version(namespace) is not None:
+        # the version was forced using require_version()
+        return
+
+    version = repository.get_version(namespace)
+    warnings.warn(
+        "%(namespace)s was imported without specifying a version first. "
+        "Use gi.require_version('%(namespace)s', '%(version)s') before "
+        "import to ensure that the right version gets loaded."
+        % {"namespace": namespace, "version": version},
+        PyGIWarning, stacklevel=stacklevel)
+
+
+def get_import_stacklevel(import_hook):
+    """Returns the stacklevel value for warnings.warn() for when the warning
+    gets emitted by an imported module, but the warning should point at the
+    code doing the import.
+
+    Pass import_hook=True if the warning gets generated by an import hook
+    (warn() gets called in load_module(), see PEP302)
+    """
+
+    py_version = sys.version_info[:2]
+    if py_version <= (3, 2):
+        # 2.7 included
+        return 4 if import_hook else 2
+    elif py_version == (3, 3):
+        return 8 if import_hook else 10
+    elif py_version == (3, 4):
+        return 10 if import_hook else 8
+    else:
+        # fixed again in 3.5+, see https://bugs.python.org/issue24305
+        return 4 if import_hook else 2
+
+
+class DynamicImporter(object):
+
+    # Note: see PEP302 for the Importer Protocol implemented below.
+
+    def __init__(self, path):
+        self.path = path
+
+    def find_module(self, fullname, path=None):
+        if not fullname.startswith(self.path):
+            return
+
+        path, namespace = fullname.rsplit('.', 1)
+        if path != self.path:
+            return
+
+        return self
+
+    def load_module(self, fullname):
+        if fullname in sys.modules:
+            return sys.modules[fullname]
+
+        path, namespace = fullname.rsplit('.', 1)
+
+        # is_registered() is faster than enumerate_versions() and
+        # in the common case of a namespace getting loaded before its
+        # dependencies, is_registered() returns True for all dependencies.
+        if not repository.is_registered(namespace) and not \
+                repository.enumerate_versions(namespace):
+            raise ImportError('cannot import name %s, '
+                              'introspection typelib not found' % namespace)
+
+        stacklevel = get_import_stacklevel(import_hook=True)
+        with _check_require_version(namespace, stacklevel=stacklevel):
+            try:
+                introspection_module = get_introspection_module(namespace)
+            except RepositoryError as e:
+                raise ImportError(e)
+            # Import all dependencies first so their init functions
+            # (gdk_init, ..) in overrides get called.
+            # https://bugzilla.gnome.org/show_bug.cgi?id=656314
+            for dep in repository.get_immediate_dependencies(namespace):
+                importlib.import_module('gi.repository.' + dep.split("-")[0])
+            dynamic_module = load_overrides(introspection_module)
+
+        dynamic_module.__file__ = '<%s>' % fullname
+        dynamic_module.__loader__ = self
+        sys.modules[fullname] = dynamic_module
+
+        return dynamic_module
diff --git a/gi/meson.build b/gi/meson.build
new file mode 100644 (file)
index 0000000..c1afd68
--- /dev/null
@@ -0,0 +1,92 @@
+sources = [
+  'pygboxed.c',
+  'pygenum.c',
+  'pygflags.c',
+  'pyginterface.c',
+  'pygobject-object.c',
+  'pygparamspec.c',
+  'pygpointer.c',
+  'pygoptioncontext.c',
+  'pygoptiongroup.c',
+  'pygspawn.c',
+  'gimodule.c',
+  'pygi-repository.c',
+  'pygi-info.c',
+  'pygi-foreign.c',
+  'pygi-struct.c',
+  'pygi-source.c',
+  'pygi-argument.c',
+  'pygi-resulttuple.c',
+  'pygi-type.c',
+  'pygi-boxed.c',
+  'pygi-closure.c',
+  'pygi-ccallback.c',
+  'pygi-util.c',
+  'pygi-property.c',
+  'pygi-signal-closure.c',
+  'pygi-invoke.c',
+  'pygi-cache.c',
+  'pygi-marshal-cleanup.c',
+  'pygi-basictype.c',
+  'pygi-list.c',
+  'pygi-array.c',
+  'pygi-error.c',
+  'pygi-object.c',
+  'pygi-value.c',
+  'pygi-enum-marshal.c',
+  'pygi-struct-marshal.c',
+  'pygi-hashtable.c']
+
+headers = [
+  'pygobject.h'
+]
+
+install_headers(headers, subdir : 'pygobject-@0@'.format(platform_version))
+
+python_sources = [
+  '_compat.py',
+  '_constants.py',
+  'docstring.py',
+  '_error.py',
+  '_gtktemplate.py',
+  'importer.py',
+  '__init__.py',
+  'module.py',
+  '_option.py',
+  '_ossighelper.py',
+  '_propertyhelper.py',
+  'pygtkcompat.py',
+  '_signalhelper.py',
+  'types.py',
+]
+
+python.install_sources(python_sources,
+  pure : false,
+  subdir : 'gi'
+)
+
+# https://github.com/mesonbuild/meson/issues/4117
+if host_machine.system() == 'windows'
+  python_ext_dep = python_dep
+else
+  python_ext_dep = python_dep.partial_dependency(compile_args: true)
+endif
+
+giext = python.extension_module('_gi', sources,
+  dependencies : [python_ext_dep, glib_dep, gi_dep, ffi_dep],
+  include_directories: include_directories('..'),
+  install: true,
+  subdir : 'gi',
+  c_args: pyext_c_args + main_c_args
+)
+
+if with_pycairo
+  gicairoext = python.extension_module('_gi_cairo', ['pygi-foreign-cairo.c'],
+    dependencies : [python_ext_dep, glib_dep, gi_dep, ffi_dep, pycairo_dep, cairo_dep, cairo_gobject_dep],
+    install: true,
+    subdir : 'gi',
+    c_args: pyext_c_args + main_c_args)
+endif
+
+subdir('overrides')
+subdir('repository')
diff --git a/gi/module.py b/gi/module.py
new file mode 100644 (file)
index 0000000..c5b030a
--- /dev/null
@@ -0,0 +1,267 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2007-2009 Johan Dahlin <johan@gnome.org>
+#
+#   module.py: dynamic module for introspected libraries.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from __future__ import absolute_import
+
+import importlib
+
+try:
+    maketrans = ''.maketrans
+except AttributeError:
+    # fallback for Python 2
+    from string import maketrans
+
+import gi
+
+from ._gi import \
+    Repository, \
+    FunctionInfo, \
+    RegisteredTypeInfo, \
+    EnumInfo, \
+    ObjectInfo, \
+    InterfaceInfo, \
+    ConstantInfo, \
+    StructInfo, \
+    UnionInfo, \
+    CallbackInfo, \
+    Struct, \
+    Boxed, \
+    CCallback, \
+    enum_add, \
+    enum_register_new_gtype_and_add, \
+    flags_add, \
+    flags_register_new_gtype_and_add, \
+    GInterface
+from .types import \
+    GObjectMeta, \
+    StructMeta
+
+from ._constants import \
+    TYPE_NONE, \
+    TYPE_BOXED, \
+    TYPE_POINTER, \
+    TYPE_ENUM, \
+    TYPE_FLAGS
+
+
+repository = Repository.get_default()
+
+# Cache of IntrospectionModules that have been loaded.
+_introspection_modules = {}
+
+
+def get_parent_for_object(object_info):
+    parent_object_info = object_info.get_parent()
+
+    if not parent_object_info:
+        # If we reach the end of the introspection info class hierarchy, look
+        # for an existing wrapper on the GType and use it as a base for the
+        # new introspection wrapper. This allows static C wrappers already
+        # registered with the GType to be used as the introspection base
+        # (_gi.GObject for example)
+        gtype = object_info.get_g_type()
+        if gtype and gtype.pytype:
+            return gtype.pytype
+
+        # Otherwise use builtins.object as the base
+        return object
+
+    namespace = parent_object_info.get_namespace()
+    name = parent_object_info.get_name()
+
+    module = importlib.import_module('gi.repository.' + namespace)
+    return getattr(module, name)
+
+
+def get_interfaces_for_object(object_info):
+    interfaces = []
+    for interface_info in object_info.get_interfaces():
+        namespace = interface_info.get_namespace()
+        name = interface_info.get_name()
+
+        module = importlib.import_module('gi.repository.' + namespace)
+        interfaces.append(getattr(module, name))
+    return interfaces
+
+
+class IntrospectionModule(object):
+    """An object which wraps an introspection typelib.
+
+    This wrapping creates a python module like representation of the typelib
+    using gi repository as a foundation. Accessing attributes of the module
+    will dynamically pull them in and create wrappers for the members.
+    These members are then cached on this introspection module.
+    """
+    def __init__(self, namespace, version=None):
+        """Might raise gi._gi.RepositoryError"""
+
+        repository.require(namespace, version)
+        self._namespace = namespace
+        self._version = version
+        self.__name__ = 'gi.repository.' + namespace
+
+        path = repository.get_typelib_path(self._namespace)
+        self.__path__ = [path]
+
+        if self._version is None:
+            self._version = repository.get_version(self._namespace)
+
+    def __getattr__(self, name):
+        info = repository.find_by_name(self._namespace, name)
+        if not info:
+            raise AttributeError("%r object has no attribute %r" % (
+                                 self.__name__, name))
+
+        if isinstance(info, EnumInfo):
+            g_type = info.get_g_type()
+            wrapper = g_type.pytype
+
+            if wrapper is None:
+                if info.is_flags():
+                    if g_type.is_a(TYPE_FLAGS):
+                        wrapper = flags_add(g_type)
+                    else:
+                        assert g_type == TYPE_NONE
+                        wrapper = flags_register_new_gtype_and_add(info)
+                else:
+                    if g_type.is_a(TYPE_ENUM):
+                        wrapper = enum_add(g_type)
+                    else:
+                        assert g_type == TYPE_NONE
+                        wrapper = enum_register_new_gtype_and_add(info)
+
+                wrapper.__info__ = info
+                wrapper.__module__ = 'gi.repository.' + info.get_namespace()
+
+                # Don't use upper() here to avoid locale specific
+                # identifier conversion (e. g. in Turkish 'i'.upper() == 'i')
+                # see https://bugzilla.gnome.org/show_bug.cgi?id=649165
+                ascii_upper_trans = maketrans(
+                    'abcdefgjhijklmnopqrstuvwxyz',
+                    'ABCDEFGJHIJKLMNOPQRSTUVWXYZ')
+                for value_info in info.get_values():
+                    value_name = value_info.get_name_unescaped().translate(ascii_upper_trans)
+                    setattr(wrapper, value_name, wrapper(value_info.get_value()))
+                for method_info in info.get_methods():
+                    setattr(wrapper, method_info.__name__, method_info)
+
+            if g_type != TYPE_NONE:
+                g_type.pytype = wrapper
+
+        elif isinstance(info, RegisteredTypeInfo):
+            g_type = info.get_g_type()
+
+            # Create a wrapper.
+            if isinstance(info, ObjectInfo):
+                parent = get_parent_for_object(info)
+                interfaces = tuple(interface for interface in get_interfaces_for_object(info)
+                                   if not issubclass(parent, interface))
+                bases = (parent,) + interfaces
+                metaclass = GObjectMeta
+            elif isinstance(info, CallbackInfo):
+                bases = (CCallback,)
+                metaclass = GObjectMeta
+            elif isinstance(info, InterfaceInfo):
+                bases = (GInterface,)
+                metaclass = GObjectMeta
+            elif isinstance(info, (StructInfo, UnionInfo)):
+                if g_type.is_a(TYPE_BOXED):
+                    bases = (Boxed,)
+                elif (g_type.is_a(TYPE_POINTER) or
+                      g_type == TYPE_NONE or
+                      g_type.fundamental == g_type):
+                    bases = (Struct,)
+                else:
+                    raise TypeError("unable to create a wrapper for %s.%s" % (info.get_namespace(), info.get_name()))
+                metaclass = StructMeta
+            else:
+                raise NotImplementedError(info)
+
+            # Check if there is already a Python wrapper that is not a parent class
+            # of the wrapper being created. If it is a parent, it is ok to clobber
+            # g_type.pytype with a new child class wrapper of the existing parent.
+            # Note that the return here never occurs under normal circumstances due
+            # to caching on the __dict__ itself.
+            if g_type != TYPE_NONE:
+                type_ = g_type.pytype
+                if type_ is not None and type_ not in bases:
+                    self.__dict__[name] = type_
+                    return type_
+
+            dict_ = {
+                '__info__': info,
+                '__module__': 'gi.repository.' + self._namespace,
+                '__gtype__': g_type
+            }
+            wrapper = metaclass(name, bases, dict_)
+
+            # Register the new Python wrapper.
+            if g_type != TYPE_NONE:
+                g_type.pytype = wrapper
+
+        elif isinstance(info, FunctionInfo):
+            wrapper = info
+        elif isinstance(info, ConstantInfo):
+            wrapper = info.get_value()
+        else:
+            raise NotImplementedError(info)
+
+        # Cache the newly created wrapper which will then be
+        # available directly on this introspection module instead of being
+        # lazily constructed through the __getattr__ we are currently in.
+        self.__dict__[name] = wrapper
+        return wrapper
+
+    def __repr__(self):
+        path = repository.get_typelib_path(self._namespace)
+        return "<IntrospectionModule %r from %r>" % (self._namespace, path)
+
+    def __dir__(self):
+        # Python's default dir() is just dir(self.__class__) + self.__dict__.keys()
+        result = set(dir(self.__class__))
+        result.update(self.__dict__.keys())
+
+        # update *set* because some repository attributes have already been
+        # wrapped by __getattr__() and included in self.__dict__; but skip
+        # Callback types, as these are not real objects which we can actually
+        # get
+        namespace_infos = repository.get_infos(self._namespace)
+        result.update(info.get_name() for info in namespace_infos if
+                      not isinstance(info, CallbackInfo))
+
+        return list(result)
+
+
+def get_introspection_module(namespace):
+    """
+    :Returns:
+        An object directly wrapping the gi module without overrides.
+
+    Might raise gi._gi.RepositoryError
+    """
+    if namespace in _introspection_modules:
+        return _introspection_modules[namespace]
+
+    version = gi.get_required_version(namespace)
+    module = IntrospectionModule(namespace, version)
+    _introspection_modules[namespace] = module
+    return module
diff --git a/gi/overrides/GIMarshallingTests.py b/gi/overrides/GIMarshallingTests.py
new file mode 100644 (file)
index 0000000..e9f8e33
--- /dev/null
@@ -0,0 +1,72 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Simon van der Linden <svdlinden@src.gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from ..overrides import override
+from ..module import get_introspection_module
+
+GIMarshallingTests = get_introspection_module('GIMarshallingTests')
+
+__all__ = []
+
+OVERRIDES_CONSTANT = 7
+__all__.append('OVERRIDES_CONSTANT')
+
+
+class OverridesStruct(GIMarshallingTests.OverridesStruct):
+
+    def __new__(cls, long_):
+        return GIMarshallingTests.OverridesStruct.__new__(cls)
+
+    def __init__(self, long_):
+        GIMarshallingTests.OverridesStruct.__init__(self)
+        self.long_ = long_
+
+    def method(self):
+        return GIMarshallingTests.OverridesStruct.method(self) / 7
+
+
+OverridesStruct = override(OverridesStruct)
+__all__.append('OverridesStruct')
+
+
+class OverridesObject(GIMarshallingTests.OverridesObject):
+
+    def __new__(cls, long_):
+        return GIMarshallingTests.OverridesObject.__new__(cls)
+
+    def __init__(self, long_):
+        GIMarshallingTests.OverridesObject.__init__(self)
+        # FIXME: doesn't work yet
+        # self.long_ = long_
+
+    @classmethod
+    def new(cls, long_):
+        self = GIMarshallingTests.OverridesObject.new()
+        # FIXME: doesn't work yet
+        # self.long_ = long_
+        return self
+
+    def method(self):
+        """Overridden doc string."""
+        return GIMarshallingTests.OverridesObject.method(self) / 7
+
+
+OverridesObject = override(OverridesObject)
+__all__.append('OverridesObject')
diff --git a/gi/overrides/GLib.py b/gi/overrides/GLib.py
new file mode 100644 (file)
index 0000000..d717d5f
--- /dev/null
@@ -0,0 +1,882 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Tomeu Vizoso <tomeu.vizoso@collabora.co.uk>
+# Copyright (C) 2011, 2012 Canonical Ltd.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+import warnings
+import sys
+import socket
+
+from .._ossighelper import wakeup_on_signal, register_sigint_fallback
+from ..module import get_introspection_module
+from .._gi import (variant_type_from_string, source_new,
+                   source_set_callback, io_channel_read)
+from ..overrides import override, deprecated, deprecated_attr
+from gi import PyGIDeprecationWarning, version_info
+
+GLib = get_introspection_module('GLib')
+
+__all__ = []
+
+from gi import _option as option
+option  # pyflakes
+__all__.append('option')
+
+
+# Types and functions still needed from static bindings
+from gi import _gi
+from gi._error import GError
+
+Error = GError
+OptionContext = _gi.OptionContext
+OptionGroup = _gi.OptionGroup
+Pid = _gi.Pid
+spawn_async = _gi.spawn_async
+
+
+def threads_init():
+    warnings.warn('Since version 3.11, calling threads_init is no longer needed. '
+                  'See: https://wiki.gnome.org/PyGObject/Threading',
+                  PyGIDeprecationWarning, stacklevel=2)
+
+
+def gerror_matches(self, domain, code):
+    # Handle cases where self.domain was set to an integer for compatibility
+    # with the introspected GLib.Error.
+    if isinstance(self.domain, str):
+        self_domain_quark = GLib.quark_from_string(self.domain)
+    else:
+        self_domain_quark = self.domain
+    return (self_domain_quark, self.code) == (domain, code)
+
+
+def gerror_new_literal(domain, message, code):
+    domain_quark = GLib.quark_to_string(domain)
+    return GError(message, domain_quark, code)
+
+
+# Monkey patch methods that rely on GLib introspection to be loaded at runtime.
+Error.__name__ = 'Error'
+Error.__module__ = 'gi.repository.GLib'
+Error.__gtype__ = GLib.Error.__gtype__
+Error.matches = gerror_matches
+Error.new_literal = staticmethod(gerror_new_literal)
+
+
+__all__ += ['GError', 'Error', 'OptionContext', 'OptionGroup', 'Pid',
+            'spawn_async', 'threads_init']
+
+
+class _VariantCreator(object):
+
+    _LEAF_CONSTRUCTORS = {
+        'b': GLib.Variant.new_boolean,
+        'y': GLib.Variant.new_byte,
+        'n': GLib.Variant.new_int16,
+        'q': GLib.Variant.new_uint16,
+        'i': GLib.Variant.new_int32,
+        'u': GLib.Variant.new_uint32,
+        'x': GLib.Variant.new_int64,
+        't': GLib.Variant.new_uint64,
+        'h': GLib.Variant.new_handle,
+        'd': GLib.Variant.new_double,
+        's': GLib.Variant.new_string,
+        'o': GLib.Variant.new_object_path,
+        'g': GLib.Variant.new_signature,
+        'v': GLib.Variant.new_variant,
+    }
+
+    def _create(self, format, value):
+        """Create a GVariant object from given format and a value that matches
+        the format.
+
+        This method recursively calls itself for complex structures (arrays,
+        dictionaries, boxed).
+
+        Returns the generated GVariant.
+
+        If value is None it will generate an empty GVariant container type.
+        """
+        gvtype = GLib.VariantType(format)
+        if format in self._LEAF_CONSTRUCTORS:
+            return self._LEAF_CONSTRUCTORS[format](value)
+
+        # Since we discarded all leaf types, this must be a container
+        builder = GLib.VariantBuilder.new(gvtype)
+        if value is None:
+            return builder.end()
+
+        if gvtype.is_maybe():
+            builder.add_value(self._create(gvtype.element().dup_string(), value))
+            return builder.end()
+
+        try:
+            iter(value)
+        except TypeError:
+            raise TypeError("Could not create array, tuple or dictionary entry from non iterable value %s %s" %
+                            (format, value))
+
+        if gvtype.is_tuple() and gvtype.n_items() != len(value):
+            raise TypeError("Tuple mismatches value's number of elements %s %s" % (format, value))
+        if gvtype.is_dict_entry() and len(value) != 2:
+            raise TypeError("Dictionary entries must have two elements %s %s" % (format, value))
+
+        if gvtype.is_array():
+            element_type = gvtype.element().dup_string()
+            if isinstance(value, dict):
+                value = value.items()
+            for i in value:
+                builder.add_value(self._create(element_type, i))
+        else:
+            remainer_format = format[1:]
+            for i in value:
+                dup = variant_type_from_string(remainer_format).dup_string()
+                builder.add_value(self._create(dup, i))
+                remainer_format = remainer_format[len(dup):]
+
+        return builder.end()
+
+
+class Variant(GLib.Variant):
+    def __new__(cls, format_string, value):
+        """Create a GVariant from a native Python object.
+
+        format_string is a standard GVariant type signature, value is a Python
+        object whose structure has to match the signature.
+
+        Examples:
+          GLib.Variant('i', 1)
+          GLib.Variant('(is)', (1, 'hello'))
+          GLib.Variant('(asa{sv})', ([], {'foo': GLib.Variant('b', True),
+                                          'bar': GLib.Variant('i', 2)}))
+        """
+        if not GLib.VariantType.string_is_valid(format_string):
+            raise TypeError("Invalid GVariant format string '%s'", format_string)
+        creator = _VariantCreator()
+        v = creator._create(format_string, value)
+        v.format_string = format_string
+        return v
+
+    @staticmethod
+    def new_tuple(*elements):
+        return GLib.Variant.new_tuple(elements)
+
+    def __del__(self):
+        try:
+            self.unref()
+        except ImportError:
+            # Calling unref will cause gi and gi.repository.GLib to be
+            # imported. However, if the program is exiting, then these
+            # modules have likely been removed from sys.modules and will
+            # raise an exception. Assume that's the case for ImportError
+            # and ignore the exception since everything will be cleaned
+            # up, anyways.
+            pass
+
+    def __str__(self):
+        return self.print_(True)
+
+    def __repr__(self):
+        if hasattr(self, 'format_string'):
+            f = self.format_string
+        else:
+            f = self.get_type_string()
+        return "GLib.Variant('%s', %s)" % (f, self.print_(False))
+
+    def __eq__(self, other):
+        try:
+            return self.equal(other)
+        except TypeError:
+            return False
+
+    def __ne__(self, other):
+        try:
+            return not self.equal(other)
+        except TypeError:
+            return True
+
+    def __hash__(self):
+        # We're not using just hash(self.unpack()) because otherwise we'll have
+        # hash collisions between the same content in different variant types,
+        # which will cause a performance issue in set/dict/etc.
+        return hash((self.get_type_string(), self.unpack()))
+
+    def unpack(self):
+        """Decompose a GVariant into a native Python object."""
+
+        LEAF_ACCESSORS = {
+            'b': self.get_boolean,
+            'y': self.get_byte,
+            'n': self.get_int16,
+            'q': self.get_uint16,
+            'i': self.get_int32,
+            'u': self.get_uint32,
+            'x': self.get_int64,
+            't': self.get_uint64,
+            'h': self.get_handle,
+            'd': self.get_double,
+            's': self.get_string,
+            'o': self.get_string,  # object path
+            'g': self.get_string,  # signature
+        }
+
+        # simple values
+        la = LEAF_ACCESSORS.get(self.get_type_string())
+        if la:
+            return la()
+
+        # tuple
+        if self.get_type_string().startswith('('):
+            res = [self.get_child_value(i).unpack()
+                   for i in range(self.n_children())]
+            return tuple(res)
+
+        # dictionary
+        if self.get_type_string().startswith('a{'):
+            res = {}
+            for i in range(self.n_children()):
+                v = self.get_child_value(i)
+                res[v.get_child_value(0).unpack()] = v.get_child_value(1).unpack()
+            return res
+
+        # array
+        if self.get_type_string().startswith('a'):
+            return [self.get_child_value(i).unpack()
+                    for i in range(self.n_children())]
+
+        # variant (just unbox transparently)
+        if self.get_type_string().startswith('v'):
+            return self.get_variant().unpack()
+
+        # maybe
+        if self.get_type_string().startswith('m'):
+            if not self.n_children():
+                return None
+            return self.get_child_value(0).unpack()
+
+        raise NotImplementedError('unsupported GVariant type ' + self.get_type_string())
+
+    @classmethod
+    def split_signature(klass, signature):
+        """Return a list of the element signatures of the topmost signature tuple.
+
+        If the signature is not a tuple, it returns one element with the entire
+        signature. If the signature is an empty tuple, the result is [].
+
+        This is useful for e. g. iterating over method parameters which are
+        passed as a single Variant.
+        """
+        if signature == '()':
+            return []
+
+        if not signature.startswith('('):
+            return [signature]
+
+        result = []
+        head = ''
+        tail = signature[1:-1]  # eat the surrounding ()
+        while tail:
+            c = tail[0]
+            head += c
+            tail = tail[1:]
+
+            if c in ('m', 'a'):
+                # prefixes, keep collecting
+                continue
+            if c in ('(', '{'):
+                # consume until corresponding )/}
+                level = 1
+                up = c
+                if up == '(':
+                    down = ')'
+                else:
+                    down = '}'
+                while level > 0:
+                    c = tail[0]
+                    head += c
+                    tail = tail[1:]
+                    if c == up:
+                        level += 1
+                    elif c == down:
+                        level -= 1
+
+            # otherwise we have a simple type
+            result.append(head)
+            head = ''
+
+        return result
+
+    #
+    # Pythonic iterators
+    #
+
+    def __len__(self):
+        if self.get_type_string() in ['s', 'o', 'g']:
+            return len(self.get_string())
+        # Array, dict, tuple
+        if self.get_type_string().startswith('a') or self.get_type_string().startswith('('):
+            return self.n_children()
+        raise TypeError('GVariant type %s does not have a length' % self.get_type_string())
+
+    def __getitem__(self, key):
+        # dict
+        if self.get_type_string().startswith('a{'):
+            try:
+                val = self.lookup_value(key, variant_type_from_string('*'))
+                if val is None:
+                    raise KeyError(key)
+                return val.unpack()
+            except TypeError:
+                # lookup_value() only works for string keys, which is certainly
+                # the common case; we have to do painful iteration for other
+                # key types
+                for i in range(self.n_children()):
+                    v = self.get_child_value(i)
+                    if v.get_child_value(0).unpack() == key:
+                        return v.get_child_value(1).unpack()
+                raise KeyError(key)
+
+        # array/tuple
+        if self.get_type_string().startswith('a') or self.get_type_string().startswith('('):
+            key = int(key)
+            if key < 0:
+                key = self.n_children() + key
+            if key < 0 or key >= self.n_children():
+                raise IndexError('list index out of range')
+            return self.get_child_value(key).unpack()
+
+        # string
+        if self.get_type_string() in ['s', 'o', 'g']:
+            return self.get_string().__getitem__(key)
+
+        raise TypeError('GVariant type %s is not a container' % self.get_type_string())
+
+    #
+    # Pythonic bool operations
+    #
+
+    def __nonzero__(self):
+        return self.__bool__()
+
+    def __bool__(self):
+        if self.get_type_string() in ['y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd']:
+            return self.unpack() != 0
+        if self.get_type_string() in ['b']:
+            return self.get_boolean()
+        if self.get_type_string() in ['s', 'o', 'g']:
+            return len(self.get_string()) != 0
+        # Array, dict, tuple
+        if self.get_type_string().startswith('a') or self.get_type_string().startswith('('):
+            return self.n_children() != 0
+        if self.get_type_string() in ['v']:
+            # unpack works recursively, hence bool also works recursively
+            return bool(self.unpack())
+        # Everything else is True
+        return True
+
+    def keys(self):
+        if not self.get_type_string().startswith('a{'):
+            return TypeError, 'GVariant type %s is not a dictionary' % self.get_type_string()
+
+        res = []
+        for i in range(self.n_children()):
+            v = self.get_child_value(i)
+            res.append(v.get_child_value(0).unpack())
+        return res
+
+
+def get_string(self):
+    value, length = GLib.Variant.get_string(self)
+    return value
+
+
+setattr(Variant, 'get_string', get_string)
+
+__all__.append('Variant')
+
+
+def markup_escape_text(text, length=-1):
+    if isinstance(text, bytes):
+        return GLib.markup_escape_text(text.decode('UTF-8'), length)
+    else:
+        return GLib.markup_escape_text(text, length)
+
+
+__all__.append('markup_escape_text')
+
+
+# backwards compatible names from old static bindings
+for n in ['DESKTOP', 'DOCUMENTS', 'DOWNLOAD', 'MUSIC', 'PICTURES',
+          'PUBLIC_SHARE', 'TEMPLATES', 'VIDEOS']:
+    attr = 'USER_DIRECTORY_' + n
+    deprecated_attr("GLib", attr, "GLib.UserDirectory.DIRECTORY_" + n)
+    globals()[attr] = getattr(GLib.UserDirectory, 'DIRECTORY_' + n)
+    __all__.append(attr)
+
+for n in ['ERR', 'HUP', 'IN', 'NVAL', 'OUT', 'PRI']:
+    globals()['IO_' + n] = getattr(GLib.IOCondition, n)
+    __all__.append('IO_' + n)
+
+for n in ['APPEND', 'GET_MASK', 'IS_READABLE', 'IS_SEEKABLE',
+          'MASK', 'NONBLOCK', 'SET_MASK']:
+    attr = 'IO_FLAG_' + n
+    deprecated_attr("GLib", attr, "GLib.IOFlags." + n)
+    globals()[attr] = getattr(GLib.IOFlags, n)
+    __all__.append(attr)
+
+# spelling for the win
+IO_FLAG_IS_WRITEABLE = GLib.IOFlags.IS_WRITABLE
+deprecated_attr("GLib", "IO_FLAG_IS_WRITEABLE", "GLib.IOFlags.IS_WRITABLE")
+__all__.append('IO_FLAG_IS_WRITEABLE')
+
+for n in ['AGAIN', 'EOF', 'ERROR', 'NORMAL']:
+    attr = 'IO_STATUS_' + n
+    globals()[attr] = getattr(GLib.IOStatus, n)
+    deprecated_attr("GLib", attr, "GLib.IOStatus." + n)
+    __all__.append(attr)
+
+for n in ['CHILD_INHERITS_STDIN', 'DO_NOT_REAP_CHILD', 'FILE_AND_ARGV_ZERO',
+          'LEAVE_DESCRIPTORS_OPEN', 'SEARCH_PATH', 'STDERR_TO_DEV_NULL',
+          'STDOUT_TO_DEV_NULL']:
+    attr = 'SPAWN_' + n
+    globals()[attr] = getattr(GLib.SpawnFlags, n)
+    deprecated_attr("GLib", attr, "GLib.SpawnFlags." + n)
+    __all__.append(attr)
+
+for n in ['HIDDEN', 'IN_MAIN', 'REVERSE', 'NO_ARG', 'FILENAME', 'OPTIONAL_ARG',
+          'NOALIAS']:
+    attr = 'OPTION_FLAG_' + n
+    globals()[attr] = getattr(GLib.OptionFlags, n)
+    deprecated_attr("GLib", attr, "GLib.OptionFlags." + n)
+    __all__.append(attr)
+
+for n in ['UNKNOWN_OPTION', 'BAD_VALUE', 'FAILED']:
+    attr = 'OPTION_ERROR_' + n
+    deprecated_attr("GLib", attr, "GLib.OptionError." + n)
+    globals()[attr] = getattr(GLib.OptionError, n)
+    __all__.append(attr)
+
+
+# these are not currently exported in GLib gir, presumably because they are
+# platform dependent; so get them from our static bindings
+for name in ['G_MINFLOAT', 'G_MAXFLOAT', 'G_MINDOUBLE', 'G_MAXDOUBLE',
+             'G_MINSHORT', 'G_MAXSHORT', 'G_MAXUSHORT', 'G_MININT', 'G_MAXINT',
+             'G_MAXUINT', 'G_MINLONG', 'G_MAXLONG', 'G_MAXULONG', 'G_MAXSIZE',
+             'G_MINSSIZE', 'G_MAXSSIZE', 'G_MINOFFSET', 'G_MAXOFFSET']:
+    attr = name.split("_", 1)[-1]
+    globals()[attr] = getattr(_gi, name)
+    __all__.append(attr)
+
+
+class MainLoop(GLib.MainLoop):
+    # Backwards compatible constructor API
+    def __new__(cls, context=None):
+        return GLib.MainLoop.new(context, False)
+
+    def __init__(self, context=None):
+        pass
+
+    def run(self):
+        with register_sigint_fallback(self.quit):
+            with wakeup_on_signal():
+                super(MainLoop, self).run()
+
+
+MainLoop = override(MainLoop)
+__all__.append('MainLoop')
+
+
+class MainContext(GLib.MainContext):
+    # Backwards compatible API with default value
+    def iteration(self, may_block=True):
+        return super(MainContext, self).iteration(may_block)
+
+
+MainContext = override(MainContext)
+__all__.append('MainContext')
+
+
+class Source(GLib.Source):
+    def __new__(cls, *args, **kwargs):
+        # use our custom pygi_source_new() here as g_source_new() is not
+        # bindable
+        source = source_new()
+        source.__class__ = cls
+        setattr(source, '__pygi_custom_source', True)
+        return source
+
+    def __init__(self, *args, **kwargs):
+        return super(Source, self).__init__()
+
+    def __del__(self):
+        if hasattr(self, '__pygi_custom_source'):
+            self.destroy()
+        super(Source, self).__del__()
+
+    def set_callback(self, fn, user_data=None):
+        if hasattr(self, '__pygi_custom_source'):
+            # use our custom pygi_source_set_callback() if for a GSource object
+            # with custom functions
+            source_set_callback(self, fn, user_data)
+        else:
+            # otherwise, for Idle and Timeout, use the standard method
+            super(Source, self).set_callback(fn, user_data)
+
+    def get_current_time(self):
+        return GLib.get_real_time() * 0.000001
+
+    get_current_time = deprecated(get_current_time,
+                                  'GLib.Source.get_time() or GLib.get_real_time()')
+
+    # as get/set_priority are introspected, we can't use the static
+    # property(get_priority, ..) here
+    def __get_priority(self):
+        return self.get_priority()
+
+    def __set_priority(self, value):
+        self.set_priority(value)
+
+    priority = property(__get_priority, __set_priority)
+
+    def __get_can_recurse(self):
+        return self.get_can_recurse()
+
+    def __set_can_recurse(self, value):
+        self.set_can_recurse(value)
+
+    can_recurse = property(__get_can_recurse, __set_can_recurse)
+
+
+Source = override(Source)
+__all__.append('Source')
+
+
+class Idle(Source):
+    def __new__(cls, priority=GLib.PRIORITY_DEFAULT):
+        source = GLib.idle_source_new()
+        source.__class__ = cls
+        return source
+
+    def __init__(self, priority=GLib.PRIORITY_DEFAULT):
+        super(Source, self).__init__()
+        if priority != GLib.PRIORITY_DEFAULT:
+            self.set_priority(priority)
+
+
+__all__.append('Idle')
+
+
+class Timeout(Source):
+    def __new__(cls, interval=0, priority=GLib.PRIORITY_DEFAULT):
+        source = GLib.timeout_source_new(interval)
+        source.__class__ = cls
+        return source
+
+    def __init__(self, interval=0, priority=GLib.PRIORITY_DEFAULT):
+        if priority != GLib.PRIORITY_DEFAULT:
+            self.set_priority(priority)
+
+
+__all__.append('Timeout')
+
+
+# backwards compatible API
+def idle_add(function, *user_data, **kwargs):
+    priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT_IDLE)
+    return GLib.idle_add(priority, function, *user_data)
+
+
+__all__.append('idle_add')
+
+
+def timeout_add(interval, function, *user_data, **kwargs):
+    priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
+    return GLib.timeout_add(priority, interval, function, *user_data)
+
+
+__all__.append('timeout_add')
+
+
+def timeout_add_seconds(interval, function, *user_data, **kwargs):
+    priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
+    return GLib.timeout_add_seconds(priority, interval, function, *user_data)
+
+
+__all__.append('timeout_add_seconds')
+
+
+# The GI GLib API uses g_io_add_watch_full renamed to g_io_add_watch with
+# a signature of (channel, priority, condition, func, user_data).
+# Prior to PyGObject 3.8, this function was statically bound with an API closer to the
+# non-full version with a signature of: (fd, condition, func, *user_data)
+# We need to support this until we are okay with breaking API in a way which is
+# not backwards compatible.
+#
+# This needs to take into account several historical APIs:
+# - calling with an fd as first argument
+# - calling with a Python file object as first argument (we keep this one as
+#   it's really convenient and does not change the number of arguments)
+# - calling without a priority as second argument
+def _io_add_watch_get_args(channel, priority_, condition, *cb_and_user_data, **kwargs):
+    if not isinstance(priority_, int) or isinstance(priority_, GLib.IOCondition):
+        warnings.warn('Calling io_add_watch without priority as second argument is deprecated',
+                      PyGIDeprecationWarning)
+        # shift the arguments around
+        user_data = cb_and_user_data
+        callback = condition
+        condition = priority_
+        if not callable(callback):
+            raise TypeError('third argument must be callable')
+
+        # backwards compatibility: Call with priority kwarg
+        if 'priority' in kwargs:
+            warnings.warn('Calling io_add_watch with priority keyword argument is deprecated, put it as second positional argument',
+                          PyGIDeprecationWarning)
+            priority_ = kwargs['priority']
+        else:
+            priority_ = GLib.PRIORITY_DEFAULT
+    else:
+        if len(cb_and_user_data) < 1 or not callable(cb_and_user_data[0]):
+            raise TypeError('expecting callback as fourth argument')
+        callback = cb_and_user_data[0]
+        user_data = cb_and_user_data[1:]
+
+    # backwards compatibility: Allow calling with fd
+    if isinstance(channel, int):
+        func_fdtransform = lambda _, cond, *data: callback(channel, cond, *data)
+        real_channel = GLib.IOChannel.unix_new(channel)
+    elif isinstance(channel, socket.socket) and sys.platform == 'win32':
+        func_fdtransform = lambda _, cond, *data: callback(channel, cond, *data)
+        real_channel = GLib.IOChannel.win32_new_socket(channel.fileno())
+    elif hasattr(channel, 'fileno'):
+        # backwards compatibility: Allow calling with Python file
+        func_fdtransform = lambda _, cond, *data: callback(channel, cond, *data)
+        real_channel = GLib.IOChannel.unix_new(channel.fileno())
+    else:
+        assert isinstance(channel, GLib.IOChannel)
+        func_fdtransform = callback
+        real_channel = channel
+
+    return real_channel, priority_, condition, func_fdtransform, user_data
+
+
+__all__.append('_io_add_watch_get_args')
+
+
+def io_add_watch(*args, **kwargs):
+    """io_add_watch(channel, priority, condition, func, *user_data) -> event_source_id"""
+    channel, priority, condition, func, user_data = _io_add_watch_get_args(*args, **kwargs)
+    return GLib.io_add_watch(channel, priority, condition, func, *user_data)
+
+
+__all__.append('io_add_watch')
+
+
+# backwards compatible API
+class IOChannel(GLib.IOChannel):
+    def __new__(cls, filedes=None, filename=None, mode=None, hwnd=None):
+        if filedes is not None:
+            return GLib.IOChannel.unix_new(filedes)
+        if filename is not None:
+            return GLib.IOChannel.new_file(filename, mode or 'r')
+        if hwnd is not None:
+            return GLib.IOChannel.win32_new_fd(hwnd)
+        raise TypeError('either a valid file descriptor, file name, or window handle must be supplied')
+
+    def __init__(self, *args, **kwargs):
+        return super(IOChannel, self).__init__()
+
+    def read(self, max_count=-1):
+        return io_channel_read(self, max_count)
+
+    def readline(self, size_hint=-1):
+        # note, size_hint is just to maintain backwards compatible API; the
+        # old static binding did not actually use it
+        (status, buf, length, terminator_pos) = self.read_line()
+        if buf is None:
+            return ''
+        return buf
+
+    def readlines(self, size_hint=-1):
+        # note, size_hint is just to maintain backwards compatible API;
+        # the old static binding did not actually use it
+        lines = []
+        status = GLib.IOStatus.NORMAL
+        while status == GLib.IOStatus.NORMAL:
+            (status, buf, length, terminator_pos) = self.read_line()
+            # note, this appends an empty line after EOF; this is
+            # bug-compatible with the old static bindings
+            if buf is None:
+                buf = ''
+            lines.append(buf)
+        return lines
+
+    def write(self, buf, buflen=-1):
+        if not isinstance(buf, bytes):
+            buf = buf.encode('UTF-8')
+        if buflen == -1:
+            buflen = len(buf)
+        (status, written) = self.write_chars(buf, buflen)
+        return written
+
+    def writelines(self, lines):
+        for line in lines:
+            self.write(line)
+
+    _whence_map = {0: GLib.SeekType.SET, 1: GLib.SeekType.CUR, 2: GLib.SeekType.END}
+
+    def seek(self, offset, whence=0):
+        try:
+            w = self._whence_map[whence]
+        except KeyError:
+            raise ValueError("invalid 'whence' value")
+        return self.seek_position(offset, w)
+
+    def add_watch(self, condition, callback, *user_data, **kwargs):
+        priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
+        return io_add_watch(self, priority, condition, callback, *user_data)
+
+    add_watch = deprecated(add_watch, 'GLib.io_add_watch()')
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        (status, buf, length, terminator_pos) = self.read_line()
+        if status == GLib.IOStatus.NORMAL:
+            return buf
+        raise StopIteration
+
+    # Python 2.x compatibility
+    next = __next__
+
+
+IOChannel = override(IOChannel)
+__all__.append('IOChannel')
+
+
+class PollFD(GLib.PollFD):
+    def __new__(cls, fd, events):
+        pollfd = GLib.PollFD()
+        pollfd.__class__ = cls
+        return pollfd
+
+    def __init__(self, fd, events):
+        self.fd = fd
+        self.events = events
+
+
+PollFD = override(PollFD)
+__all__.append('PollFD')
+
+
+# The GI GLib API uses g_child_watch_add_full renamed to g_child_watch_add with
+# a signature of (priority, pid, callback, data).
+# Prior to PyGObject 3.8, this function was statically bound with an API closer to the
+# non-full version with a signature of: (pid, callback, data=None, priority=GLib.PRIORITY_DEFAULT)
+# We need to support this until we are okay with breaking API in a way which is
+# not backwards compatible.
+def _child_watch_add_get_args(priority_or_pid, pid_or_callback, *args, **kwargs):
+    user_data = []
+
+    if callable(pid_or_callback):
+        warnings.warn('Calling child_watch_add without priority as first argument is deprecated',
+                      PyGIDeprecationWarning)
+        pid = priority_or_pid
+        callback = pid_or_callback
+        if len(args) == 0:
+            priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
+        elif len(args) == 1:
+            user_data = args
+            priority = kwargs.get('priority', GLib.PRIORITY_DEFAULT)
+        elif len(args) == 2:
+            user_data = [args[0]]
+            priority = args[1]
+        else:
+            raise TypeError('expected at most 4 positional arguments')
+    else:
+        priority = priority_or_pid
+        pid = pid_or_callback
+        if 'function' in kwargs:
+            callback = kwargs['function']
+            user_data = args
+        elif len(args) > 0 and callable(args[0]):
+            callback = args[0]
+            user_data = args[1:]
+        else:
+            raise TypeError('expected callback as third argument')
+
+    if 'data' in kwargs:
+        if user_data:
+            raise TypeError('got multiple values for "data" argument')
+        user_data = [kwargs['data']]
+
+    return priority, pid, callback, user_data
+
+
+# we need this to be accessible for unit testing
+__all__.append('_child_watch_add_get_args')
+
+
+def child_watch_add(*args, **kwargs):
+    """child_watch_add(priority, pid, function, *data)"""
+    priority, pid, function, data = _child_watch_add_get_args(*args, **kwargs)
+    return GLib.child_watch_add(priority, pid, function, *data)
+
+
+__all__.append('child_watch_add')
+
+
+def get_current_time():
+    return GLib.get_real_time() * 0.000001
+
+
+get_current_time = deprecated(get_current_time, 'GLib.get_real_time()')
+
+__all__.append('get_current_time')
+
+
+# backwards compatible API with default argument, and ignoring bytes_read
+# output argument
+def filename_from_utf8(utf8string, len=-1):
+    return GLib.filename_from_utf8(utf8string, len)[0]
+
+
+__all__.append('filename_from_utf8')
+
+
+# backwards compatible API for renamed function
+if not hasattr(GLib, 'unix_signal_add_full'):
+    def add_full_compat(*args):
+        warnings.warn('GLib.unix_signal_add_full() was renamed to GLib.unix_signal_add()',
+                      PyGIDeprecationWarning)
+        return GLib.unix_signal_add(*args)
+
+    GLib.unix_signal_add_full = add_full_compat
+
+
+# obsolete constants for backwards compatibility
+glib_version = (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION)
+__all__.append('glib_version')
+deprecated_attr("GLib", "glib_version",
+                "(GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION)")
+
+pyglib_version = version_info
+__all__.append('pyglib_version')
+deprecated_attr("GLib", "pyglib_version", "gi.version_info")
diff --git a/gi/overrides/GObject.py b/gi/overrides/GObject.py
new file mode 100644 (file)
index 0000000..68ba6d5
--- /dev/null
@@ -0,0 +1,731 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2012 Canonical Ltd.
+# Author: Martin Pitt <martin.pitt@ubuntu.com>
+# Copyright (C) 2012-2013 Simon Feltman <sfeltman@src.gnome.org>
+# Copyright (C) 2012 Bastian Winkler <buz@netbuz.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+import warnings
+from collections import namedtuple
+
+import gi.overrides
+import gi.module
+from gi.overrides import override, deprecated_attr
+from gi.repository import GLib
+from gi import PyGIDeprecationWarning
+from gi._compat import PY2, text_type
+
+from gi import _propertyhelper as propertyhelper
+from gi import _signalhelper as signalhelper
+from gi import _gi
+
+
+GObjectModule = gi.module.get_introspection_module('GObject')
+
+__all__ = []
+
+
+from gi import _option as option
+option = option
+
+
+# API aliases for backwards compatibility
+for name in ['markup_escape_text', 'get_application_name',
+             'set_application_name', 'get_prgname', 'set_prgname',
+             'main_depth', 'filename_display_basename',
+             'filename_display_name', 'filename_from_utf8',
+             'uri_list_extract_uris',
+             'MainLoop', 'MainContext', 'main_context_default',
+             'source_remove', 'Source', 'Idle', 'Timeout', 'PollFD',
+             'idle_add', 'timeout_add', 'timeout_add_seconds',
+             'io_add_watch', 'child_watch_add', 'get_current_time',
+             'spawn_async']:
+    globals()[name] = getattr(GLib, name)
+    deprecated_attr("GObject", name, "GLib." + name)
+    __all__.append(name)
+
+# deprecated constants
+for name in ['PRIORITY_DEFAULT', 'PRIORITY_DEFAULT_IDLE', 'PRIORITY_HIGH',
+             'PRIORITY_HIGH_IDLE', 'PRIORITY_LOW',
+             'IO_IN', 'IO_OUT', 'IO_PRI', 'IO_ERR', 'IO_HUP', 'IO_NVAL',
+             'IO_STATUS_ERROR', 'IO_STATUS_NORMAL', 'IO_STATUS_EOF',
+             'IO_STATUS_AGAIN', 'IO_FLAG_APPEND', 'IO_FLAG_NONBLOCK',
+             'IO_FLAG_IS_READABLE', 'IO_FLAG_IS_WRITEABLE',
+             'IO_FLAG_IS_SEEKABLE', 'IO_FLAG_MASK', 'IO_FLAG_GET_MASK',
+             'IO_FLAG_SET_MASK',
+             'SPAWN_LEAVE_DESCRIPTORS_OPEN', 'SPAWN_DO_NOT_REAP_CHILD',
+             'SPAWN_SEARCH_PATH', 'SPAWN_STDOUT_TO_DEV_NULL',
+             'SPAWN_STDERR_TO_DEV_NULL', 'SPAWN_CHILD_INHERITS_STDIN',
+             'SPAWN_FILE_AND_ARGV_ZERO',
+             'OPTION_FLAG_HIDDEN', 'OPTION_FLAG_IN_MAIN', 'OPTION_FLAG_REVERSE',
+             'OPTION_FLAG_NO_ARG', 'OPTION_FLAG_FILENAME', 'OPTION_FLAG_OPTIONAL_ARG',
+             'OPTION_FLAG_NOALIAS', 'OPTION_ERROR_UNKNOWN_OPTION',
+             'OPTION_ERROR_BAD_VALUE', 'OPTION_ERROR_FAILED', 'OPTION_REMAINING',
+             'glib_version']:
+    with warnings.catch_warnings():
+        # TODO: this uses deprecated Glib attributes, silence for now
+        warnings.simplefilter('ignore', PyGIDeprecationWarning)
+        globals()[name] = getattr(GLib, name)
+    deprecated_attr("GObject", name, "GLib." + name)
+    __all__.append(name)
+
+
+for name in ['G_MININT8', 'G_MAXINT8', 'G_MAXUINT8', 'G_MININT16',
+             'G_MAXINT16', 'G_MAXUINT16', 'G_MININT32', 'G_MAXINT32',
+             'G_MAXUINT32', 'G_MININT64', 'G_MAXINT64', 'G_MAXUINT64']:
+    new_name = name.split("_", 1)[-1]
+    globals()[name] = getattr(GLib, new_name)
+    deprecated_attr("GObject", name, "GLib." + new_name)
+    __all__.append(name)
+
+# these are not currently exported in GLib gir, presumably because they are
+# platform dependent; so get them from our static bindings
+for name in ['G_MINFLOAT', 'G_MAXFLOAT', 'G_MINDOUBLE', 'G_MAXDOUBLE',
+             'G_MINSHORT', 'G_MAXSHORT', 'G_MAXUSHORT', 'G_MININT', 'G_MAXINT',
+             'G_MAXUINT', 'G_MINLONG', 'G_MAXLONG', 'G_MAXULONG', 'G_MAXSIZE',
+             'G_MINSSIZE', 'G_MAXSSIZE', 'G_MINOFFSET', 'G_MAXOFFSET']:
+    new_name = name.split("_", 1)[-1]
+    globals()[name] = getattr(GLib, new_name)
+    deprecated_attr("GObject", name, "GLib." + new_name)
+    __all__.append(name)
+
+
+TYPE_INVALID = GObjectModule.type_from_name('invalid')
+TYPE_NONE = GObjectModule.type_from_name('void')
+TYPE_INTERFACE = GObjectModule.type_from_name('GInterface')
+TYPE_CHAR = GObjectModule.type_from_name('gchar')
+TYPE_UCHAR = GObjectModule.type_from_name('guchar')
+TYPE_BOOLEAN = GObjectModule.type_from_name('gboolean')
+TYPE_INT = GObjectModule.type_from_name('gint')
+TYPE_UINT = GObjectModule.type_from_name('guint')
+TYPE_LONG = GObjectModule.type_from_name('glong')
+TYPE_ULONG = GObjectModule.type_from_name('gulong')
+TYPE_INT64 = GObjectModule.type_from_name('gint64')
+TYPE_UINT64 = GObjectModule.type_from_name('guint64')
+TYPE_ENUM = GObjectModule.type_from_name('GEnum')
+TYPE_FLAGS = GObjectModule.type_from_name('GFlags')
+TYPE_FLOAT = GObjectModule.type_from_name('gfloat')
+TYPE_DOUBLE = GObjectModule.type_from_name('gdouble')
+TYPE_STRING = GObjectModule.type_from_name('gchararray')
+TYPE_POINTER = GObjectModule.type_from_name('gpointer')
+TYPE_BOXED = GObjectModule.type_from_name('GBoxed')
+TYPE_PARAM = GObjectModule.type_from_name('GParam')
+TYPE_OBJECT = GObjectModule.type_from_name('GObject')
+TYPE_PYOBJECT = GObjectModule.type_from_name('PyObject')
+TYPE_GTYPE = GObjectModule.type_from_name('GType')
+TYPE_STRV = GObjectModule.type_from_name('GStrv')
+TYPE_VARIANT = GObjectModule.type_from_name('GVariant')
+TYPE_GSTRING = GObjectModule.type_from_name('GString')
+TYPE_VALUE = GObjectModule.Value.__gtype__
+TYPE_UNICHAR = TYPE_UINT
+__all__ += ['TYPE_INVALID', 'TYPE_NONE', 'TYPE_INTERFACE', 'TYPE_CHAR',
+            'TYPE_UCHAR', 'TYPE_BOOLEAN', 'TYPE_INT', 'TYPE_UINT', 'TYPE_LONG',
+            'TYPE_ULONG', 'TYPE_INT64', 'TYPE_UINT64', 'TYPE_ENUM', 'TYPE_FLAGS',
+            'TYPE_FLOAT', 'TYPE_DOUBLE', 'TYPE_STRING', 'TYPE_POINTER',
+            'TYPE_BOXED', 'TYPE_PARAM', 'TYPE_OBJECT', 'TYPE_PYOBJECT',
+            'TYPE_GTYPE', 'TYPE_STRV', 'TYPE_VARIANT', 'TYPE_GSTRING',
+            'TYPE_UNICHAR', 'TYPE_VALUE']
+
+
+# Deprecated, use GLib directly
+for name in ['Pid', 'GError', 'OptionGroup', 'OptionContext']:
+    globals()[name] = getattr(GLib, name)
+    deprecated_attr("GObject", name, "GLib." + name)
+    __all__.append(name)
+
+
+# Deprecated, use: GObject.ParamFlags.* directly
+for name in ['PARAM_CONSTRUCT', 'PARAM_CONSTRUCT_ONLY', 'PARAM_LAX_VALIDATION',
+             'PARAM_READABLE', 'PARAM_WRITABLE']:
+    new_name = name.split("_", 1)[-1]
+    globals()[name] = getattr(GObjectModule.ParamFlags, new_name)
+    deprecated_attr("GObject", name, "GObject.ParamFlags." + new_name)
+    __all__.append(name)
+
+# PARAM_READWRITE should come from the gi module but cannot due to:
+# https://bugzilla.gnome.org/show_bug.cgi?id=687615
+PARAM_READWRITE = GObjectModule.ParamFlags.READABLE | \
+    GObjectModule.ParamFlags.WRITABLE
+__all__.append("PARAM_READWRITE")
+
+# READWRITE is part of ParamFlags since glib 2.42. Only mark PARAM_READWRITE as
+# deprecated in case ParamFlags.READWRITE is available. Also include the glib
+# version in the warning so it's clear that this needs a newer glib, unlike
+# the other ParamFlags related deprecations.
+# https://bugzilla.gnome.org/show_bug.cgi?id=726037
+if hasattr(GObjectModule.ParamFlags, "READWRITE"):
+    deprecated_attr("GObject", "PARAM_READWRITE",
+                    "GObject.ParamFlags.READWRITE (glib 2.42+)")
+
+
+# Deprecated, use: GObject.SignalFlags.* directly
+for name in ['SIGNAL_ACTION', 'SIGNAL_DETAILED', 'SIGNAL_NO_HOOKS',
+             'SIGNAL_NO_RECURSE', 'SIGNAL_RUN_CLEANUP', 'SIGNAL_RUN_FIRST',
+             'SIGNAL_RUN_LAST']:
+    new_name = name.split("_", 1)[-1]
+    globals()[name] = getattr(GObjectModule.SignalFlags, new_name)
+    deprecated_attr("GObject", name, "GObject.SignalFlags." + new_name)
+    __all__.append(name)
+
+# Static types
+GBoxed = _gi.GBoxed
+GEnum = _gi.GEnum
+GFlags = _gi.GFlags
+GInterface = _gi.GInterface
+GObject = _gi.GObject
+GObjectWeakRef = _gi.GObjectWeakRef
+GParamSpec = _gi.GParamSpec
+GPointer = _gi.GPointer
+GType = _gi.GType
+Warning = _gi.Warning
+__all__ += ['GBoxed', 'GEnum', 'GFlags', 'GInterface', 'GObject',
+            'GObjectWeakRef', 'GParamSpec', 'GPointer', 'GType',
+            'Warning']
+
+
+features = {'generic-c-marshaller': True}
+list_properties = _gi.list_properties
+new = _gi.new
+pygobject_version = _gi.pygobject_version
+threads_init = GLib.threads_init
+type_register = _gi.type_register
+__all__ += ['features', 'list_properties', 'new',
+            'pygobject_version', 'threads_init', 'type_register']
+
+
+class Value(GObjectModule.Value):
+    def __init__(self, value_type=None, py_value=None):
+        GObjectModule.Value.__init__(self)
+        if value_type is not None:
+            self.init(value_type)
+            if py_value is not None:
+                self.set_value(py_value)
+
+    def __del__(self):
+        if self._is_valid:
+            if self._free_on_dealloc and self.g_type != TYPE_INVALID:
+                self.unset()
+
+        # We must call base class __del__() after unset.
+        super(Value, self).__del__()
+
+    def set_boxed(self, boxed):
+        # Workaround the introspection marshalers inability to know
+        # these methods should be marshaling boxed types. This is because
+        # the type information is stored on the GValue.
+        _gi._gvalue_set(self, boxed)
+
+    def get_boxed(self):
+        return _gi._gvalue_get(self)
+
+    def set_value(self, py_value):
+        gtype = self.g_type
+
+        if gtype == _gi.TYPE_INVALID:
+            raise TypeError("GObject.Value needs to be initialized first")
+        elif gtype == TYPE_BOOLEAN:
+            self.set_boolean(py_value)
+        elif gtype == TYPE_CHAR:
+            self.set_char(py_value)
+        elif gtype == TYPE_UCHAR:
+            self.set_uchar(py_value)
+        elif gtype == TYPE_INT:
+            self.set_int(py_value)
+        elif gtype == TYPE_UINT:
+            self.set_uint(py_value)
+        elif gtype == TYPE_LONG:
+            self.set_long(py_value)
+        elif gtype == TYPE_ULONG:
+            self.set_ulong(py_value)
+        elif gtype == TYPE_INT64:
+            self.set_int64(py_value)
+        elif gtype == TYPE_UINT64:
+            self.set_uint64(py_value)
+        elif gtype == TYPE_FLOAT:
+            self.set_float(py_value)
+        elif gtype == TYPE_DOUBLE:
+            self.set_double(py_value)
+        elif gtype == TYPE_STRING:
+            if isinstance(py_value, str):
+                py_value = str(py_value)
+            elif PY2:
+                if isinstance(py_value, text_type):
+                    py_value = py_value.encode('UTF-8')
+                else:
+                    raise ValueError("Expected string or unicode but got %s%s" %
+                                     (py_value, type(py_value)))
+            else:
+                raise ValueError("Expected string but got %s%s" %
+                                 (py_value, type(py_value)))
+            self.set_string(py_value)
+        elif gtype == TYPE_PARAM:
+            self.set_param(py_value)
+        elif gtype.is_a(TYPE_ENUM):
+            self.set_enum(py_value)
+        elif gtype.is_a(TYPE_FLAGS):
+            self.set_flags(py_value)
+        elif gtype.is_a(TYPE_BOXED):
+            self.set_boxed(py_value)
+        elif gtype == TYPE_POINTER:
+            self.set_pointer(py_value)
+        elif gtype.is_a(TYPE_OBJECT):
+            self.set_object(py_value)
+        elif gtype == TYPE_UNICHAR:
+            self.set_uint(int(py_value))
+        # elif gtype == TYPE_OVERRIDE:
+        #     pass
+        elif gtype == TYPE_GTYPE:
+            self.set_gtype(py_value)
+        elif gtype == TYPE_VARIANT:
+            self.set_variant(py_value)
+        elif gtype == TYPE_PYOBJECT:
+            self.set_boxed(py_value)
+        else:
+            raise TypeError("Unknown value type %s" % gtype)
+
+    def get_value(self):
+        gtype = self.g_type
+
+        if gtype == TYPE_BOOLEAN:
+            return self.get_boolean()
+        elif gtype == TYPE_CHAR:
+            return self.get_char()
+        elif gtype == TYPE_UCHAR:
+            return self.get_uchar()
+        elif gtype == TYPE_INT:
+            return self.get_int()
+        elif gtype == TYPE_UINT:
+            return self.get_uint()
+        elif gtype == TYPE_LONG:
+            return self.get_long()
+        elif gtype == TYPE_ULONG:
+            return self.get_ulong()
+        elif gtype == TYPE_INT64:
+            return self.get_int64()
+        elif gtype == TYPE_UINT64:
+            return self.get_uint64()
+        elif gtype == TYPE_FLOAT:
+            return self.get_float()
+        elif gtype == TYPE_DOUBLE:
+            return self.get_double()
+        elif gtype == TYPE_STRING:
+            return self.get_string()
+        elif gtype == TYPE_PARAM:
+            return self.get_param()
+        elif gtype.is_a(TYPE_ENUM):
+            return self.get_enum()
+        elif gtype.is_a(TYPE_FLAGS):
+            return self.get_flags()
+        elif gtype.is_a(TYPE_BOXED):
+            return self.get_boxed()
+        elif gtype == TYPE_POINTER:
+            return self.get_pointer()
+        elif gtype.is_a(TYPE_OBJECT):
+            return self.get_object()
+        elif gtype == TYPE_UNICHAR:
+            return self.get_uint()
+        elif gtype == TYPE_GTYPE:
+            return self.get_gtype()
+        elif gtype == TYPE_VARIANT:
+            return self.get_variant()
+        elif gtype == TYPE_PYOBJECT:
+            pass
+        else:
+            return None
+
+    def __repr__(self):
+        return '<Value (%s) %s>' % (self.g_type.name, self.get_value())
+
+
+Value = override(Value)
+__all__.append('Value')
+
+
+def type_from_name(name):
+    type_ = GObjectModule.type_from_name(name)
+    if type_ == TYPE_INVALID:
+        raise RuntimeError('unknown type name: %s' % name)
+    return type_
+
+
+__all__.append('type_from_name')
+
+
+def type_parent(type_):
+    parent = GObjectModule.type_parent(type_)
+    if parent == TYPE_INVALID:
+        raise RuntimeError('no parent for type')
+    return parent
+
+
+__all__.append('type_parent')
+
+
+def _validate_type_for_signal_method(type_):
+    if hasattr(type_, '__gtype__'):
+        type_ = type_.__gtype__
+    if not type_.is_instantiatable() and not type_.is_interface():
+        raise TypeError('type must be instantiable or an interface, got %s' % type_)
+
+
+def signal_list_ids(type_):
+    _validate_type_for_signal_method(type_)
+    return GObjectModule.signal_list_ids(type_)
+
+
+__all__.append('signal_list_ids')
+
+
+def signal_list_names(type_):
+    ids = signal_list_ids(type_)
+    return tuple(GObjectModule.signal_name(i) for i in ids)
+
+
+__all__.append('signal_list_names')
+
+
+def signal_lookup(name, type_):
+    _validate_type_for_signal_method(type_)
+    return GObjectModule.signal_lookup(name, type_)
+
+
+__all__.append('signal_lookup')
+
+
+SignalQuery = namedtuple('SignalQuery',
+                         ['signal_id',
+                          'signal_name',
+                          'itype',
+                          'signal_flags',
+                          'return_type',
+                          # n_params',
+                          'param_types'])
+
+
+def signal_query(id_or_name, type_=None):
+    if type_ is not None:
+        id_or_name = signal_lookup(id_or_name, type_)
+
+    res = GObjectModule.signal_query(id_or_name)
+    if res is None:
+        return None
+
+    if res.signal_id == 0:
+        return None
+
+    # Return a named tuple to allows indexing which is compatible with the
+    # static bindings along with field like access of the gi struct.
+    # Note however that the n_params was not returned from the static bindings
+    # so we must skip over it.
+    return SignalQuery(res.signal_id, res.signal_name, res.itype,
+                       res.signal_flags, res.return_type,
+                       tuple(res.param_types))
+
+
+__all__.append('signal_query')
+
+
+class _HandlerBlockManager(object):
+    def __init__(self, obj, handler_id):
+        self.obj = obj
+        self.handler_id = handler_id
+
+    def __enter__(self):
+        pass
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        GObjectModule.signal_handler_unblock(self.obj, self.handler_id)
+
+
+def signal_handler_block(obj, handler_id):
+    """Blocks the signal handler from being invoked until
+    handler_unblock() is called.
+
+    :param GObject.Object obj:
+        Object instance to block handlers for.
+    :param int handler_id:
+        Id of signal to block.
+    :returns:
+        A context manager which optionally can be used to
+        automatically unblock the handler:
+
+    .. code-block:: python
+
+        with GObject.signal_handler_block(obj, id):
+            pass
+    """
+    GObjectModule.signal_handler_block(obj, handler_id)
+    return _HandlerBlockManager(obj, handler_id)
+
+
+__all__.append('signal_handler_block')
+
+
+def signal_parse_name(detailed_signal, itype, force_detail_quark):
+    """Parse a detailed signal name into (signal_id, detail).
+
+    :param str detailed_signal:
+        Signal name which can include detail.
+        For example: "notify:prop_name"
+    :returns:
+        Tuple of (signal_id, detail)
+    :raises ValueError:
+        If the given signal is unknown.
+    """
+    res, signal_id, detail = GObjectModule.signal_parse_name(detailed_signal, itype,
+                                                             force_detail_quark)
+    if res:
+        return signal_id, detail
+    else:
+        raise ValueError('%s: unknown signal name: %s' % (itype, detailed_signal))
+
+
+__all__.append('signal_parse_name')
+
+
+def remove_emission_hook(obj, detailed_signal, hook_id):
+    signal_id, detail = signal_parse_name(detailed_signal, obj, True)
+    GObjectModule.signal_remove_emission_hook(signal_id, hook_id)
+
+
+__all__.append('remove_emission_hook')
+
+
+# GObject accumulators with pure Python implementations
+# These return a tuple of (continue_emission, accumulation_result)
+
+def signal_accumulator_first_wins(ihint, return_accu, handler_return, user_data=None):
+    # Stop emission but return the result of the last handler
+    return (False, handler_return)
+
+
+__all__.append('signal_accumulator_first_wins')
+
+
+def signal_accumulator_true_handled(ihint, return_accu, handler_return, user_data=None):
+    # Stop emission if the last handler returns True
+    return (not handler_return, handler_return)
+
+
+__all__.append('signal_accumulator_true_handled')
+
+
+# Statically bound signal functions which need to clobber GI (for now)
+
+add_emission_hook = _gi.add_emission_hook
+signal_new = _gi.signal_new
+
+__all__ += ['add_emission_hook', 'signal_new']
+
+
+class _FreezeNotifyManager(object):
+    def __init__(self, obj):
+        self.obj = obj
+
+    def __enter__(self):
+        pass
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.obj.thaw_notify()
+
+
+def _signalmethod(func):
+    # Function wrapper for signal functions used as instance methods.
+    # This is needed when the signal functions come directly from GI.
+    # (they are not already wrapped)
+    @gi.overrides.wraps(func)
+    def meth(*args, **kwargs):
+        return func(*args, **kwargs)
+    return meth
+
+
+class Object(GObjectModule.Object):
+    def _unsupported_method(self, *args, **kargs):
+        raise RuntimeError('This method is currently unsupported.')
+
+    def _unsupported_data_method(self, *args, **kargs):
+        raise RuntimeError('Data access methods are unsupported. '
+                           'Use normal Python attributes instead')
+
+    # Generic data methods are not needed in python as it can be handled
+    # with standard attribute access: https://bugzilla.gnome.org/show_bug.cgi?id=641944
+    get_data = _unsupported_data_method
+    get_qdata = _unsupported_data_method
+    set_data = _unsupported_data_method
+    steal_data = _unsupported_data_method
+    steal_qdata = _unsupported_data_method
+    replace_data = _unsupported_data_method
+    replace_qdata = _unsupported_data_method
+
+    # The following methods as unsupported until we verify
+    # they work as gi methods.
+    bind_property_full = _unsupported_method
+    compat_control = _unsupported_method
+    interface_find_property = _unsupported_method
+    interface_install_property = _unsupported_method
+    interface_list_properties = _unsupported_method
+    notify_by_pspec = _unsupported_method
+    run_dispose = _unsupported_method
+    watch_closure = _unsupported_method
+
+    # Make all reference management methods private but still accessible.
+    _ref = GObjectModule.Object.ref
+    _ref_sink = GObjectModule.Object.ref_sink
+    _unref = GObjectModule.Object.unref
+    _force_floating = GObjectModule.Object.force_floating
+
+    ref = _unsupported_method
+    ref_sink = _unsupported_method
+    unref = _unsupported_method
+    force_floating = _unsupported_method
+
+    # The following methods are static APIs which need to leap frog the
+    # gi methods until we verify the gi methods can replace them.
+    get_property = _gi.GObject.get_property
+    get_properties = _gi.GObject.get_properties
+    set_property = _gi.GObject.set_property
+    set_properties = _gi.GObject.set_properties
+    bind_property = _gi.GObject.bind_property
+    connect = _gi.GObject.connect
+    connect_after = _gi.GObject.connect_after
+    connect_object = _gi.GObject.connect_object
+    connect_object_after = _gi.GObject.connect_object_after
+    disconnect_by_func = _gi.GObject.disconnect_by_func
+    handler_block_by_func = _gi.GObject.handler_block_by_func
+    handler_unblock_by_func = _gi.GObject.handler_unblock_by_func
+    emit = _gi.GObject.emit
+    chain = _gi.GObject.chain
+    weak_ref = _gi.GObject.weak_ref
+    __copy__ = _gi.GObject.__copy__
+    __deepcopy__ = _gi.GObject.__deepcopy__
+
+    def freeze_notify(self):
+        """Freezes the object's property-changed notification queue.
+
+        :returns:
+            A context manager which optionally can be used to
+            automatically thaw notifications.
+
+        This will freeze the object so that "notify" signals are blocked until
+        the thaw_notify() method is called.
+
+        .. code-block:: python
+
+            with obj.freeze_notify():
+                pass
+        """
+        super(Object, self).freeze_notify()
+        return _FreezeNotifyManager(self)
+
+    def connect_data(self, detailed_signal, handler, *data, **kwargs):
+        """Connect a callback to the given signal with optional user data.
+
+        :param str detailed_signal:
+            A detailed signal to connect to.
+        :param callable handler:
+            Callback handler to connect to the signal.
+        :param *data:
+            Variable data which is passed through to the signal handler.
+        :param GObject.ConnectFlags connect_flags:
+            Flags used for connection options.
+        :returns:
+            A signal id which can be used with disconnect.
+        """
+        flags = kwargs.get('connect_flags', 0)
+        if flags & GObjectModule.ConnectFlags.AFTER:
+            connect_func = _gi.GObject.connect_after
+        else:
+            connect_func = _gi.GObject.connect
+
+        if flags & GObjectModule.ConnectFlags.SWAPPED:
+            if len(data) != 1:
+                raise ValueError('Using GObject.ConnectFlags.SWAPPED requires exactly '
+                                 'one argument for user data, got: %s' % [data])
+
+            def new_handler(obj, *args):
+                # Swap obj with the last element in args which will be the user
+                # data passed to the connect function.
+                args = list(args)
+                swap = args.pop()
+                args = args + [obj]
+                return handler(swap, *args)
+        else:
+            new_handler = handler
+
+        return connect_func(self, detailed_signal, new_handler, *data)
+
+    #
+    # Aliases
+    #
+
+    handler_block = signal_handler_block
+    handler_unblock = _signalmethod(GObjectModule.signal_handler_unblock)
+    disconnect = _signalmethod(GObjectModule.signal_handler_disconnect)
+    handler_disconnect = _signalmethod(GObjectModule.signal_handler_disconnect)
+    handler_is_connected = _signalmethod(GObjectModule.signal_handler_is_connected)
+    stop_emission_by_name = _signalmethod(GObjectModule.signal_stop_emission_by_name)
+
+    #
+    # Deprecated Methods
+    #
+
+    def stop_emission(self, detailed_signal):
+        """Deprecated, please use stop_emission_by_name."""
+        warnings.warn(self.stop_emission.__doc__, PyGIDeprecationWarning, stacklevel=2)
+        return self.stop_emission_by_name(detailed_signal)
+
+    emit_stop_by_name = stop_emission
+
+
+Object = override(Object)
+GObject = Object
+__all__ += ['Object', 'GObject']
+
+
+class Binding(GObjectModule.Binding):
+    def __call__(self):
+        warnings.warn('Using parentheses (binding()) to retrieve the Binding object is no '
+                      'longer needed because the binding is returned directly from "bind_property.',
+                      PyGIDeprecationWarning, stacklevel=2)
+        return self
+
+    def unbind(self):
+        # Fixed in newer glib
+        if (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION) >= (2, 57, 3):
+            return super(Binding, self).unbind()
+
+        if hasattr(self, '_unbound'):
+            raise ValueError('binding has already been cleared out')
+        else:
+            setattr(self, '_unbound', True)
+            super(Binding, self).unbind()
+
+
+Binding = override(Binding)
+__all__.append('Binding')
+
+
+Property = propertyhelper.Property
+Signal = signalhelper.Signal
+SignalOverride = signalhelper.SignalOverride
+# Deprecated naming "property" available for backwards compatibility.
+# Keep this at the end of the file to avoid clobbering the builtin.
+property = Property
+deprecated_attr("GObject", "property", "GObject.Property")
+__all__ += ['Property', 'Signal', 'SignalOverride', 'property']
diff --git a/gi/overrides/Gdk.py b/gi/overrides/Gdk.py
new file mode 100644 (file)
index 0000000..a1ef5f9
--- /dev/null
@@ -0,0 +1,461 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2009 Johan Dahlin <johan@gnome.org>
+#               2010 Simon van der Linden <svdlinden@src.gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+import sys
+import warnings
+
+from ..overrides import override, strip_boolean_result
+from ..module import get_introspection_module
+from gi import PyGIDeprecationWarning, require_version
+
+Gdk = get_introspection_module('Gdk')
+
+__all__ = []
+
+
+# https://bugzilla.gnome.org/show_bug.cgi?id=673396
+try:
+    require_version("GdkX11", Gdk._version)
+    from gi.repository import GdkX11
+    GdkX11  # pyflakes
+except (ValueError, ImportError):
+    pass
+
+if hasattr(Gdk, 'Color'):
+    # Gdk.Color was deprecated since 3.14 and dropped in Gtk+-4.0
+    class Color(Gdk.Color):
+        MAX_VALUE = 65535
+
+        def __init__(self, red, green, blue):
+            Gdk.Color.__init__(self)
+            self.red = red
+            self.green = green
+            self.blue = blue
+
+        def __eq__(self, other):
+            return self.equal(other)
+
+        def __repr__(self):
+            return 'Gdk.Color(red=%d, green=%d, blue=%d)' % (self.red, self.green, self.blue)
+
+        red_float = property(fget=lambda self: self.red / float(self.MAX_VALUE),
+                             fset=lambda self, v: setattr(self, 'red', int(v * self.MAX_VALUE)))
+
+        green_float = property(fget=lambda self: self.green / float(self.MAX_VALUE),
+                               fset=lambda self, v: setattr(self, 'green', int(v * self.MAX_VALUE)))
+
+        blue_float = property(fget=lambda self: self.blue / float(self.MAX_VALUE),
+                              fset=lambda self, v: setattr(self, 'blue', int(v * self.MAX_VALUE)))
+
+        def to_floats(self):
+            """Return (red_float, green_float, blue_float) triple."""
+
+            return (self.red_float, self.green_float, self.blue_float)
+
+        @staticmethod
+        def from_floats(red, green, blue):
+            """Return a new Color object from red/green/blue values from 0.0 to 1.0."""
+
+            return Color(int(red * Color.MAX_VALUE),
+                         int(green * Color.MAX_VALUE),
+                         int(blue * Color.MAX_VALUE))
+
+    Color = override(Color)
+    __all__.append('Color')
+
+if hasattr(Gdk, 'RGBA'):
+    # Introduced since Gtk+-3.0
+    class RGBA(Gdk.RGBA):
+        def __init__(self, red=1.0, green=1.0, blue=1.0, alpha=1.0):
+            Gdk.RGBA.__init__(self)
+            self.red = red
+            self.green = green
+            self.blue = blue
+            self.alpha = alpha
+
+        def __eq__(self, other):
+            return self.equal(other)
+
+        def __repr__(self):
+            return 'Gdk.RGBA(red=%f, green=%f, blue=%f, alpha=%f)' % (self.red, self.green, self.blue, self.alpha)
+
+        def __iter__(self):
+            """Iterator which allows easy conversion to tuple and list types."""
+
+            yield self.red
+            yield self.green
+            yield self.blue
+            yield self.alpha
+
+        def to_color(self):
+            """Converts this RGBA into a Color instance which excludes alpha."""
+
+            return Color(int(self.red * Color.MAX_VALUE),
+                         int(self.green * Color.MAX_VALUE),
+                         int(self.blue * Color.MAX_VALUE))
+
+        @classmethod
+        def from_color(cls, color):
+            """Returns a new RGBA instance given a Color instance."""
+
+            return cls(color.red_float, color.green_float, color.blue_float)
+
+    RGBA = override(RGBA)
+    __all__.append('RGBA')
+
+if Gdk._version == '2.0':
+    class Rectangle(Gdk.Rectangle):
+
+        def __init__(self, x, y, width, height):
+            Gdk.Rectangle.__init__(self)
+            self.x = x
+            self.y = y
+            self.width = width
+            self.height = height
+
+        def __repr__(self):
+            return 'Gdk.Rectangle(x=%d, y=%d, width=%d, height=%d)' % (self.x, self.y, self.height, self.width)
+
+    Rectangle = override(Rectangle)
+    __all__.append('Rectangle')
+else:
+    # Newer GTK+/gobject-introspection (3.17.x) include GdkRectangle in the
+    # typelib. See https://bugzilla.gnome.org/show_bug.cgi?id=748832 and
+    # https://bugzilla.gnome.org/show_bug.cgi?id=748833
+    if not hasattr(Gdk, 'Rectangle'):
+        from gi.repository import cairo as _cairo
+        Rectangle = _cairo.RectangleInt
+
+        __all__.append('Rectangle')
+    else:
+        # https://bugzilla.gnome.org/show_bug.cgi?id=756364
+        # These methods used to be functions, keep aliases for backwards compat
+        rectangle_intersect = Gdk.Rectangle.intersect
+        rectangle_union = Gdk.Rectangle.union
+
+        __all__.append('rectangle_intersect')
+        __all__.append('rectangle_union')
+
+if Gdk._version == '2.0':
+    class Drawable(Gdk.Drawable):
+        def cairo_create(self):
+            return Gdk.cairo_create(self)
+
+    Drawable = override(Drawable)
+    __all__.append('Drawable')
+else:
+    class Window(Gdk.Window):
+        def __new__(cls, parent, attributes, attributes_mask):
+            # Gdk.Window had to be made abstract,
+            # this override allows using the standard constructor
+            return Gdk.Window.new(parent, attributes, attributes_mask)
+
+        def __init__(self, parent, attributes, attributes_mask):
+            pass
+
+        def cairo_create(self):
+            return Gdk.cairo_create(self)
+
+    Window = override(Window)
+    __all__.append('Window')
+
+if Gdk._version in ("2.0", "3.0"):
+    Gdk.EventType._2BUTTON_PRESS = getattr(Gdk.EventType, "2BUTTON_PRESS")
+    Gdk.EventType._3BUTTON_PRESS = getattr(Gdk.EventType, "3BUTTON_PRESS")
+
+
+class Event(Gdk.Event):
+    _UNION_MEMBERS = {
+        Gdk.EventType.DELETE: 'any',
+        Gdk.EventType.DESTROY: 'any',
+        Gdk.EventType.EXPOSE: 'expose',
+        Gdk.EventType.MOTION_NOTIFY: 'motion',
+        Gdk.EventType.BUTTON_PRESS: 'button',
+        Gdk.EventType.BUTTON_RELEASE: 'button',
+        Gdk.EventType.KEY_PRESS: 'key',
+        Gdk.EventType.KEY_RELEASE: 'key',
+        Gdk.EventType.ENTER_NOTIFY: 'crossing',
+        Gdk.EventType.LEAVE_NOTIFY: 'crossing',
+        Gdk.EventType.FOCUS_CHANGE: 'focus_change',
+        Gdk.EventType.CONFIGURE: 'configure',
+        Gdk.EventType.MAP: 'any',
+        Gdk.EventType.UNMAP: 'any',
+        Gdk.EventType.PROXIMITY_IN: 'proximity',
+        Gdk.EventType.PROXIMITY_OUT: 'proximity',
+        Gdk.EventType.DRAG_ENTER: 'dnd',
+        Gdk.EventType.DRAG_LEAVE: 'dnd',
+        Gdk.EventType.DRAG_MOTION: 'dnd',
+        Gdk.EventType.DROP_START: 'dnd',
+    }
+
+    if Gdk._version in ("2.0", "3.0"):
+        _UNION_MEMBERS.update({
+            Gdk.EventType._2BUTTON_PRESS: 'button',
+            Gdk.EventType._3BUTTON_PRESS: 'button',
+            Gdk.EventType.PROPERTY_NOTIFY: 'property',
+            Gdk.EventType.SELECTION_CLEAR: 'selection',
+            Gdk.EventType.SELECTION_REQUEST: 'selection',
+            Gdk.EventType.SELECTION_NOTIFY: 'selection',
+            Gdk.EventType.DRAG_STATUS: 'dnd',
+            Gdk.EventType.DROP_FINISHED: 'dnd',
+            Gdk.EventType.CLIENT_EVENT: 'client',
+            Gdk.EventType.VISIBILITY_NOTIFY: 'visibility',
+        })
+
+    if Gdk._version == '2.0':
+        _UNION_MEMBERS[Gdk.EventType.NO_EXPOSE] = 'no_expose'
+
+    if hasattr(Gdk.EventType, 'TOUCH_BEGIN'):
+        _UNION_MEMBERS.update(
+            {
+                Gdk.EventType.TOUCH_BEGIN: 'touch',
+                Gdk.EventType.TOUCH_UPDATE: 'touch',
+                Gdk.EventType.TOUCH_END: 'touch',
+                Gdk.EventType.TOUCH_CANCEL: 'touch',
+            })
+
+    def __getattr__(self, name):
+        real_event = getattr(self, '_UNION_MEMBERS').get(self.type)
+        if real_event:
+            return getattr(getattr(self, real_event), name)
+        else:
+            raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name))
+
+    def __setattr__(self, name, value):
+        real_event = getattr(self, '_UNION_MEMBERS').get(self.type)
+        if real_event:
+            setattr(getattr(self, real_event), name, value)
+        else:
+            Gdk.Event.__setattr__(self, name, value)
+
+    def __repr__(self):
+        base_repr = Gdk.Event.__repr__(self).strip("><")
+        return "<%s type=%r>" % (base_repr, self.type)
+
+
+Event = override(Event)
+__all__.append('Event')
+
+# manually bind GdkEvent members to GdkEvent
+
+modname = globals()['__name__']
+module = sys.modules[modname]
+
+# right now we can't get the type_info from the
+# field info so manually list the class names
+event_member_classes = ['EventAny',
+                        'EventExpose',
+                        'EventMotion',
+                        'EventButton',
+                        'EventScroll',
+                        'EventKey',
+                        'EventCrossing',
+                        'EventFocus',
+                        'EventConfigure',
+                        'EventProximity',
+                        'EventDND',
+                        'EventSetting',
+                        'EventGrabBroken']
+
+if Gdk._version in ("2.0", "3.0"):
+    event_member_classes.extend([
+        'EventVisibility',
+        'EventProperty',
+        'EventSelection',
+        'EventOwnerChange',
+        'EventWindowState',
+        'EventVisibility',
+    ])
+
+if Gdk._version == '2.0':
+    event_member_classes.append('EventNoExpose')
+
+if hasattr(Gdk, 'EventTouch'):
+    event_member_classes.append('EventTouch')
+
+
+# whitelist all methods that have a success return we want to mask
+gsuccess_mask_funcs = ['get_state',
+                       'get_axis',
+                       'get_coords',
+                       'get_root_coords']
+
+
+for event_class in event_member_classes:
+    override_class = type(event_class, (getattr(Gdk, event_class),), {})
+    # add the event methods
+    for method_info in Gdk.Event.__info__.get_methods():
+        name = method_info.get_name()
+        event_method = getattr(Gdk.Event, name)
+        # python2 we need to use the __func__ attr to avoid internal
+        # instance checks
+        event_method = getattr(event_method, '__func__', event_method)
+
+        # use the _gsuccess_mask decorator if this method is whitelisted
+        if name in gsuccess_mask_funcs:
+            event_method = strip_boolean_result(event_method)
+        setattr(override_class, name, event_method)
+
+    setattr(module, event_class, override_class)
+    __all__.append(event_class)
+
+# end GdkEvent overrides
+
+
+class DragContext(Gdk.DragContext):
+    def finish(self, success, del_, time):
+        Gtk = get_introspection_module('Gtk')
+        Gtk.drag_finish(self, success, del_, time)
+
+
+DragContext = override(DragContext)
+__all__.append('DragContext')
+
+
+class Cursor(Gdk.Cursor):
+
+    def __new__(cls, *args, **kwds):
+        arg_len = len(args)
+        kwd_len = len(kwds)
+        total_len = arg_len + kwd_len
+
+        if total_len == 1:
+            if Gdk._version == "4.0":
+                raise ValueError("Wrong number of parameters")
+            # Since g_object_newv (super.__new__) does not seem valid for
+            # direct use with GdkCursor, we must assume usage of at least
+            # one of the C constructors to be valid.
+            return cls.new(*args, **kwds)
+
+        elif total_len == 2:
+            warnings.warn('Calling "Gdk.Cursor(display, cursor_type)" has been deprecated. '
+                          'Please use Gdk.Cursor.new_for_display(display, cursor_type). '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
+                          PyGIDeprecationWarning)
+            return cls.new_for_display(*args, **kwds)
+
+        elif total_len == 4:
+            warnings.warn('Calling "Gdk.Cursor(display, pixbuf, x, y)" has been deprecated. '
+                          'Please use Gdk.Cursor.new_from_pixbuf(display, pixbuf, x, y). '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
+                          PyGIDeprecationWarning)
+            return cls.new_from_pixbuf(*args, **kwds)
+
+        elif total_len == 6:
+            if Gdk._version != '2.0':
+                # pixmaps don't exist in Gdk 3.0
+                raise ValueError("Wrong number of parameters")
+
+            warnings.warn('Calling "Gdk.Cursor(source, mask, fg, bg, x, y)" has been deprecated. '
+                          'Please use Gdk.Cursor.new_from_pixmap(source, mask, fg, bg, x, y). '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
+                          PyGIDeprecationWarning)
+            return cls.new_from_pixmap(*args, **kwds)
+
+        else:
+            raise ValueError("Wrong number of parameters")
+
+
+Cursor = override(Cursor)
+__all__.append('Cursor')
+
+if hasattr(Gdk, 'color_parse'):
+    # Gdk.Color was deprecated since 3.14 and dropped in Gtk+-4.0
+    color_parse = strip_boolean_result(Gdk.color_parse)
+    __all__.append('color_parse')
+
+
+# Note, we cannot override the entire class as Gdk.Atom has no gtype, so just
+# hack some individual methods
+def _gdk_atom_str(atom):
+    n = atom.name()
+    if n:
+        return n
+    # fall back to atom index
+    return 'Gdk.Atom<%i>' % hash(atom)
+
+
+def _gdk_atom_repr(atom):
+    n = atom.name()
+    if n:
+        return 'Gdk.Atom.intern("%s", False)' % n
+    # fall back to atom index
+    return '<Gdk.Atom(%i)>' % hash(atom)
+
+
+if Gdk._version in ("2.0", "3.0"):
+    Gdk.Atom.__str__ = _gdk_atom_str
+    Gdk.Atom.__repr__ = _gdk_atom_repr
+
+
+# constants
+if Gdk._version == '3.0':
+    SELECTION_PRIMARY = Gdk.atom_intern('PRIMARY', True)
+    __all__.append('SELECTION_PRIMARY')
+
+    SELECTION_SECONDARY = Gdk.atom_intern('SECONDARY', True)
+    __all__.append('SELECTION_SECONDARY')
+
+    SELECTION_CLIPBOARD = Gdk.atom_intern('CLIPBOARD', True)
+    __all__.append('SELECTION_CLIPBOARD')
+
+    TARGET_BITMAP = Gdk.atom_intern('BITMAP', True)
+    __all__.append('TARGET_BITMAP')
+
+    TARGET_COLORMAP = Gdk.atom_intern('COLORMAP', True)
+    __all__.append('TARGET_COLORMAP')
+
+    TARGET_DRAWABLE = Gdk.atom_intern('DRAWABLE', True)
+    __all__.append('TARGET_DRAWABLE')
+
+    TARGET_PIXMAP = Gdk.atom_intern('PIXMAP', True)
+    __all__.append('TARGET_PIXMAP')
+
+    TARGET_STRING = Gdk.atom_intern('STRING', True)
+    __all__.append('TARGET_STRING')
+
+    SELECTION_TYPE_ATOM = Gdk.atom_intern('ATOM', True)
+    __all__.append('SELECTION_TYPE_ATOM')
+
+    SELECTION_TYPE_BITMAP = Gdk.atom_intern('BITMAP', True)
+    __all__.append('SELECTION_TYPE_BITMAP')
+
+    SELECTION_TYPE_COLORMAP = Gdk.atom_intern('COLORMAP', True)
+    __all__.append('SELECTION_TYPE_COLORMAP')
+
+    SELECTION_TYPE_DRAWABLE = Gdk.atom_intern('DRAWABLE', True)
+    __all__.append('SELECTION_TYPE_DRAWABLE')
+
+    SELECTION_TYPE_INTEGER = Gdk.atom_intern('INTEGER', True)
+    __all__.append('SELECTION_TYPE_INTEGER')
+
+    SELECTION_TYPE_PIXMAP = Gdk.atom_intern('PIXMAP', True)
+    __all__.append('SELECTION_TYPE_PIXMAP')
+
+    SELECTION_TYPE_WINDOW = Gdk.atom_intern('WINDOW', True)
+    __all__.append('SELECTION_TYPE_WINDOW')
+
+    SELECTION_TYPE_STRING = Gdk.atom_intern('STRING', True)
+    __all__.append('SELECTION_TYPE_STRING')
+
+if Gdk._version in ('2.0', '3.0'):
+    import sys
+    initialized, argv = Gdk.init_check(sys.argv)
diff --git a/gi/overrides/GdkPixbuf.py b/gi/overrides/GdkPixbuf.py
new file mode 100644 (file)
index 0000000..0f6cd75
--- /dev/null
@@ -0,0 +1,53 @@
+# Copyright 2018 Christoph Reiter <reiter.christoph@gmail.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+import warnings
+
+from gi import PyGIDeprecationWarning
+from gi.repository import GLib
+
+from ..overrides import override
+from ..module import get_introspection_module
+
+
+GdkPixbuf = get_introspection_module('GdkPixbuf')
+__all__ = []
+
+
+@override
+class Pixbuf(GdkPixbuf.Pixbuf):
+
+    @classmethod
+    def new_from_data(
+            cls, data, colorspace, has_alpha, bits_per_sample,
+            width, height, rowstride,
+            destroy_fn=None, *destroy_fn_data):
+
+        if destroy_fn is not None:
+            w = PyGIDeprecationWarning("destroy_fn argument deprecated")
+            warnings.warn(w)
+        if destroy_fn_data:
+            w = PyGIDeprecationWarning("destroy_fn_data argument deprecated")
+            warnings.warn(w)
+
+        data = GLib.Bytes.new(data)
+        return cls.new_from_bytes(
+            data, colorspace, has_alpha, bits_per_sample,
+            width, height, rowstride)
+
+
+__all__.append('Pixbuf')
diff --git a/gi/overrides/Gio.py b/gi/overrides/Gio.py
new file mode 100644 (file)
index 0000000..5c19ef2
--- /dev/null
@@ -0,0 +1,503 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Ignacio Casal Quinteiro <icq@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+import warnings
+
+from .._ossighelper import wakeup_on_signal, register_sigint_fallback
+from ..overrides import override, deprecated_init
+from ..module import get_introspection_module
+from .._compat import xrange
+from gi._gi import pygobject_new_full
+from gi import PyGIWarning
+
+from gi.repository import GLib
+
+import sys
+
+Gio = get_introspection_module('Gio')
+
+__all__ = []
+
+
+class Application(Gio.Application):
+
+    def run(self, *args, **kwargs):
+        with register_sigint_fallback(self.quit):
+            with wakeup_on_signal():
+                return Gio.Application.run(self, *args, **kwargs)
+
+
+Application = override(Application)
+__all__.append('Application')
+
+
+class VolumeMonitor(Gio.VolumeMonitor):
+
+    def __init__(self, *args, **kwargs):
+        super(VolumeMonitor, self).__init__(*args, **kwargs)
+
+        # https://bugzilla.gnome.org/show_bug.cgi?id=744690
+        warnings.warn(
+            "Gio.VolumeMonitor shouldn't be instantiated directly, "
+            "use Gio.VolumeMonitor.get() instead.",
+            PyGIWarning, stacklevel=2)
+
+
+VolumeMonitor = override(VolumeMonitor)
+__all__.append('VolumeMonitor')
+
+
+class ActionMap(Gio.ActionMap):
+    def add_action_entries(self, entries, user_data=None):
+        """
+        The add_action_entries() method is a convenience function for creating
+        multiple Gio.SimpleAction instances and adding them to a Gio.ActionMap.
+        Each action is constructed as per one entry.
+
+        :param list entries:
+            List of entry tuples for add_action() method. The entry tuple can
+            vary in size with the following information:
+
+                * The name of the action. Must be specified.
+                * The callback to connect to the "activate" signal of the
+                  action. Since GLib 2.40, this can be None for stateful
+                  actions, in which case the default handler is used. For
+                  boolean-stated actions with no parameter, this is a toggle.
+                  For other state types (and parameter type equal to the state
+                  type) this will be a function that just calls change_state
+                  (which you should provide).
+                * The type of the parameter that must be passed to the activate
+                  function for this action, given as a single GLib.Variant type
+                  string (or None for no parameter)
+                * The initial state for this action, given in GLib.Variant text
+                  format. The state is parsed with no extra type information, so
+                  type tags must be added to the string if they are necessary.
+                  Stateless actions should give None here.
+                * The callback to connect to the "change-state" signal of the
+                  action. All stateful actions should provide a handler here;
+                  stateless actions should not.
+
+        :param user_data:
+            The user data for signal connections, or None
+        """
+        try:
+            iter(entries)
+        except (TypeError):
+            raise TypeError('entries must be iterable')
+
+        def _process_action(name, activate=None, parameter_type=None,
+                            state=None, change_state=None):
+            if parameter_type:
+                if not GLib.VariantType.string_is_valid(parameter_type):
+                    raise TypeError("The type string '%s' given as the "
+                                    "parameter type for action '%s' is "
+                                    "not a valid GVariant type string. " %
+                                    (parameter_type, name))
+                variant_parameter = GLib.VariantType.new(parameter_type)
+            else:
+                variant_parameter = None
+
+            if state is not None:
+                # stateful action
+                variant_state = GLib.Variant.parse(None, state, None, None)
+                action = Gio.SimpleAction.new_stateful(name, variant_parameter,
+                                                       variant_state)
+                if change_state is not None:
+                    action.connect('change-state', change_state, user_data)
+            else:
+                # stateless action
+                if change_state is not None:
+                    raise ValueError("Stateless action '%s' should give "
+                                     "None for 'change_state', not '%s'." %
+                                     (name, change_state))
+                action = Gio.SimpleAction(name=name, parameter_type=variant_parameter)
+
+            if activate is not None:
+                action.connect('activate', activate, user_data)
+            self.add_action(action)
+
+        for entry in entries:
+            # using inner function above since entries can leave out optional arguments
+            _process_action(*entry)
+
+
+ActionMap = override(ActionMap)
+__all__.append('ActionMap')
+
+
+class FileEnumerator(Gio.FileEnumerator):
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        file_info = self.next_file(None)
+
+        if file_info is not None:
+            return file_info
+        else:
+            raise StopIteration
+
+    # python 2 compat for the iter protocol
+    next = __next__
+
+
+FileEnumerator = override(FileEnumerator)
+__all__.append('FileEnumerator')
+
+
+class MenuItem(Gio.MenuItem):
+    def set_attribute(self, attributes):
+        for (name, format_string, value) in attributes:
+            self.set_attribute_value(name, GLib.Variant(format_string, value))
+
+
+MenuItem = override(MenuItem)
+__all__.append('MenuItem')
+
+
+class Settings(Gio.Settings):
+    '''Provide dictionary-like access to GLib.Settings.'''
+
+    __init__ = deprecated_init(Gio.Settings.__init__,
+                               arg_names=('schema', 'path', 'backend'))
+
+    def __contains__(self, key):
+        return key in self.list_keys()
+
+    def __len__(self):
+        return len(self.list_keys())
+
+    def __iter__(self):
+        for key in self.list_keys():
+            yield key
+
+    def __bool__(self):
+        # for "if mysettings" we don't want a dictionary-like test here, just
+        # if the object isn't None
+        return True
+
+    # alias for Python 2.x object protocol
+    __nonzero__ = __bool__
+
+    def __getitem__(self, key):
+        # get_value() aborts the program on an unknown key
+        if key not in self:
+            raise KeyError('unknown key: %r' % (key,))
+
+        return self.get_value(key).unpack()
+
+    def __setitem__(self, key, value):
+        # set_value() aborts the program on an unknown key
+        if key not in self:
+            raise KeyError('unknown key: %r' % (key,))
+
+        # determine type string of this key
+        range = self.get_range(key)
+        type_ = range.get_child_value(0).get_string()
+        v = range.get_child_value(1)
+        if type_ == 'type':
+            # v is boxed empty array, type of its elements is the allowed value type
+            type_str = v.get_child_value(0).get_type_string()
+            assert type_str.startswith('a')
+            type_str = type_str[1:]
+        elif type_ == 'enum':
+            # v is an array with the allowed values
+            assert v.get_child_value(0).get_type_string().startswith('a')
+            type_str = v.get_child_value(0).get_child_value(0).get_type_string()
+            allowed = v.unpack()
+            if value not in allowed:
+                raise ValueError('value %s is not an allowed enum (%s)' % (value, allowed))
+        elif type_ == 'range':
+            tuple_ = v.get_child_value(0)
+            type_str = tuple_.get_child_value(0).get_type_string()
+            min_, max_ = tuple_.unpack()
+            if value < min_ or value > max_:
+                raise ValueError(
+                    'value %s not in range (%s - %s)' % (value, min_, max_))
+        else:
+            raise NotImplementedError('Cannot handle allowed type range class ' + str(type_))
+
+        self.set_value(key, GLib.Variant(type_str, value))
+
+    def keys(self):
+        return self.list_keys()
+
+
+Settings = override(Settings)
+__all__.append('Settings')
+
+
+class _DBusProxyMethodCall:
+    '''Helper class to implement DBusProxy method calls.'''
+
+    def __init__(self, dbus_proxy, method_name):
+        self.dbus_proxy = dbus_proxy
+        self.method_name = method_name
+
+    def __async_result_handler(self, obj, result, user_data):
+        (result_callback, error_callback, real_user_data) = user_data
+        try:
+            ret = obj.call_finish(result)
+        except Exception:
+            etype, e = sys.exc_info()[:2]
+            # return exception as value
+            if error_callback:
+                error_callback(obj, e, real_user_data)
+            else:
+                result_callback(obj, e, real_user_data)
+            return
+
+        result_callback(obj, self._unpack_result(ret), real_user_data)
+
+    def __call__(self, *args, **kwargs):
+        # the first positional argument is the signature, unless we are calling
+        # a method without arguments; then signature is implied to be '()'.
+        if args:
+            signature = args[0]
+            args = args[1:]
+            if not isinstance(signature, str):
+                raise TypeError('first argument must be the method signature string: %r' % signature)
+        else:
+            signature = '()'
+
+        arg_variant = GLib.Variant(signature, tuple(args))
+
+        if 'result_handler' in kwargs:
+            # asynchronous call
+            user_data = (kwargs['result_handler'],
+                         kwargs.get('error_handler'),
+                         kwargs.get('user_data'))
+            self.dbus_proxy.call(self.method_name, arg_variant,
+                                 kwargs.get('flags', 0), kwargs.get('timeout', -1), None,
+                                 self.__async_result_handler, user_data)
+        else:
+            # synchronous call
+            result = self.dbus_proxy.call_sync(self.method_name, arg_variant,
+                                               kwargs.get('flags', 0),
+                                               kwargs.get('timeout', -1),
+                                               None)
+            return self._unpack_result(result)
+
+    @classmethod
+    def _unpack_result(klass, result):
+        '''Convert a D-BUS return variant into an appropriate return value'''
+
+        result = result.unpack()
+
+        # to be compatible with standard Python behaviour, unbox
+        # single-element tuples and return None for empty result tuples
+        if len(result) == 1:
+            result = result[0]
+        elif len(result) == 0:
+            result = None
+
+        return result
+
+
+class DBusProxy(Gio.DBusProxy):
+    '''Provide comfortable and pythonic method calls.
+
+    This marshalls the method arguments into a GVariant, invokes the
+    call_sync() method on the DBusProxy object, and unmarshalls the result
+    GVariant back into a Python tuple.
+
+    The first argument always needs to be the D-Bus signature tuple of the
+    method call. Example:
+
+      proxy = Gio.DBusProxy.new_sync(...)
+      result = proxy.MyMethod('(is)', 42, 'hello')
+
+    The exception are methods which take no arguments, like
+    proxy.MyMethod('()'). For these you can omit the signature and just write
+    proxy.MyMethod().
+
+    Optional keyword arguments:
+
+    - timeout: timeout for the call in milliseconds (default to D-Bus timeout)
+
+    - flags: Combination of Gio.DBusCallFlags.*
+
+    - result_handler: Do an asynchronous method call and invoke
+         result_handler(proxy_object, result, user_data) when it finishes.
+
+    - error_handler: If the asynchronous call raises an exception,
+      error_handler(proxy_object, exception, user_data) is called when it
+      finishes. If error_handler is not given, result_handler is called with
+      the exception object as result instead.
+
+    - user_data: Optional user data to pass to result_handler for
+      asynchronous calls.
+
+    Example for asynchronous calls:
+
+      def mymethod_done(proxy, result, user_data):
+          if isinstance(result, Exception):
+              # handle error
+          else:
+              # do something with result
+
+      proxy.MyMethod('(is)', 42, 'hello',
+          result_handler=mymethod_done, user_data='data')
+    '''
+    def __getattr__(self, name):
+        return _DBusProxyMethodCall(self, name)
+
+
+DBusProxy = override(DBusProxy)
+__all__.append('DBusProxy')
+
+
+class ListModel(Gio.ListModel):
+
+    def __getitem__(self, key):
+        if isinstance(key, slice):
+            return [self.get_item(i) for i in xrange(*key.indices(len(self)))]
+        elif isinstance(key, int):
+            if key < 0:
+                key += len(self)
+            if key < 0:
+                raise IndexError
+            ret = self.get_item(key)
+            if ret is None:
+                raise IndexError
+            return ret
+        else:
+            raise TypeError
+
+    def __contains__(self, item):
+        pytype = self.get_item_type().pytype
+        if not isinstance(item, pytype):
+            raise TypeError(
+                "Expected type %s.%s" % (pytype.__module__, pytype.__name__))
+        for i in self:
+            if i == item:
+                return True
+        return False
+
+    def __len__(self):
+        return self.get_n_items()
+
+    def __iter__(self):
+        for i in xrange(len(self)):
+            yield self.get_item(i)
+
+
+ListModel = override(ListModel)
+__all__.append('ListModel')
+
+
+def _wrap_list_store_sort_func(func):
+
+    def wrap(a, b, *user_data):
+        a = pygobject_new_full(a, False)
+        b = pygobject_new_full(b, False)
+        return func(a, b, *user_data)
+
+    return wrap
+
+
+if (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION) < (2, 57, 1):
+    # The "additions" functionality in splice() was broken in older glib
+    # https://bugzilla.gnome.org/show_bug.cgi?id=795307
+    # This is a slower fallback which emits a signal per added item
+    def _list_store_splice(self, position, n_removals, additions):
+        self.splice(position, n_removals, [])
+        for v in reversed(additions):
+            self.insert(position, v)
+else:
+    def _list_store_splice(self, position, n_removals, additions):
+        self.splice(position, n_removals, additions)
+
+
+class ListStore(Gio.ListStore):
+
+    def sort(self, compare_func, *user_data):
+        compare_func = _wrap_list_store_sort_func(compare_func)
+        return super(ListStore, self).sort(compare_func, *user_data)
+
+    def insert_sorted(self, item, compare_func, *user_data):
+        compare_func = _wrap_list_store_sort_func(compare_func)
+        return super(ListStore, self).insert_sorted(
+            item, compare_func, *user_data)
+
+    def __delitem__(self, key):
+        if isinstance(key, slice):
+            start, stop, step = key.indices(len(self))
+            if step == 1:
+                _list_store_splice(self, start, max(stop - start, 0), [])
+            elif step == -1:
+                _list_store_splice(self, stop + 1, max(start - stop, 0), [])
+            else:
+                for i in sorted(xrange(start, stop, step), reverse=True):
+                    self.remove(i)
+        elif isinstance(key, int):
+            if key < 0:
+                key += len(self)
+            if key < 0 or key >= len(self):
+                raise IndexError
+            self.remove(key)
+        else:
+            raise TypeError
+
+    def __setitem__(self, key, value):
+        if isinstance(key, slice):
+            pytype = self.get_item_type().pytype
+            valuelist = []
+            for v in value:
+                if not isinstance(v, pytype):
+                    raise TypeError(
+                        "Expected type %s.%s" % (
+                            pytype.__module__, pytype.__name__))
+                valuelist.append(v)
+
+            start, stop, step = key.indices(len(self))
+            if step == 1:
+                _list_store_splice(
+                    self, start, max(stop - start, 0), valuelist)
+            else:
+                indices = list(xrange(start, stop, step))
+                if len(indices) != len(valuelist):
+                    raise ValueError
+
+                if step == -1:
+                    _list_store_splice(
+                        self, stop + 1, max(start - stop, 0), valuelist[::-1])
+                else:
+                    for i, v in zip(indices, valuelist):
+                        _list_store_splice(self, i, 1, [v])
+        elif isinstance(key, int):
+            if key < 0:
+                key += len(self)
+            if key < 0 or key >= len(self):
+                raise IndexError
+
+            pytype = self.get_item_type().pytype
+            if not isinstance(value, pytype):
+                raise TypeError(
+                    "Expected type %s.%s" % (
+                        pytype.__module__, pytype.__name__))
+
+            _list_store_splice(self, key, 1, [value])
+        else:
+            raise TypeError
+
+
+ListStore = override(ListStore)
+__all__.append('ListStore')
diff --git a/gi/overrides/Gtk.py b/gi/overrides/Gtk.py
new file mode 100644 (file)
index 0000000..05da18a
--- /dev/null
@@ -0,0 +1,1640 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2009 Johan Dahlin <johan@gnome.org>
+#               2010 Simon van der Linden <svdlinden@src.gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+import sys
+import warnings
+
+if sys.version_info[0] == 2:
+    import collections as abc
+else:
+    from collections import abc
+
+from gi.repository import GObject
+from .._ossighelper import wakeup_on_signal, register_sigint_fallback
+from .._gtktemplate import Template
+from ..overrides import override, strip_boolean_result, deprecated_init
+from ..module import get_introspection_module
+from .._compat import string_types
+from gi import PyGIDeprecationWarning
+
+
+Gtk = get_introspection_module('Gtk')
+
+__all__ = []
+
+
+Template = Template
+__all__.append('Template')
+
+if Gtk._version == '2.0':
+    warn_msg = "You have imported the Gtk 2.0 module.  Because Gtk 2.0 \
+was not designed for use with introspection some of the \
+interfaces and API will fail.  As such this is not supported \
+by the pygobject development team and we encourage you to \
+port your app to Gtk 3 or greater. PyGTK is the recomended \
+python module to use with Gtk 2.0"
+
+    warnings.warn(warn_msg, RuntimeWarning)
+
+
+class PyGTKDeprecationWarning(PyGIDeprecationWarning):
+    pass
+
+
+__all__.append('PyGTKDeprecationWarning')
+
+
+def _construct_target_list(targets):
+    """Create a list of TargetEntry items from a list of tuples in the form (target, flags, info)
+
+    The list can also contain existing TargetEntry items in which case the existing entry
+    is re-used in the return list.
+    """
+    target_entries = []
+    for entry in targets:
+        if not isinstance(entry, Gtk.TargetEntry):
+            entry = Gtk.TargetEntry.new(*entry)
+        target_entries.append(entry)
+    return target_entries
+
+
+__all__.append('_construct_target_list')
+
+
+def _extract_handler_and_args(obj_or_map, handler_name):
+    handler = None
+    if isinstance(obj_or_map, abc.Mapping):
+        handler = obj_or_map.get(handler_name, None)
+    else:
+        handler = getattr(obj_or_map, handler_name, None)
+
+    if handler is None:
+        raise AttributeError('Handler %s not found' % handler_name)
+
+    args = ()
+    if isinstance(handler, abc.Sequence):
+        if len(handler) == 0:
+            raise TypeError("Handler %s tuple can not be empty" % handler)
+        args = handler[1:]
+        handler = handler[0]
+
+    elif not callable(handler):
+        raise TypeError('Handler %s is not a method, function or tuple' % handler)
+
+    return handler, args
+
+
+# Exposed for unit-testing.
+__all__.append('_extract_handler_and_args')
+
+
+def _builder_connect_callback(builder, gobj, signal_name, handler_name, connect_obj, flags, obj_or_map):
+    handler, args = _extract_handler_and_args(obj_or_map, handler_name)
+
+    after = flags & GObject.ConnectFlags.AFTER
+    if connect_obj is not None:
+        if after:
+            gobj.connect_object_after(signal_name, handler, connect_obj, *args)
+        else:
+            gobj.connect_object(signal_name, handler, connect_obj, *args)
+    else:
+        if after:
+            gobj.connect_after(signal_name, handler, *args)
+        else:
+            gobj.connect(signal_name, handler, *args)
+
+
+class _FreezeNotifyManager(object):
+    def __init__(self, obj):
+        self.obj = obj
+
+    def __enter__(self):
+        pass
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.obj.thaw_child_notify()
+
+
+class Widget(Gtk.Widget):
+
+    translate_coordinates = strip_boolean_result(Gtk.Widget.translate_coordinates)
+
+    def freeze_child_notify(self):
+        super(Widget, self).freeze_child_notify()
+        return _FreezeNotifyManager(self)
+
+    def drag_dest_set_target_list(self, target_list):
+        if (target_list is not None) and (not isinstance(target_list, Gtk.TargetList)):
+            target_list = Gtk.TargetList.new(_construct_target_list(target_list))
+        super(Widget, self).drag_dest_set_target_list(target_list)
+
+    def drag_source_set_target_list(self, target_list):
+        if (target_list is not None) and (not isinstance(target_list, Gtk.TargetList)):
+            target_list = Gtk.TargetList.new(_construct_target_list(target_list))
+        super(Widget, self).drag_source_set_target_list(target_list)
+
+    def style_get_property(self, property_name, value=None):
+        if value is None:
+            prop = self.find_style_property(property_name)
+            if prop is None:
+                raise ValueError('Class "%s" does not contain style property "%s"' %
+                                 (self, property_name))
+            value = GObject.Value(prop.value_type)
+
+        Gtk.Widget.style_get_property(self, property_name, value)
+        return value.get_value()
+
+
+Widget = override(Widget)
+__all__.append('Widget')
+
+
+class Container(Gtk.Container, Widget):
+
+    def __len__(self):
+        return len(self.get_children())
+
+    def __contains__(self, child):
+        return child in self.get_children()
+
+    def __iter__(self):
+        return iter(self.get_children())
+
+    def __bool__(self):
+        return True
+
+    # alias for Python 2.x object protocol
+    __nonzero__ = __bool__
+
+    get_focus_chain = strip_boolean_result(Gtk.Container.get_focus_chain)
+
+    def child_get_property(self, child, property_name, value=None):
+        if value is None:
+            prop = self.find_child_property(property_name)
+            if prop is None:
+                raise ValueError('Class "%s" does not contain child property "%s"' %
+                                 (self, property_name))
+            value = GObject.Value(prop.value_type)
+
+        Gtk.Container.child_get_property(self, child, property_name, value)
+        return value.get_value()
+
+    def child_get(self, child, *prop_names):
+        """Returns a list of child property values for the given names."""
+        return [self.child_get_property(child, name) for name in prop_names]
+
+    def child_set(self, child, **kwargs):
+        """Set a child properties on the given child to key/value pairs."""
+        for name, value in kwargs.items():
+            name = name.replace('_', '-')
+            self.child_set_property(child, name, value)
+
+
+Container = override(Container)
+__all__.append('Container')
+
+
+class Editable(Gtk.Editable):
+
+    def insert_text(self, text, position):
+        return super(Editable, self).insert_text(text, -1, position)
+
+    get_selection_bounds = strip_boolean_result(Gtk.Editable.get_selection_bounds, fail_ret=())
+
+
+Editable = override(Editable)
+__all__.append("Editable")
+
+
+if Gtk._version in ("2.0", "3.0"):
+    class Action(Gtk.Action):
+        __init__ = deprecated_init(Gtk.Action.__init__,
+                                   arg_names=('name', 'label', 'tooltip', 'stock_id'),
+                                   category=PyGTKDeprecationWarning)
+
+    Action = override(Action)
+    __all__.append("Action")
+
+    class RadioAction(Gtk.RadioAction):
+        __init__ = deprecated_init(Gtk.RadioAction.__init__,
+                                   arg_names=('name', 'label', 'tooltip', 'stock_id', 'value'),
+                                   category=PyGTKDeprecationWarning)
+
+    RadioAction = override(RadioAction)
+    __all__.append("RadioAction")
+
+    class ActionGroup(Gtk.ActionGroup):
+        __init__ = deprecated_init(Gtk.ActionGroup.__init__,
+                                   arg_names=('name',),
+                                   category=PyGTKDeprecationWarning)
+
+        def add_actions(self, entries, user_data=None):
+            """
+            The add_actions() method is a convenience method that creates a number
+            of gtk.Action  objects based on the information in the list of action
+            entry tuples contained in entries and adds them to the action group.
+            The entry tuples can vary in size from one to six items with the
+            following information:
+
+                * The name of the action. Must be specified.
+                * The stock id for the action. Optional with a default value of None
+                  if a label is specified.
+                * The label for the action. This field should typically be marked
+                  for translation, see the set_translation_domain() method. Optional
+                  with a default value of None if a stock id is specified.
+                * The accelerator for the action, in the format understood by the
+                  gtk.accelerator_parse() function. Optional with a default value of
+                  None.
+                * The tooltip for the action. This field should typically be marked
+                  for translation, see the set_translation_domain() method. Optional
+                  with a default value of None.
+                * The callback function invoked when the action is activated.
+                  Optional with a default value of None.
+
+            The "activate" signals of the actions are connected to the callbacks and
+            their accel paths are set to <Actions>/group-name/action-name.
+            """
+            try:
+                iter(entries)
+            except (TypeError):
+                raise TypeError('entries must be iterable')
+
+            def _process_action(name, stock_id=None, label=None, accelerator=None, tooltip=None, callback=None):
+                action = Action(name=name, label=label, tooltip=tooltip, stock_id=stock_id)
+                if callback is not None:
+                    if user_data is None:
+                        action.connect('activate', callback)
+                    else:
+                        action.connect('activate', callback, user_data)
+
+                self.add_action_with_accel(action, accelerator)
+
+            for e in entries:
+                # using inner function above since entries can leave out optional arguments
+                _process_action(*e)
+
+        def add_toggle_actions(self, entries, user_data=None):
+            """
+            The add_toggle_actions() method is a convenience method that creates a
+            number of gtk.ToggleAction objects based on the information in the list
+            of action entry tuples contained in entries and adds them to the action
+            group. The toggle action entry tuples can vary in size from one to seven
+            items with the following information:
+
+                * The name of the action. Must be specified.
+                * The stock id for the action. Optional with a default value of None
+                  if a label is specified.
+                * The label for the action. This field should typically be marked
+                  for translation, see the set_translation_domain() method. Optional
+                  with a default value of None if a stock id is specified.
+                * The accelerator for the action, in the format understood by the
+                  gtk.accelerator_parse() function. Optional with a default value of
+                  None.
+                * The tooltip for the action. This field should typically be marked
+                  for translation, see the set_translation_domain() method. Optional
+                  with a default value of None.
+                * The callback function invoked when the action is activated.
+                  Optional with a default value of None.
+                * A flag indicating whether the toggle action is active. Optional
+                  with a default value of False.
+
+            The "activate" signals of the actions are connected to the callbacks and
+            their accel paths are set to <Actions>/group-name/action-name.
+            """
+
+            try:
+                iter(entries)
+            except (TypeError):
+                raise TypeError('entries must be iterable')
+
+            def _process_action(name, stock_id=None, label=None, accelerator=None, tooltip=None, callback=None, is_active=False):
+                action = Gtk.ToggleAction(name=name, label=label, tooltip=tooltip, stock_id=stock_id)
+                action.set_active(is_active)
+                if callback is not None:
+                    if user_data is None:
+                        action.connect('activate', callback)
+                    else:
+                        action.connect('activate', callback, user_data)
+
+                self.add_action_with_accel(action, accelerator)
+
+            for e in entries:
+                # using inner function above since entries can leave out optional arguments
+                _process_action(*e)
+
+        def add_radio_actions(self, entries, value=None, on_change=None, user_data=None):
+            """
+            The add_radio_actions() method is a convenience method that creates a
+            number of gtk.RadioAction objects based on the information in the list
+            of action entry tuples contained in entries and adds them to the action
+            group. The entry tuples can vary in size from one to six items with the
+            following information:
+
+                * The name of the action. Must be specified.
+                * The stock id for the action. Optional with a default value of None
+                  if a label is specified.
+                * The label for the action. This field should typically be marked
+                  for translation, see the set_translation_domain() method. Optional
+                  with a default value of None if a stock id is specified.
+                * The accelerator for the action, in the format understood by the
+                  gtk.accelerator_parse() function. Optional with a default value of
+                  None.
+                * The tooltip for the action. This field should typically be marked
+                  for translation, see the set_translation_domain() method. Optional
+                  with a default value of None.
+                * The value to set on the radio action. Optional with a default
+                  value of 0. Should be specified in applications.
+
+            The value parameter specifies the radio action that should be set
+            active. The "changed" signal of the first radio action is connected to
+            the on_change callback (if specified and not None) and the accel paths
+            of the actions are set to <Actions>/group-name/action-name.
+            """
+            try:
+                iter(entries)
+            except (TypeError):
+                raise TypeError('entries must be iterable')
+
+            first_action = None
+
+            def _process_action(group_source, name, stock_id=None, label=None, accelerator=None, tooltip=None, entry_value=0):
+                action = RadioAction(name=name, label=label, tooltip=tooltip, stock_id=stock_id, value=entry_value)
+
+                # FIXME: join_group is a patch to Gtk+ 3.0
+                #        otherwise we can't effectively add radio actions to a
+                #        group.  Should we depend on 3.0 and error out here
+                #        or should we offer the functionality via a compat
+                #        C module?
+                if hasattr(action, 'join_group'):
+                    action.join_group(group_source)
+
+                if value == entry_value:
+                    action.set_active(True)
+
+                self.add_action_with_accel(action, accelerator)
+                return action
+
+            for e in entries:
+                # using inner function above since entries can leave out optional arguments
+                action = _process_action(first_action, *e)
+                if first_action is None:
+                    first_action = action
+
+            if first_action is not None and on_change is not None:
+                if user_data is None:
+                    first_action.connect('changed', on_change)
+                else:
+                    first_action.connect('changed', on_change, user_data)
+
+    ActionGroup = override(ActionGroup)
+    __all__.append('ActionGroup')
+
+    class UIManager(Gtk.UIManager):
+        def add_ui_from_string(self, buffer):
+            if not isinstance(buffer, string_types):
+                raise TypeError('buffer must be a string')
+
+            length = len(buffer.encode('UTF-8'))
+
+            return Gtk.UIManager.add_ui_from_string(self, buffer, length)
+
+        def insert_action_group(self, buffer, length=-1):
+            return Gtk.UIManager.insert_action_group(self, buffer, length)
+
+    UIManager = override(UIManager)
+    __all__.append('UIManager')
+
+
+class ComboBox(Gtk.ComboBox, Container):
+    get_active_iter = strip_boolean_result(Gtk.ComboBox.get_active_iter)
+
+
+ComboBox = override(ComboBox)
+__all__.append('ComboBox')
+
+
+class Box(Gtk.Box):
+    __init__ = deprecated_init(Gtk.Box.__init__,
+                               arg_names=('homogeneous', 'spacing'),
+                               category=PyGTKDeprecationWarning)
+
+
+Box = override(Box)
+__all__.append('Box')
+
+
+class SizeGroup(Gtk.SizeGroup):
+    __init__ = deprecated_init(Gtk.SizeGroup.__init__,
+                               arg_names=('mode',),
+                               deprecated_defaults={'mode': Gtk.SizeGroupMode.VERTICAL},
+                               category=PyGTKDeprecationWarning)
+
+
+SizeGroup = override(SizeGroup)
+__all__.append('SizeGroup')
+
+
+class MenuItem(Gtk.MenuItem):
+    __init__ = deprecated_init(Gtk.MenuItem.__init__,
+                               arg_names=('label',),
+                               category=PyGTKDeprecationWarning)
+
+
+MenuItem = override(MenuItem)
+__all__.append('MenuItem')
+
+
+class Builder(Gtk.Builder):
+    def connect_signals(self, obj_or_map):
+        """Connect signals specified by this builder to a name, handler mapping.
+
+        Connect signal, name, and handler sets specified in the builder with
+        the given mapping "obj_or_map". The handler/value aspect of the mapping
+        can also contain a tuple in the form of (handler [,arg1 [,argN]])
+        allowing for extra arguments to be passed to the handler. For example:
+
+        .. code-block:: python
+
+            builder.connect_signals({'on_clicked': (on_clicked, arg1, arg2)})
+        """
+        self.connect_signals_full(_builder_connect_callback, obj_or_map)
+
+    def add_from_string(self, buffer):
+        if not isinstance(buffer, string_types):
+            raise TypeError('buffer must be a string')
+
+        length = len(buffer)
+
+        return Gtk.Builder.add_from_string(self, buffer, length)
+
+    def add_objects_from_string(self, buffer, object_ids):
+        if not isinstance(buffer, string_types):
+            raise TypeError('buffer must be a string')
+
+        length = len(buffer)
+
+        return Gtk.Builder.add_objects_from_string(self, buffer, length, object_ids)
+
+
+Builder = override(Builder)
+__all__.append('Builder')
+
+
+# NOTE: This must come before any other Window/Dialog subclassing, to ensure
+# that we have a correct inheritance hierarchy.
+
+
+class Window(Gtk.Window):
+    __init__ = deprecated_init(Gtk.Window.__init__,
+                               arg_names=('type',),
+                               category=PyGTKDeprecationWarning)
+
+
+Window = override(Window)
+__all__.append('Window')
+
+
+class Dialog(Gtk.Dialog, Container):
+    _old_arg_names = ('title', 'parent', 'flags', 'buttons', '_buttons_property')
+    _init = deprecated_init(Gtk.Dialog.__init__,
+                            arg_names=('title', 'transient_for', 'flags',
+                                       'add_buttons', 'buttons'),
+                            ignore=('flags', 'add_buttons'),
+                            deprecated_aliases={'transient_for': 'parent',
+                                                'buttons': '_buttons_property'},
+                            category=PyGTKDeprecationWarning)
+
+    def __init__(self, *args, **kwargs):
+
+        new_kwargs = kwargs.copy()
+        old_kwargs = dict(zip(self._old_arg_names, args))
+        old_kwargs.update(kwargs)
+
+        # Increment the warning stacklevel for sub-classes which implement their own __init__.
+        stacklevel = 2
+        if self.__class__ != Dialog and self.__class__.__init__ != Dialog.__init__:
+            stacklevel += 1
+
+        # buttons was overloaded by PyGtk but is needed for Gtk.MessageDialog
+        # as a pass through, so type check the argument and give a deprecation
+        # when it is not of type Gtk.ButtonsType
+        add_buttons = old_kwargs.get('buttons', None)
+        if add_buttons is not None and not isinstance(add_buttons, Gtk.ButtonsType):
+            warnings.warn('The "buttons" argument must be a Gtk.ButtonsType enum value. '
+                          'Please use the "add_buttons" method for adding buttons. '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
+                          PyGTKDeprecationWarning, stacklevel=stacklevel)
+            if 'buttons' in new_kwargs:
+                del new_kwargs['buttons']
+        else:
+            add_buttons = None
+
+        flags = old_kwargs.get('flags', 0)
+        if flags:
+            warnings.warn('The "flags" argument for dialog construction is deprecated. '
+                          'Please use initializer keywords: modal=True and/or destroy_with_parent=True. '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations',
+                          PyGTKDeprecationWarning, stacklevel=stacklevel)
+
+            if flags & Gtk.DialogFlags.MODAL:
+                new_kwargs['modal'] = True
+
+            if flags & Gtk.DialogFlags.DESTROY_WITH_PARENT:
+                new_kwargs['destroy_with_parent'] = True
+
+        self._init(*args, **new_kwargs)
+
+        if add_buttons:
+            self.add_buttons(*add_buttons)
+
+    def run(self, *args, **kwargs):
+        with register_sigint_fallback(self.destroy):
+            with wakeup_on_signal():
+                return Gtk.Dialog.run(self, *args, **kwargs)
+
+    action_area = property(lambda dialog: dialog.get_action_area())
+    vbox = property(lambda dialog: dialog.get_content_area())
+
+    def add_buttons(self, *args):
+        """
+        The add_buttons() method adds several buttons to the Gtk.Dialog using
+        the button data passed as arguments to the method. This method is the
+        same as calling the Gtk.Dialog.add_button() repeatedly. The button data
+        pairs - button text (or stock ID) and a response ID integer are passed
+        individually. For example:
+
+        .. code-block:: python
+
+            dialog.add_buttons(Gtk.STOCK_OPEN, 42, "Close", Gtk.ResponseType.CLOSE)
+
+        will add "Open" and "Close" buttons to dialog.
+        """
+        def _button(b):
+            while b:
+                t, r = b[0:2]
+                b = b[2:]
+                yield t, r
+
+        try:
+            for text, response in _button(args):
+                self.add_button(text, response)
+        except (IndexError):
+            raise TypeError('Must pass an even number of arguments')
+
+
+Dialog = override(Dialog)
+__all__.append('Dialog')
+
+
+class MessageDialog(Gtk.MessageDialog, Dialog):
+    __init__ = deprecated_init(Gtk.MessageDialog.__init__,
+                               arg_names=('parent', 'flags', 'message_type',
+                                          'buttons', 'message_format'),
+                               deprecated_aliases={'text': 'message_format',
+                                                   'message_type': 'type'},
+                               category=PyGTKDeprecationWarning)
+
+    def format_secondary_text(self, message_format):
+        self.set_property('secondary-use-markup', False)
+        self.set_property('secondary-text', message_format)
+
+    def format_secondary_markup(self, message_format):
+        self.set_property('secondary-use-markup', True)
+        self.set_property('secondary-text', message_format)
+
+
+MessageDialog = override(MessageDialog)
+__all__.append('MessageDialog')
+
+
+if Gtk._version in ("2.0", "3.0"):
+    class ColorSelectionDialog(Gtk.ColorSelectionDialog):
+        __init__ = deprecated_init(Gtk.ColorSelectionDialog.__init__,
+                                   arg_names=('title',),
+                                   category=PyGTKDeprecationWarning)
+
+    ColorSelectionDialog = override(ColorSelectionDialog)
+    __all__.append('ColorSelectionDialog')
+
+
+class FileChooserDialog(Gtk.FileChooserDialog):
+    __init__ = deprecated_init(Gtk.FileChooserDialog.__init__,
+                               arg_names=('title', 'parent', 'action', 'buttons'),
+                               category=PyGTKDeprecationWarning)
+
+
+FileChooserDialog = override(FileChooserDialog)
+__all__.append('FileChooserDialog')
+
+
+if Gtk._version in ("2.0", "3.0"):
+    class FontSelectionDialog(Gtk.FontSelectionDialog):
+        __init__ = deprecated_init(Gtk.FontSelectionDialog.__init__,
+                                   arg_names=('title',),
+                                   category=PyGTKDeprecationWarning)
+
+    FontSelectionDialog = override(FontSelectionDialog)
+    __all__.append('FontSelectionDialog')
+
+
+if Gtk._version in ("2.0", "3.0"):
+    class RecentChooserDialog(Gtk.RecentChooserDialog):
+        # Note, the "manager" keyword must work across the entire 3.x series because
+        # "recent_manager" is not backwards compatible with PyGObject versions prior to 3.10.
+        __init__ = deprecated_init(Gtk.RecentChooserDialog.__init__,
+                                   arg_names=('title', 'parent', 'recent_manager', 'buttons'),
+                                   deprecated_aliases={'recent_manager': 'manager'},
+                                   category=PyGTKDeprecationWarning)
+
+    RecentChooserDialog = override(RecentChooserDialog)
+    __all__.append('RecentChooserDialog')
+
+
+class IconView(Gtk.IconView):
+    __init__ = deprecated_init(Gtk.IconView.__init__,
+                               arg_names=('model',),
+                               category=PyGTKDeprecationWarning)
+
+    get_item_at_pos = strip_boolean_result(Gtk.IconView.get_item_at_pos)
+    get_visible_range = strip_boolean_result(Gtk.IconView.get_visible_range)
+    get_dest_item_at_pos = strip_boolean_result(Gtk.IconView.get_dest_item_at_pos)
+
+
+IconView = override(IconView)
+__all__.append('IconView')
+
+
+class ToolButton(Gtk.ToolButton):
+    __init__ = deprecated_init(Gtk.ToolButton.__init__,
+                               arg_names=('stock_id',),
+                               category=PyGTKDeprecationWarning)
+
+
+ToolButton = override(ToolButton)
+__all__.append('ToolButton')
+
+
+class IMContext(Gtk.IMContext):
+    get_surrounding = strip_boolean_result(Gtk.IMContext.get_surrounding)
+
+
+IMContext = override(IMContext)
+__all__.append('IMContext')
+
+
+class RecentInfo(Gtk.RecentInfo):
+    get_application_info = strip_boolean_result(Gtk.RecentInfo.get_application_info)
+
+
+RecentInfo = override(RecentInfo)
+__all__.append('RecentInfo')
+
+
+class TextBuffer(Gtk.TextBuffer):
+    def _get_or_create_tag_table(self):
+        table = self.get_tag_table()
+        if table is None:
+            table = Gtk.TextTagTable()
+            self.set_tag_table(table)
+
+        return table
+
+    def create_tag(self, tag_name=None, **properties):
+        """Creates a tag and adds it to the tag table of the TextBuffer.
+
+        :param str tag_name:
+            Name of the new tag, or None
+        :param **properties:
+            Keyword list of properties and their values
+
+        This is equivalent to creating a Gtk.TextTag and then adding the
+        tag to the buffer's tag table. The returned tag is owned by
+        the buffer's tag table.
+
+        If ``tag_name`` is None, the tag is anonymous.
+
+        If ``tag_name`` is not None, a tag called ``tag_name`` must not already
+        exist in the tag table for this buffer.
+
+        Properties are passed as a keyword list of names and values (e.g.
+        foreground='DodgerBlue', weight=Pango.Weight.BOLD)
+
+        :returns:
+            A new tag.
+        """
+
+        tag = Gtk.TextTag(name=tag_name, **properties)
+        self._get_or_create_tag_table().add(tag)
+        return tag
+
+    def create_mark(self, mark_name, where, left_gravity=False):
+        return Gtk.TextBuffer.create_mark(self, mark_name, where, left_gravity)
+
+    def set_text(self, text, length=-1):
+        Gtk.TextBuffer.set_text(self, text, length)
+
+    def insert(self, iter, text, length=-1):
+        if not isinstance(text, string_types):
+            raise TypeError('text must be a string, not %s' % type(text))
+
+        Gtk.TextBuffer.insert(self, iter, text, length)
+
+    def insert_with_tags(self, iter, text, *tags):
+        start_offset = iter.get_offset()
+        self.insert(iter, text)
+
+        if not tags:
+            return
+
+        start = self.get_iter_at_offset(start_offset)
+
+        for tag in tags:
+            self.apply_tag(tag, start, iter)
+
+    def insert_with_tags_by_name(self, iter, text, *tags):
+        tag_objs = []
+
+        for tag in tags:
+            tag_obj = self.get_tag_table().lookup(tag)
+            if not tag_obj:
+                raise ValueError('unknown text tag: %s' % tag)
+            tag_objs.append(tag_obj)
+
+        self.insert_with_tags(iter, text, *tag_objs)
+
+    def insert_at_cursor(self, text, length=-1):
+        if not isinstance(text, string_types):
+            raise TypeError('text must be a string, not %s' % type(text))
+
+        Gtk.TextBuffer.insert_at_cursor(self, text, length)
+
+    get_selection_bounds = strip_boolean_result(Gtk.TextBuffer.get_selection_bounds, fail_ret=())
+
+
+TextBuffer = override(TextBuffer)
+__all__.append('TextBuffer')
+
+
+class TextIter(Gtk.TextIter):
+    forward_search = strip_boolean_result(Gtk.TextIter.forward_search)
+    backward_search = strip_boolean_result(Gtk.TextIter.backward_search)
+
+
+TextIter = override(TextIter)
+__all__.append('TextIter')
+
+
+class TreeModel(Gtk.TreeModel):
+    def __len__(self):
+        return self.iter_n_children(None)
+
+    def __bool__(self):
+        return True
+
+    # alias for Python 2.x object protocol
+    __nonzero__ = __bool__
+
+    def _getiter(self, key):
+        if isinstance(key, Gtk.TreeIter):
+            return key
+        elif isinstance(key, int) and key < 0:
+            index = len(self) + key
+            if index < 0:
+                raise IndexError("row index is out of bounds: %d" % key)
+            try:
+                aiter = self.get_iter(index)
+            except ValueError:
+                raise IndexError("could not find tree path '%s'" % key)
+            return aiter
+        else:
+            try:
+                aiter = self.get_iter(key)
+            except ValueError:
+                raise IndexError("could not find tree path '%s'" % key)
+            return aiter
+
+    def _coerce_path(self, path):
+        if isinstance(path, Gtk.TreePath):
+            return path
+        else:
+            return TreePath(path)
+
+    def __getitem__(self, key):
+        aiter = self._getiter(key)
+        return TreeModelRow(self, aiter)
+
+    def __setitem__(self, key, value):
+        row = self[key]
+        self.set_row(row.iter, value)
+
+    def __delitem__(self, key):
+        aiter = self._getiter(key)
+        self.remove(aiter)
+
+    def __iter__(self):
+        return TreeModelRowIter(self, self.get_iter_first())
+
+    get_iter_first = strip_boolean_result(Gtk.TreeModel.get_iter_first)
+    iter_children = strip_boolean_result(Gtk.TreeModel.iter_children)
+    iter_nth_child = strip_boolean_result(Gtk.TreeModel.iter_nth_child)
+    iter_parent = strip_boolean_result(Gtk.TreeModel.iter_parent)
+    get_iter_from_string = strip_boolean_result(Gtk.TreeModel.get_iter_from_string,
+                                                ValueError, 'invalid tree path')
+
+    def get_iter(self, path):
+        path = self._coerce_path(path)
+        success, aiter = super(TreeModel, self).get_iter(path)
+        if not success:
+            raise ValueError("invalid tree path '%s'" % path)
+        return aiter
+
+    def iter_next(self, aiter):
+        next_iter = aiter.copy()
+        success = super(TreeModel, self).iter_next(next_iter)
+        if success:
+            return next_iter
+
+    def iter_previous(self, aiter):
+        prev_iter = aiter.copy()
+        success = super(TreeModel, self).iter_previous(prev_iter)
+        if success:
+            return prev_iter
+
+    def _convert_row(self, row):
+        # TODO: Accept a dictionary for row
+        # model.append(None,{COLUMN_ICON: icon, COLUMN_NAME: name})
+        if isinstance(row, str):
+            raise TypeError('Expected a list or tuple, but got str')
+
+        n_columns = self.get_n_columns()
+        if len(row) != n_columns:
+            raise ValueError('row sequence has the incorrect number of elements')
+
+        result = []
+        columns = []
+        for cur_col, value in enumerate(row):
+            # do not try to set None values, they are causing warnings
+            if value is None:
+                continue
+            result.append(self._convert_value(cur_col, value))
+            columns.append(cur_col)
+        return (result, columns)
+
+    def set_row(self, treeiter, row):
+        converted_row, columns = self._convert_row(row)
+        for column in columns:
+            value = row[column]
+            if value is None:
+                continue  # None means skip this row
+
+            self.set_value(treeiter, column, value)
+
+    def _convert_value(self, column, value):
+        '''Convert value to a GObject.Value of the expected type'''
+
+        if isinstance(value, GObject.Value):
+            return value
+        return GObject.Value(self.get_column_type(column), value)
+
+    def get(self, treeiter, *columns):
+        n_columns = self.get_n_columns()
+
+        values = []
+        for col in columns:
+            if not isinstance(col, int):
+                raise TypeError("column numbers must be ints")
+
+            if col < 0 or col >= n_columns:
+                raise ValueError("column number is out of range")
+
+            values.append(self.get_value(treeiter, col))
+
+        return tuple(values)
+
+    #
+    # Signals supporting python iterables as tree paths
+    #
+    def row_changed(self, path, iter):
+        return super(TreeModel, self).row_changed(self._coerce_path(path), iter)
+
+    def row_inserted(self, path, iter):
+        return super(TreeModel, self).row_inserted(self._coerce_path(path), iter)
+
+    def row_has_child_toggled(self, path, iter):
+        return super(TreeModel, self).row_has_child_toggled(self._coerce_path(path),
+                                                            iter)
+
+    def row_deleted(self, path):
+        return super(TreeModel, self).row_deleted(self._coerce_path(path))
+
+    def rows_reordered(self, path, iter, new_order):
+        return super(TreeModel, self).rows_reordered(self._coerce_path(path),
+                                                     iter, new_order)
+
+
+TreeModel = override(TreeModel)
+__all__.append('TreeModel')
+
+
+class TreeSortable(Gtk.TreeSortable, ):
+
+    get_sort_column_id = strip_boolean_result(Gtk.TreeSortable.get_sort_column_id, fail_ret=(None, None))
+
+    def set_sort_func(self, sort_column_id, sort_func, user_data=None):
+        super(TreeSortable, self).set_sort_func(sort_column_id, sort_func, user_data)
+
+    def set_default_sort_func(self, sort_func, user_data=None):
+        super(TreeSortable, self).set_default_sort_func(sort_func, user_data)
+
+
+TreeSortable = override(TreeSortable)
+__all__.append('TreeSortable')
+
+
+class TreeModelSort(Gtk.TreeModelSort):
+    __init__ = deprecated_init(Gtk.TreeModelSort.__init__,
+                               arg_names=('model',),
+                               category=PyGTKDeprecationWarning)
+
+
+TreeModelSort = override(TreeModelSort)
+__all__.append('TreeModelSort')
+
+
+class ListStore(Gtk.ListStore, TreeModel, TreeSortable):
+    def __init__(self, *column_types):
+        Gtk.ListStore.__init__(self)
+        self.set_column_types(column_types)
+
+    def _do_insert(self, position, row):
+        if row is not None:
+            row, columns = self._convert_row(row)
+            treeiter = self.insert_with_valuesv(position, columns, row)
+        else:
+            treeiter = Gtk.ListStore.insert(self, position)
+
+        return treeiter
+
+    def append(self, row=None):
+        if row:
+            return self._do_insert(-1, row)
+        # gtk_list_store_insert() does not know about the "position == -1"
+        # case, so use append() here
+        else:
+            return Gtk.ListStore.append(self)
+
+    def prepend(self, row=None):
+        return self._do_insert(0, row)
+
+    def insert(self, position, row=None):
+        return self._do_insert(position, row)
+
+    def insert_before(self, sibling, row=None):
+        if row is not None:
+            if sibling is None:
+                position = -1
+            else:
+                position = self.get_path(sibling).get_indices()[-1]
+            return self._do_insert(position, row)
+
+        return Gtk.ListStore.insert_before(self, sibling)
+
+    def insert_after(self, sibling, row=None):
+        if row is not None:
+            if sibling is None:
+                position = 0
+            else:
+                position = self.get_path(sibling).get_indices()[-1] + 1
+            return self._do_insert(position, row)
+
+        return Gtk.ListStore.insert_after(self, sibling)
+
+    def set_value(self, treeiter, column, value):
+        value = self._convert_value(column, value)
+        Gtk.ListStore.set_value(self, treeiter, column, value)
+
+    def set(self, treeiter, *args):
+        def _set_lists(cols, vals):
+            if len(cols) != len(vals):
+                raise TypeError('The number of columns do not match the number of values')
+
+            columns = []
+            values = []
+            for col_num, value in zip(cols, vals):
+                if not isinstance(col_num, int):
+                    raise TypeError('TypeError: Expected integer argument for column.')
+
+                columns.append(col_num)
+                values.append(self._convert_value(col_num, value))
+
+            Gtk.ListStore.set(self, treeiter, columns, values)
+
+        if args:
+            if isinstance(args[0], int):
+                _set_lists(args[::2], args[1::2])
+            elif isinstance(args[0], (tuple, list)):
+                if len(args) != 2:
+                    raise TypeError('Too many arguments')
+                _set_lists(args[0], args[1])
+            elif isinstance(args[0], dict):
+                _set_lists(list(args[0]), args[0].values())
+            else:
+                raise TypeError('Argument list must be in the form of (column, value, ...), ((columns,...), (values, ...)) or {column: value}.  No -1 termination is needed.')
+
+
+ListStore = override(ListStore)
+__all__.append('ListStore')
+
+
+class TreeModelRow(object):
+
+    def __init__(self, model, iter_or_path):
+        if not isinstance(model, Gtk.TreeModel):
+            raise TypeError("expected Gtk.TreeModel, %s found" % type(model).__name__)
+        self.model = model
+        if isinstance(iter_or_path, Gtk.TreePath):
+            self.iter = model.get_iter(iter_or_path)
+        elif isinstance(iter_or_path, Gtk.TreeIter):
+            self.iter = iter_or_path
+        else:
+            raise TypeError("expected Gtk.TreeIter or Gtk.TreePath, \
+                %s found" % type(iter_or_path).__name__)
+
+    @property
+    def path(self):
+        return self.model.get_path(self.iter)
+
+    @property
+    def next(self):
+        return self.get_next()
+
+    @property
+    def previous(self):
+        return self.get_previous()
+
+    @property
+    def parent(self):
+        return self.get_parent()
+
+    def get_next(self):
+        next_iter = self.model.iter_next(self.iter)
+        if next_iter:
+            return TreeModelRow(self.model, next_iter)
+
+    def get_previous(self):
+        prev_iter = self.model.iter_previous(self.iter)
+        if prev_iter:
+            return TreeModelRow(self.model, prev_iter)
+
+    def get_parent(self):
+        parent_iter = self.model.iter_parent(self.iter)
+        if parent_iter:
+            return TreeModelRow(self.model, parent_iter)
+
+    def __getitem__(self, key):
+        if isinstance(key, int):
+            if key >= self.model.get_n_columns():
+                raise IndexError("column index is out of bounds: %d" % key)
+            elif key < 0:
+                key = self._convert_negative_index(key)
+            return self.model.get_value(self.iter, key)
+        elif isinstance(key, slice):
+            start, stop, step = key.indices(self.model.get_n_columns())
+            alist = []
+            for i in range(start, stop, step):
+                alist.append(self.model.get_value(self.iter, i))
+            return alist
+        elif isinstance(key, tuple):
+            return [self[k] for k in key]
+        else:
+            raise TypeError("indices must be integers, slice or tuple, not %s"
+                            % type(key).__name__)
+
+    def __setitem__(self, key, value):
+        if isinstance(key, int):
+            if key >= self.model.get_n_columns():
+                raise IndexError("column index is out of bounds: %d" % key)
+            elif key < 0:
+                key = self._convert_negative_index(key)
+            self.model.set_value(self.iter, key, value)
+        elif isinstance(key, slice):
+            start, stop, step = key.indices(self.model.get_n_columns())
+            indexList = range(start, stop, step)
+            if len(indexList) != len(value):
+                raise ValueError(
+                    "attempt to assign sequence of size %d to slice of size %d"
+                    % (len(value), len(indexList)))
+
+            for i, v in enumerate(indexList):
+                self.model.set_value(self.iter, v, value[i])
+        elif isinstance(key, tuple):
+            if len(key) != len(value):
+                raise ValueError(
+                    "attempt to assign sequence of size %d to sequence of size %d"
+                    % (len(value), len(key)))
+            for k, v in zip(key, value):
+                self[k] = v
+        else:
+            raise TypeError("indices must be an integer, slice or tuple, not %s"
+                            % type(key).__name__)
+
+    def _convert_negative_index(self, index):
+        new_index = self.model.get_n_columns() + index
+        if new_index < 0:
+            raise IndexError("column index is out of bounds: %d" % index)
+        return new_index
+
+    def iterchildren(self):
+        child_iter = self.model.iter_children(self.iter)
+        return TreeModelRowIter(self.model, child_iter)
+
+
+__all__.append('TreeModelRow')
+
+
+class TreeModelRowIter(object):
+
+    def __init__(self, model, aiter):
+        self.model = model
+        self.iter = aiter
+
+    def __next__(self):
+        if not self.iter:
+            raise StopIteration
+        row = TreeModelRow(self.model, self.iter)
+        self.iter = self.model.iter_next(self.iter)
+        return row
+
+    # alias for Python 2.x object protocol
+    next = __next__
+
+    def __iter__(self):
+        return self
+
+
+__all__.append('TreeModelRowIter')
+
+
+class TreePath(Gtk.TreePath):
+
+    def __new__(cls, path=0):
+        if isinstance(path, int):
+            path = str(path)
+        elif not isinstance(path, string_types):
+            path = ":".join(str(val) for val in path)
+
+        if len(path) == 0:
+            raise TypeError("could not parse subscript '%s' as a tree path" % path)
+        try:
+            return TreePath.new_from_string(path)
+        except TypeError:
+            raise TypeError("could not parse subscript '%s' as a tree path" % path)
+
+    def __init__(self, *args, **kwargs):
+        super(TreePath, self).__init__()
+
+    def __str__(self):
+        return self.to_string() or ""
+
+    def __lt__(self, other):
+        return other is not None and self.compare(other) < 0
+
+    def __le__(self, other):
+        return other is not None and self.compare(other) <= 0
+
+    def __eq__(self, other):
+        return other is not None and self.compare(other) == 0
+
+    def __ne__(self, other):
+        return other is None or self.compare(other) != 0
+
+    def __gt__(self, other):
+        return other is None or self.compare(other) > 0
+
+    def __ge__(self, other):
+        return other is None or self.compare(other) >= 0
+
+    def __iter__(self):
+        return iter(self.get_indices())
+
+    def __len__(self):
+        return self.get_depth()
+
+    def __getitem__(self, index):
+        return self.get_indices()[index]
+
+
+TreePath = override(TreePath)
+__all__.append('TreePath')
+
+
+class TreeStore(Gtk.TreeStore, TreeModel, TreeSortable):
+    def __init__(self, *column_types):
+        Gtk.TreeStore.__init__(self)
+        self.set_column_types(column_types)
+
+    def _do_insert(self, parent, position, row):
+        if row is not None:
+            row, columns = self._convert_row(row)
+            treeiter = self.insert_with_values(parent, position, columns, row)
+        else:
+            treeiter = Gtk.TreeStore.insert(self, parent, position)
+
+        return treeiter
+
+    def append(self, parent, row=None):
+        return self._do_insert(parent, -1, row)
+
+    def prepend(self, parent, row=None):
+        return self._do_insert(parent, 0, row)
+
+    def insert(self, parent, position, row=None):
+        return self._do_insert(parent, position, row)
+
+    def insert_before(self, parent, sibling, row=None):
+        if row is not None:
+            if sibling is None:
+                position = -1
+            else:
+                position = self.get_path(sibling).get_indices()[-1]
+            return self._do_insert(parent, position, row)
+
+        return Gtk.TreeStore.insert_before(self, parent, sibling)
+
+    def insert_after(self, parent, sibling, row=None):
+        if row is not None:
+            if sibling is None:
+                position = 0
+            else:
+                position = self.get_path(sibling).get_indices()[-1] + 1
+            return self._do_insert(parent, position, row)
+
+        return Gtk.TreeStore.insert_after(self, parent, sibling)
+
+    def set_value(self, treeiter, column, value):
+        value = self._convert_value(column, value)
+        Gtk.TreeStore.set_value(self, treeiter, column, value)
+
+    def set(self, treeiter, *args):
+        def _set_lists(cols, vals):
+            if len(cols) != len(vals):
+                raise TypeError('The number of columns do not match the number of values')
+
+            columns = []
+            values = []
+            for col_num, value in zip(cols, vals):
+                if not isinstance(col_num, int):
+                    raise TypeError('TypeError: Expected integer argument for column.')
+
+                columns.append(col_num)
+                values.append(self._convert_value(col_num, value))
+
+            Gtk.TreeStore.set(self, treeiter, columns, values)
+
+        if args:
+            if isinstance(args[0], int):
+                _set_lists(args[::2], args[1::2])
+            elif isinstance(args[0], (tuple, list)):
+                if len(args) != 2:
+                    raise TypeError('Too many arguments')
+                _set_lists(args[0], args[1])
+            elif isinstance(args[0], dict):
+                _set_lists(args[0].keys(), args[0].values())
+            else:
+                raise TypeError('Argument list must be in the form of (column, value, ...), ((columns,...), (values, ...)) or {column: value}.  No -1 termination is needed.')
+
+
+TreeStore = override(TreeStore)
+__all__.append('TreeStore')
+
+
+class TreeView(Gtk.TreeView, Container):
+    __init__ = deprecated_init(Gtk.TreeView.__init__,
+                               arg_names=('model',),
+                               category=PyGTKDeprecationWarning)
+
+    get_path_at_pos = strip_boolean_result(Gtk.TreeView.get_path_at_pos)
+    get_visible_range = strip_boolean_result(Gtk.TreeView.get_visible_range)
+    get_dest_row_at_pos = strip_boolean_result(Gtk.TreeView.get_dest_row_at_pos)
+
+    def enable_model_drag_source(self, start_button_mask, targets, actions):
+        target_entries = _construct_target_list(targets)
+        super(TreeView, self).enable_model_drag_source(start_button_mask,
+                                                       target_entries,
+                                                       actions)
+
+    def enable_model_drag_dest(self, targets, actions):
+        target_entries = _construct_target_list(targets)
+        super(TreeView, self).enable_model_drag_dest(target_entries,
+                                                     actions)
+
+    def scroll_to_cell(self, path, column=None, use_align=False, row_align=0.0, col_align=0.0):
+        if not isinstance(path, Gtk.TreePath):
+            path = TreePath(path)
+        super(TreeView, self).scroll_to_cell(path, column, use_align, row_align, col_align)
+
+    def set_cursor(self, path, column=None, start_editing=False):
+        if not isinstance(path, Gtk.TreePath):
+            path = TreePath(path)
+        super(TreeView, self).set_cursor(path, column, start_editing)
+
+    def get_cell_area(self, path, column=None):
+        if not isinstance(path, Gtk.TreePath):
+            path = TreePath(path)
+        return super(TreeView, self).get_cell_area(path, column)
+
+    def insert_column_with_attributes(self, position, title, cell, **kwargs):
+        column = TreeViewColumn()
+        column.set_title(title)
+        column.pack_start(cell, False)
+        self.insert_column(column, position)
+        column.set_attributes(cell, **kwargs)
+
+
+TreeView = override(TreeView)
+__all__.append('TreeView')
+
+
+class TreeViewColumn(Gtk.TreeViewColumn):
+    def __init__(self, title='',
+                 cell_renderer=None,
+                 **attributes):
+        Gtk.TreeViewColumn.__init__(self, title=title)
+        if cell_renderer:
+            self.pack_start(cell_renderer, True)
+
+        for (name, value) in attributes.items():
+            self.add_attribute(cell_renderer, name, value)
+
+    cell_get_position = strip_boolean_result(Gtk.TreeViewColumn.cell_get_position)
+
+    def set_cell_data_func(self, cell_renderer, func, func_data=None):
+        super(TreeViewColumn, self).set_cell_data_func(cell_renderer, func, func_data)
+
+    def set_attributes(self, cell_renderer, **attributes):
+        Gtk.CellLayout.clear_attributes(self, cell_renderer)
+
+        for (name, value) in attributes.items():
+            Gtk.CellLayout.add_attribute(self, cell_renderer, name, value)
+
+
+TreeViewColumn = override(TreeViewColumn)
+__all__.append('TreeViewColumn')
+
+
+class TreeSelection(Gtk.TreeSelection):
+
+    def select_path(self, path):
+        if not isinstance(path, Gtk.TreePath):
+            path = TreePath(path)
+        super(TreeSelection, self).select_path(path)
+
+    def get_selected(self):
+        success, model, aiter = super(TreeSelection, self).get_selected()
+        if success:
+            return (model, aiter)
+        else:
+            return (model, None)
+
+    # for compatibility with PyGtk
+
+    def get_selected_rows(self):
+        rows, model = super(TreeSelection, self).get_selected_rows()
+        return (model, rows)
+
+
+TreeSelection = override(TreeSelection)
+__all__.append('TreeSelection')
+
+
+class Button(Gtk.Button, Container):
+    _init = deprecated_init(Gtk.Button.__init__,
+                            arg_names=('label', 'stock', 'use_stock', 'use_underline'),
+                            ignore=('stock',),
+                            category=PyGTKDeprecationWarning,
+                            stacklevel=3)
+
+    def __init__(self, *args, **kwargs):
+        # Doubly deprecated initializer, the stock keyword is non-standard.
+        # Simply give a warning that stock items are deprecated even though
+        # we want to deprecate the non-standard keyword as well here from
+        # the overrides.
+        if 'stock' in kwargs and kwargs['stock']:
+            warnings.warn('Stock items are deprecated. '
+                          'Please use: Gtk.Button.new_with_mnemonic(label)',
+                          PyGTKDeprecationWarning, stacklevel=2)
+            new_kwargs = kwargs.copy()
+            new_kwargs['label'] = new_kwargs['stock']
+            new_kwargs['use_stock'] = True
+            new_kwargs['use_underline'] = True
+            del new_kwargs['stock']
+            Gtk.Button.__init__(self, **new_kwargs)
+        else:
+            self._init(*args, **kwargs)
+
+
+Button = override(Button)
+__all__.append('Button')
+
+
+class LinkButton(Gtk.LinkButton):
+    __init__ = deprecated_init(Gtk.LinkButton.__init__,
+                               arg_names=('uri', 'label'),
+                               category=PyGTKDeprecationWarning)
+
+
+LinkButton = override(LinkButton)
+__all__.append('LinkButton')
+
+
+class Label(Gtk.Label):
+    __init__ = deprecated_init(Gtk.Label.__init__,
+                               arg_names=('label',),
+                               category=PyGTKDeprecationWarning)
+
+
+Label = override(Label)
+__all__.append('Label')
+
+
+class Adjustment(Gtk.Adjustment):
+    _init = deprecated_init(Gtk.Adjustment.__init__,
+                            arg_names=('value', 'lower', 'upper',
+                                       'step_increment', 'page_increment', 'page_size'),
+                            deprecated_aliases={'page_increment': 'page_incr',
+                                                'step_increment': 'step_incr'},
+                            category=PyGTKDeprecationWarning,
+                            stacklevel=3)
+
+    def __init__(self, *args, **kwargs):
+        self._init(*args, **kwargs)
+
+        # The value property is set between lower and (upper - page_size).
+        # Just in case lower, upper or page_size was still 0 when value
+        # was set, we set it again here.
+        if 'value' in kwargs:
+            self.set_value(kwargs['value'])
+        elif len(args) >= 1:
+            self.set_value(args[0])
+
+
+Adjustment = override(Adjustment)
+__all__.append('Adjustment')
+
+
+if Gtk._version in ("2.0", "3.0"):
+    class Table(Gtk.Table, Container):
+        __init__ = deprecated_init(Gtk.Table.__init__,
+                                   arg_names=('n_rows', 'n_columns', 'homogeneous'),
+                                   deprecated_aliases={'n_rows': 'rows', 'n_columns': 'columns'},
+                                   category=PyGTKDeprecationWarning)
+
+        def attach(self, child, left_attach, right_attach, top_attach, bottom_attach, xoptions=Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, yoptions=Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, xpadding=0, ypadding=0):
+            Gtk.Table.attach(self, child, left_attach, right_attach, top_attach, bottom_attach, xoptions, yoptions, xpadding, ypadding)
+
+    Table = override(Table)
+    __all__.append('Table')
+
+
+class ScrolledWindow(Gtk.ScrolledWindow):
+    __init__ = deprecated_init(Gtk.ScrolledWindow.__init__,
+                               arg_names=('hadjustment', 'vadjustment'),
+                               category=PyGTKDeprecationWarning)
+
+
+ScrolledWindow = override(ScrolledWindow)
+__all__.append('ScrolledWindow')
+
+
+if Gtk._version in ("2.0", "3.0"):
+    class HScrollbar(Gtk.HScrollbar):
+        __init__ = deprecated_init(Gtk.HScrollbar.__init__,
+                                   arg_names=('adjustment',),
+                                   category=PyGTKDeprecationWarning)
+
+    HScrollbar = override(HScrollbar)
+    __all__.append('HScrollbar')
+
+    class VScrollbar(Gtk.VScrollbar):
+        __init__ = deprecated_init(Gtk.VScrollbar.__init__,
+                                   arg_names=('adjustment',),
+                                   category=PyGTKDeprecationWarning)
+
+    VScrollbar = override(VScrollbar)
+    __all__.append('VScrollbar')
+
+
+class Paned(Gtk.Paned):
+    def pack1(self, child, resize=False, shrink=True):
+        super(Paned, self).pack1(child, resize, shrink)
+
+    def pack2(self, child, resize=True, shrink=True):
+        super(Paned, self).pack2(child, resize, shrink)
+
+
+Paned = override(Paned)
+__all__.append('Paned')
+
+
+if Gtk._version in ("2.0", "3.0"):
+    class Arrow(Gtk.Arrow):
+        __init__ = deprecated_init(Gtk.Arrow.__init__,
+                                   arg_names=('arrow_type', 'shadow_type'),
+                                   category=PyGTKDeprecationWarning)
+
+    Arrow = override(Arrow)
+    __all__.append('Arrow')
+
+    class IconSet(Gtk.IconSet):
+        def __new__(cls, pixbuf=None):
+            if pixbuf is not None:
+                warnings.warn('Gtk.IconSet(pixbuf) has been deprecated. Please use: '
+                              'Gtk.IconSet.new_from_pixbuf(pixbuf)',
+                              PyGTKDeprecationWarning, stacklevel=2)
+                iconset = Gtk.IconSet.new_from_pixbuf(pixbuf)
+            else:
+                iconset = Gtk.IconSet.__new__(cls)
+            return iconset
+
+        def __init__(self, *args, **kwargs):
+            return super(IconSet, self).__init__()
+
+    IconSet = override(IconSet)
+    __all__.append('IconSet')
+
+
+class Viewport(Gtk.Viewport):
+    __init__ = deprecated_init(Gtk.Viewport.__init__,
+                               arg_names=('hadjustment', 'vadjustment'),
+                               category=PyGTKDeprecationWarning)
+
+
+Viewport = override(Viewport)
+__all__.append('Viewport')
+
+
+class TreeModelFilter(Gtk.TreeModelFilter):
+    def set_visible_func(self, func, data=None):
+        super(TreeModelFilter, self).set_visible_func(func, data)
+
+    def set_value(self, iter, column, value):
+        # Delegate to child model
+        iter = self.convert_iter_to_child_iter(iter)
+        self.get_model().set_value(iter, column, value)
+
+
+TreeModelFilter = override(TreeModelFilter)
+__all__.append('TreeModelFilter')
+
+if Gtk._version != '2.0':
+    class Menu(Gtk.Menu):
+        def popup(self, parent_menu_shell, parent_menu_item, func, data, button, activate_time):
+            self.popup_for_device(None, parent_menu_shell, parent_menu_item, func, data, button, activate_time)
+    Menu = override(Menu)
+    __all__.append('Menu')
+
+_Gtk_main_quit = Gtk.main_quit
+
+
+@override(Gtk.main_quit)
+def main_quit(*args):
+    _Gtk_main_quit()
+
+
+_Gtk_main = Gtk.main
+
+
+@override(Gtk.main)
+def main(*args, **kwargs):
+    with register_sigint_fallback(Gtk.main_quit):
+        with wakeup_on_signal():
+            return _Gtk_main(*args, **kwargs)
+
+
+if Gtk._version in ("2.0", "3.0"):
+    stock_lookup = strip_boolean_result(Gtk.stock_lookup)
+    __all__.append('stock_lookup')
+
+if Gtk._version == "4.0":
+    Gtk.init_check()
+else:
+    initialized, argv = Gtk.init_check(sys.argv)
+    sys.argv = list(argv)
diff --git a/gi/overrides/Pango.py b/gi/overrides/Pango.py
new file mode 100644 (file)
index 0000000..067a628
--- /dev/null
@@ -0,0 +1,55 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Paolo Borelli <pborelli@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from ..overrides import override
+from ..module import get_introspection_module
+
+Pango = get_introspection_module('Pango')
+
+__all__ = []
+
+
+class FontDescription(Pango.FontDescription):
+
+    def __new__(cls, string=None):
+        if string is not None:
+            return Pango.font_description_from_string(string)
+        else:
+            return Pango.FontDescription.__new__(cls)
+
+    def __init__(self, *args, **kwargs):
+        return super(FontDescription, self).__init__()
+
+
+FontDescription = override(FontDescription)
+__all__.append('FontDescription')
+
+
+class Layout(Pango.Layout):
+
+    def __new__(cls, context):
+        return Pango.Layout.new(context)
+
+    def set_markup(self, text, length=-1):
+        super(Layout, self).set_markup(text, length)
+
+
+Layout = override(Layout)
+__all__.append('Layout')
diff --git a/gi/overrides/__init__.py b/gi/overrides/__init__.py
new file mode 100644 (file)
index 0000000..e262b6c
--- /dev/null
@@ -0,0 +1,350 @@
+import types
+import warnings
+import importlib
+import sys
+from pkgutil import get_loader
+
+from gi import PyGIDeprecationWarning
+from gi._gi import CallableInfo
+from gi._constants import \
+    TYPE_NONE, \
+    TYPE_INVALID
+
+# support overrides in different directories than our gi module
+from pkgutil import extend_path
+__path__ = extend_path(__path__, __name__)
+
+
+# namespace -> (attr, replacement)
+_deprecated_attrs = {}
+
+
+def wraps(wrapped):
+    def assign(wrapper):
+        wrapper.__name__ = wrapped.__name__
+        wrapper.__module__ = wrapped.__module__
+        return wrapper
+    return assign
+
+
+class OverridesProxyModule(types.ModuleType):
+    """Wraps a introspection module and contains all overrides"""
+
+    def __init__(self, introspection_module):
+        super(OverridesProxyModule, self).__init__(
+            introspection_module.__name__)
+        self._introspection_module = introspection_module
+
+    def __getattr__(self, name):
+        return getattr(self._introspection_module, name)
+
+    def __dir__(self):
+        result = set(dir(self.__class__))
+        result.update(self.__dict__.keys())
+        result.update(dir(self._introspection_module))
+        return sorted(result)
+
+    def __repr__(self):
+        return "<%s %r>" % (type(self).__name__, self._introspection_module)
+
+
+class _DeprecatedAttribute(object):
+    """A deprecation descriptor for OverridesProxyModule subclasses.
+
+    Emits a PyGIDeprecationWarning on every access and tries to act as a
+    normal instance attribute (can be replaced and deleted).
+    """
+
+    def __init__(self, namespace, attr, value, replacement):
+        self._attr = attr
+        self._value = value
+        self._warning = PyGIDeprecationWarning(
+            '%s.%s is deprecated; use %s instead' % (
+                namespace, attr, replacement))
+
+    def __get__(self, instance, owner):
+        if instance is None:
+            raise AttributeError(self._attr)
+        warnings.warn(self._warning, stacklevel=2)
+        return self._value
+
+    def __set__(self, instance, value):
+        attr = self._attr
+        # delete the descriptor, then set the instance value
+        delattr(type(instance), attr)
+        setattr(instance, attr, value)
+
+    def __delete__(self, instance):
+        # delete the descriptor
+        delattr(type(instance), self._attr)
+
+
+def load_overrides(introspection_module):
+    """Loads overrides for an introspection module.
+
+    Either returns the same module again in case there are no overrides or a
+    proxy module including overrides. Doesn't cache the result.
+    """
+
+    namespace = introspection_module.__name__.rsplit(".", 1)[-1]
+    module_key = 'gi.repository.' + namespace
+
+    # We use sys.modules so overrides can import from gi.repository
+    # but restore everything at the end so this doesn't have any side effects
+    has_old = module_key in sys.modules
+    old_module = sys.modules.get(module_key)
+
+    # Create a new sub type, so we can separate descriptors like
+    # _DeprecatedAttribute for each namespace.
+    proxy_type = type(namespace + "ProxyModule", (OverridesProxyModule, ), {})
+
+    proxy = proxy_type(introspection_module)
+    sys.modules[module_key] = proxy
+
+    # backwards compat:
+    # gedit uses gi.importer.modules['Gedit']._introspection_module
+    from ..importer import modules
+    assert hasattr(proxy, "_introspection_module")
+    modules[namespace] = proxy
+
+    try:
+        override_package_name = 'gi.overrides.' + namespace
+
+        # http://bugs.python.org/issue14710
+        try:
+            override_loader = get_loader(override_package_name)
+
+        except AttributeError:
+            override_loader = None
+
+        # Avoid checking for an ImportError, an override might
+        # depend on a missing module thus causing an ImportError
+        if override_loader is None:
+            return introspection_module
+
+        override_mod = importlib.import_module(override_package_name)
+
+    finally:
+        del modules[namespace]
+        del sys.modules[module_key]
+        if has_old:
+            sys.modules[module_key] = old_module
+
+    # backwards compat: for gst-python/gstmodule.c,
+    # which tries to access Gst.Fraction through
+    # Gst._overrides_module.Fraction. We assign the proxy instead as that
+    # contains all overridden classes like Fraction during import anyway and
+    # there is no need to keep the real override module alive.
+    proxy._overrides_module = proxy
+
+    override_all = []
+    if hasattr(override_mod, "__all__"):
+        override_all = override_mod.__all__
+
+    for var in override_all:
+        try:
+            item = getattr(override_mod, var)
+        except (AttributeError, TypeError):
+            # Gedit puts a non-string in __all__, so catch TypeError here
+            continue
+        setattr(proxy, var, item)
+
+    # Replace deprecated module level attributes with a descriptor
+    # which emits a warning when accessed.
+    for attr, replacement in _deprecated_attrs.pop(namespace, []):
+        try:
+            value = getattr(proxy, attr)
+        except AttributeError:
+            raise AssertionError(
+                "%s was set deprecated but wasn't added to __all__" % attr)
+        delattr(proxy, attr)
+        deprecated_attr = _DeprecatedAttribute(
+            namespace, attr, value, replacement)
+        setattr(proxy_type, attr, deprecated_attr)
+
+    return proxy
+
+
+def override(type_):
+    """Decorator for registering an override.
+
+    Other than objects added to __all__, these can get referenced in the same
+    override module via the gi.repository module (get_parent_for_object() does
+    for example), so they have to be added to the module immediately.
+    """
+
+    if isinstance(type_, CallableInfo):
+        func = type_
+        namespace = func.__module__.rsplit('.', 1)[-1]
+        module = sys.modules["gi.repository." + namespace]
+
+        def wrapper(func):
+            setattr(module, func.__name__, func)
+            return func
+
+        return wrapper
+    elif isinstance(type_, types.FunctionType):
+        raise TypeError("func must be a gi function, got %s" % type_)
+    else:
+        try:
+            info = getattr(type_, '__info__')
+        except AttributeError:
+            raise TypeError(
+                'Can not override a type %s, which is not in a gobject '
+                'introspection typelib' % type_.__name__)
+
+        if not type_.__module__.startswith('gi.overrides'):
+            raise KeyError(
+                'You have tried override outside of the overrides module. '
+                'This is not allowed (%s, %s)' % (type_, type_.__module__))
+
+        g_type = info.get_g_type()
+        assert g_type != TYPE_NONE
+        if g_type != TYPE_INVALID:
+            g_type.pytype = type_
+
+        namespace = type_.__module__.rsplit(".", 1)[-1]
+        module = sys.modules["gi.repository." + namespace]
+        setattr(module, type_.__name__, type_)
+
+        return type_
+
+
+overridefunc = override
+"""Deprecated"""
+
+
+def deprecated(fn, replacement):
+    """Decorator for marking methods and classes as deprecated"""
+    @wraps(fn)
+    def wrapped(*args, **kwargs):
+        warnings.warn('%s is deprecated; use %s instead' % (fn.__name__, replacement),
+                      PyGIDeprecationWarning, stacklevel=2)
+        return fn(*args, **kwargs)
+    return wrapped
+
+
+def deprecated_attr(namespace, attr, replacement):
+    """Marks a module level attribute as deprecated. Accessing it will emit
+    a PyGIDeprecationWarning warning.
+
+    e.g. for ``deprecated_attr("GObject", "STATUS_FOO", "GLib.Status.FOO")``
+    accessing GObject.STATUS_FOO will emit:
+
+        "GObject.STATUS_FOO is deprecated; use GLib.Status.FOO instead"
+
+    :param str namespace:
+        The namespace of the override this is called in.
+    :param str namespace:
+        The attribute name (which gets added to __all__).
+    :param str replacement:
+        The replacement text which will be included in the warning.
+    """
+
+    _deprecated_attrs.setdefault(namespace, []).append((attr, replacement))
+
+
+def deprecated_init(super_init_func, arg_names, ignore=tuple(),
+                    deprecated_aliases={}, deprecated_defaults={},
+                    category=PyGIDeprecationWarning,
+                    stacklevel=2):
+    """Wrapper for deprecating GObject based __init__ methods which specify
+    defaults already available or non-standard defaults.
+
+    :param callable super_init_func:
+        Initializer to wrap.
+    :param list arg_names:
+        Ordered argument name list.
+    :param list ignore:
+        List of argument names to ignore when calling the wrapped function.
+        This is useful for function which take a non-standard keyword that is munged elsewhere.
+    :param dict deprecated_aliases:
+        Dictionary mapping a keyword alias to the actual g_object_newv keyword.
+    :param dict deprecated_defaults:
+        Dictionary of non-standard defaults that will be used when the
+        keyword is not explicitly passed.
+    :param Exception category:
+        Exception category of the error.
+    :param int stacklevel:
+        Stack level for the deprecation passed on to warnings.warn
+    :returns: Wrapped version of ``super_init_func`` which gives a deprecation
+        warning when non-keyword args or aliases are used.
+    :rtype: callable
+    """
+    # We use a list of argument names to maintain order of the arguments
+    # being deprecated. This allows calls with positional arguments to
+    # continue working but with a deprecation message.
+    def new_init(self, *args, **kwargs):
+        """Initializer for a GObject based classes with support for property
+        sets through the use of explicit keyword arguments.
+        """
+        # Print warnings for calls with positional arguments.
+        if args:
+            warnings.warn('Using positional arguments with the GObject constructor has been deprecated. '
+                          'Please specify keyword(s) for "%s" or use a class specific constructor. '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations' %
+                          ', '.join(arg_names[:len(args)]),
+                          category, stacklevel=stacklevel)
+            new_kwargs = dict(zip(arg_names, args))
+        else:
+            new_kwargs = {}
+        new_kwargs.update(kwargs)
+
+        # Print warnings for alias usage and transfer them into the new key.
+        aliases_used = []
+        for key, alias in deprecated_aliases.items():
+            if alias in new_kwargs:
+                new_kwargs[key] = new_kwargs.pop(alias)
+                aliases_used.append(key)
+
+        if aliases_used:
+            warnings.warn('The keyword(s) "%s" have been deprecated in favor of "%s" respectively. '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations' %
+                          (', '.join(deprecated_aliases[k] for k in sorted(aliases_used)),
+                           ', '.join(sorted(aliases_used))),
+                          category, stacklevel=stacklevel)
+
+        # Print warnings for defaults different than what is already provided by the property
+        defaults_used = []
+        for key, value in deprecated_defaults.items():
+            if key not in new_kwargs:
+                new_kwargs[key] = deprecated_defaults[key]
+                defaults_used.append(key)
+
+        if defaults_used:
+            warnings.warn('Initializer is relying on deprecated non-standard '
+                          'defaults. Please update to explicitly use: %s '
+                          'See: https://wiki.gnome.org/PyGObject/InitializerDeprecations' %
+                          ', '.join('%s=%s' % (k, deprecated_defaults[k]) for k in sorted(defaults_used)),
+                          category, stacklevel=stacklevel)
+
+        # Remove keywords that should be ignored.
+        for key in ignore:
+            if key in new_kwargs:
+                new_kwargs.pop(key)
+
+        return super_init_func(self, **new_kwargs)
+
+    return new_init
+
+
+def strip_boolean_result(method, exc_type=None, exc_str=None, fail_ret=None):
+    """Translate method's return value for stripping off success flag.
+
+    There are a lot of methods which return a "success" boolean and have
+    several out arguments. Translate such a method to return the out arguments
+    on success and None on failure.
+    """
+    @wraps(method)
+    def wrapped(*args, **kwargs):
+        ret = method(*args, **kwargs)
+        if ret[0]:
+            if len(ret) == 2:
+                return ret[1]
+            else:
+                return ret[1:]
+        else:
+            if exc_type:
+                raise exc_type(exc_str or 'call failed')
+            return fail_ret
+    return wrapped
diff --git a/gi/overrides/keysyms.py b/gi/overrides/keysyms.py
new file mode 100644 (file)
index 0000000..07ce277
--- /dev/null
@@ -0,0 +1,53 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# pygtk - Python bindings for the GTK toolkit.
+# Copyright (C) 1998-2003  James Henstridge
+#
+#   gtk/keysyms.py: list of keysyms.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import warnings
+
+from ..module import get_introspection_module
+
+Gdk = get_introspection_module('Gdk')
+
+warnings.warn('keysyms has been deprecated. Please use Gdk.KEY_<name> instead.',
+              RuntimeWarning)
+
+_modname = globals()['__name__']
+_keysyms = sys.modules[_modname]
+
+for name in dir(Gdk):
+    if name.startswith('KEY_'):
+        target = name[4:]
+        if target[0] in '0123456789':
+            target = '_' + target
+        value = getattr(Gdk, name)
+        setattr(_keysyms, target, value)
+
+
+# Not found in Gdk but left for compatibility.
+Armenian_eternity = 0x14a1
+Armenian_section_sign = 0x14a2
+Armenian_parenleft = 0x14a5
+Armenian_guillemotright = 0x14a6
+Armenian_guillemotleft = 0x14a7
+Armenian_em_dash = 0x14a8
+Armenian_dot = 0x14a9
+Armenian_mijaket = 0x14a9
+Armenian_comma = 0x14ab
+Armenian_en_dash = 0x14ac
+Armenian_ellipsis = 0x14ae
diff --git a/gi/overrides/meson.build b/gi/overrides/meson.build
new file mode 100644 (file)
index 0000000..6ff073f
--- /dev/null
@@ -0,0 +1,15 @@
+python_sources = [
+  'GLib.py',
+  'Gtk.py',
+  'Gdk.py',
+  'GdkPixbuf.py',
+  'GObject.py',
+  'Gio.py',
+  'GIMarshallingTests.py',
+  'Pango.py',
+  'keysyms.py',
+  '__init__.py']
+
+python.install_sources(python_sources,
+  subdir : join_paths('gi', 'overrides')
+)
diff --git a/gi/pygboxed.c b/gi/pygboxed.c
new file mode 100644 (file)
index 0000000..1f355be
--- /dev/null
@@ -0,0 +1,261 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *
+ *   pygboxed.c: wrapper for GBoxed
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <Python.h>
+#include <glib-object.h>
+
+#include "pygboxed.h"
+#include "pygi-type.h"
+#include "pygi-type.h"
+#include "pygi-util.h"
+
+GQuark pygboxed_type_key;
+
+PYGLIB_DEFINE_TYPE("gobject.GBoxed", PyGBoxed_Type, PyGBoxed);
+
+static void
+gboxed_dealloc(PyGBoxed *self)
+{
+    if (self->free_on_dealloc && pyg_boxed_get_ptr (self)) {
+       PyGILState_STATE state = PyGILState_Ensure();
+       g_boxed_free (self->gtype, pyg_boxed_get_ptr (self));
+       PyGILState_Release(state);
+    }
+
+    Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static PyObject*
+gboxed_richcompare(PyObject *self, PyObject *other, int op)
+{
+    if (Py_TYPE(self) == Py_TYPE(other) &&
+        PyObject_IsInstance(self, (PyObject*)&PyGBoxed_Type))
+        return pyg_ptr_richcompare (pyg_boxed_get_ptr (self),
+                                    pyg_boxed_get_ptr (other),
+                                    op);
+    else {
+        Py_INCREF(Py_NotImplemented);
+        return Py_NotImplemented;
+    }
+}
+
+static PYGLIB_Py_hash_t
+gboxed_hash(PyGBoxed *self)
+{
+    return PYGLIB_Py_hash_t_FromVoidPtr (pyg_boxed_get_ptr (self));
+}
+
+static PyObject *
+gboxed_repr(PyGBoxed *boxed)
+{
+    PyObject *module, *repr, *self = (PyObject *)boxed;
+    gchar *module_str, *namespace;
+
+    module = PyObject_GetAttrString (self, "__module__");
+    if (module == NULL)
+        return NULL;
+
+    if (!PYGLIB_PyUnicode_Check (module)) {
+        Py_DECREF (module);
+        return NULL;
+    }
+
+    module_str = PYGLIB_PyUnicode_AsString (module);
+    namespace = g_strrstr (module_str, ".");
+    if (namespace == NULL) {
+        namespace = module_str;
+    } else {
+        namespace += 1;
+    }
+
+    repr = PYGLIB_PyUnicode_FromFormat ("<%s.%s object at %p (%s at %p)>",
+                                        namespace, Py_TYPE (self)->tp_name,
+                                        self, g_type_name (boxed->gtype),
+                                        pyg_boxed_get_ptr (boxed));
+    Py_DECREF (module);
+    return repr;
+}
+
+static int
+gboxed_init(PyGBoxed *self, PyObject *args, PyObject *kwargs)
+{
+    gchar buf[512];
+
+    if (!PyArg_ParseTuple(args, ":GBoxed.__init__"))
+       return -1;
+
+    pyg_boxed_set_ptr (self, NULL);
+    self->gtype = 0;
+    self->free_on_dealloc = FALSE;
+
+    g_snprintf(buf, sizeof(buf), "%s can not be constructed",
+              Py_TYPE(self)->tp_name);
+    PyErr_SetString(PyExc_NotImplementedError, buf);
+    return -1;
+}
+
+static void
+gboxed_free(PyObject *op)
+{
+  PyObject_FREE(op);
+}
+
+static PyObject *
+gboxed_copy(PyGBoxed *self)
+{
+    return pygi_gboxed_new (self->gtype, pyg_boxed_get_ptr (self), TRUE, TRUE);
+}
+
+static PyMethodDef pygboxed_methods[] = {
+    { "copy", (PyCFunction) gboxed_copy, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+
+/**
+ * pygi_register_gboxed:
+ * @dict: the module dictionary to store the wrapper class.
+ * @class_name: the Python name for the wrapper class.
+ * @boxed_type: the GType of the boxed type being wrapped.
+ * @type: the wrapper class.
+ *
+ * Registers a wrapper for a boxed type.  The wrapper class will be a
+ * subclass of gobject.GBoxed, and a reference to the wrapper class
+ * will be stored in the provided module dictionary.
+ */
+void
+pygi_register_gboxed (PyObject *dict, const gchar *class_name,
+                      GType boxed_type, PyTypeObject *type)
+{
+    PyObject *o;
+
+    g_return_if_fail(dict != NULL);
+    g_return_if_fail(class_name != NULL);
+    g_return_if_fail(boxed_type != 0);
+
+    if (!type->tp_dealloc)  type->tp_dealloc  = (destructor)gboxed_dealloc;
+
+    Py_TYPE(type) = &PyType_Type;
+    g_assert (Py_TYPE (&PyGBoxed_Type) != NULL);
+    type->tp_base = &PyGBoxed_Type;
+
+    if (PyType_Ready(type) < 0) {
+       g_warning("could not get type `%s' ready", type->tp_name);
+       return;
+    }
+
+    PyDict_SetItemString(type->tp_dict, "__gtype__",
+                        o=pyg_type_wrapper_new(boxed_type));
+    Py_DECREF(o);
+
+    g_type_set_qdata(boxed_type, pygboxed_type_key, type);
+
+    PyDict_SetItemString(dict, (char *)class_name, (PyObject *)type);
+}
+
+/**
+ * pygi_gboxed_new:
+ * @boxed_type: the GType of the boxed value.
+ * @boxed: the boxed value.
+ * @copy_boxed: whether the new boxed wrapper should hold a copy of the value.
+ * @own_ref: whether the boxed wrapper should own the boxed value.
+ *
+ * Creates a wrapper for a boxed value.  If @copy_boxed is set to
+ * True, the wrapper will hold a copy of the value, instead of the
+ * value itself.  If @own_ref is True, then the value held by the
+ * wrapper will be freed when the wrapper is deallocated.  If
+ * @copy_boxed is True, then @own_ref must also be True.
+ *
+ * Returns: the boxed wrapper or %NULL and sets an exception.
+ */
+PyObject *
+pygi_gboxed_new (GType boxed_type, gpointer boxed, gboolean copy_boxed,
+                 gboolean own_ref)
+{
+    PyGILState_STATE state;
+    PyGBoxed *self;
+    PyTypeObject *tp;
+
+    g_return_val_if_fail(boxed_type != 0, NULL);
+    g_return_val_if_fail(!copy_boxed || (copy_boxed && own_ref), NULL);
+
+    state = PyGILState_Ensure();
+
+    if (!boxed) {
+       Py_INCREF(Py_None);
+       PyGILState_Release(state);
+       return Py_None;
+    }
+
+    tp = g_type_get_qdata(boxed_type, pygboxed_type_key);
+
+    if (!tp)
+        tp = (PyTypeObject *)pygi_type_import_by_g_type(boxed_type);
+
+    if (!tp)
+       tp = (PyTypeObject *)&PyGBoxed_Type; /* fallback */
+
+    if (!PyType_IsSubtype (tp, &PyGBoxed_Type)) {
+        PyErr_Format (PyExc_RuntimeError, "%s isn't a GBoxed", tp->tp_name);
+        PyGILState_Release (state);
+        return NULL;
+    }
+
+    self = (PyGBoxed *)tp->tp_alloc(tp, 0);
+
+    if (self == NULL) {
+       PyGILState_Release(state);
+        return NULL;
+    }
+
+    if (copy_boxed)
+       boxed = g_boxed_copy(boxed_type, boxed);
+    pyg_boxed_set_ptr (self, boxed);
+    self->gtype = boxed_type;
+    self->free_on_dealloc = own_ref;
+
+    PyGILState_Release(state);
+    
+    return (PyObject *)self;
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_gboxed_register_types(PyObject *d)
+{
+    pygboxed_type_key        = g_quark_from_static_string("PyGBoxed::class");
+
+    PyGBoxed_Type.tp_dealloc = (destructor)gboxed_dealloc;
+    PyGBoxed_Type.tp_richcompare = gboxed_richcompare;
+    PyGBoxed_Type.tp_repr = (reprfunc)gboxed_repr;
+    PyGBoxed_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+    PyGBoxed_Type.tp_methods = pygboxed_methods;
+    PyGBoxed_Type.tp_init = (initproc)gboxed_init;
+    PyGBoxed_Type.tp_free = (freefunc)gboxed_free;
+    PyGBoxed_Type.tp_hash = (hashfunc)gboxed_hash;
+    
+    PYGOBJECT_REGISTER_GTYPE(d, PyGBoxed_Type, "GBoxed", G_TYPE_BOXED);
+
+    return 0;
+}
diff --git a/gi/pygboxed.h b/gi/pygboxed.h
new file mode 100644 (file)
index 0000000..c1b80bf
--- /dev/null
@@ -0,0 +1,34 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGOBJECT_BOXED_H__ 
+#define __PYGOBJECT_BOXED_H__
+
+extern GQuark pygboxed_type_key;
+
+extern PyTypeObject PyGBoxed_Type;
+
+void       pygi_register_gboxed (PyObject *dict, const gchar *class_name,
+                                 GType boxed_type, PyTypeObject *type);
+PyObject * pygi_gboxed_new      (GType boxed_type, gpointer boxed,
+                                 gboolean copy_boxed, gboolean own_ref);
+
+int pygi_gboxed_register_types(PyObject *d);
+
+#endif /* __PYGOBJECT_BOXED_H__ */
diff --git a/gi/pygenum.c b/gi/pygenum.c
new file mode 100644 (file)
index 0000000..69f1cd7
--- /dev/null
@@ -0,0 +1,399 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ * Copyright (C) 2004       Johan Dahlin
+ *
+ *   pygenum.c: GEnum wrapper
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "pygi-python-compat.h"
+#include "pygi-type.h"
+#include "pygi-util.h"
+#include "pygi-type.h"
+#include "pygi-basictype.h"
+#include "pygenum.h"
+#include "pygboxed.h"
+
+GQuark pygenum_class_key;
+
+PYGLIB_DEFINE_TYPE("gobject.GEnum", PyGEnum_Type, PyGEnum);
+
+static PyObject *
+pyg_enum_val_new(PyObject* subclass, GType gtype, PyObject *intval)
+{
+    PyObject *args, *item;
+    args = Py_BuildValue("(O)", intval);
+    item =  (&PYGLIB_PyLong_Type)->tp_new((PyTypeObject*)subclass, args, NULL);
+    Py_DECREF(args);
+    if (!item)
+       return NULL;
+    ((PyGEnum*)item)->gtype = gtype;
+
+    return item;
+}
+
+static PyObject *
+pyg_enum_richcompare(PyGEnum *self, PyObject *other, int op)
+{
+    static char warning[256];
+
+    if (!PYGLIB_PyLong_Check(other)) {
+       Py_INCREF(Py_NotImplemented);
+       return Py_NotImplemented;
+    }
+
+    if (PyObject_TypeCheck(other, &PyGEnum_Type) && ((PyGEnum*)other)->gtype != self->gtype) {
+       g_snprintf(warning, sizeof(warning), "comparing different enum types: %s and %s",
+                  g_type_name(self->gtype), g_type_name(((PyGEnum*)other)->gtype));
+       if (PyErr_Warn(PyExc_Warning, warning))
+           return NULL;
+    }
+
+    return pyg_integer_richcompare((PyObject *)self, other, op);
+}
+
+static PyObject *
+pyg_enum_repr(PyGEnum *self)
+{
+    PyObject *module;
+    GEnumClass *enum_class;
+    const char *value;
+    guint index;
+    char *namespace, *module_str;
+    static char tmp[256];
+    long l;
+
+    module = PyObject_GetAttrString ((PyObject *)self, "__module__");
+    if (module == NULL)
+        return NULL;
+
+    if (!PYGLIB_PyUnicode_Check (module)) {
+        Py_DECREF (module);
+        return NULL;
+    }
+
+    enum_class = g_type_class_ref(self->gtype);
+    g_assert(G_IS_ENUM_CLASS(enum_class));
+
+    l = PYGLIB_PyLong_AS_LONG(self);
+    for (index = 0; index < enum_class->n_values; index++)
+        if (l == enum_class->values[index].value)
+            break;
+
+    module_str = PYGLIB_PyUnicode_AsString (module);
+    namespace = g_strrstr (module_str, ".");
+    if (namespace == NULL) {
+        namespace = module_str;
+    } else {
+        namespace += 1;
+    }
+
+    value = enum_class->values[index].value_name;
+    if (value)
+        sprintf(tmp, "<enum %s of type %s.%s>", value,
+                namespace, Py_TYPE (self)->tp_name);
+    else
+        sprintf(tmp, "<enum %ld of type %s.%s>", PYGLIB_PyLong_AS_LONG(self),
+                namespace, Py_TYPE (self)->tp_name);
+    Py_DECREF (module);
+    g_type_class_unref(enum_class);
+
+    return PYGLIB_PyUnicode_FromString(tmp);
+}
+
+static PyObject *
+pyg_enum_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { "value", NULL };
+    long value;
+    PyObject *pytc, *values, *ret, *intvalue;
+    GType gtype;
+    GEnumClass *eclass;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "l", kwlist, &value))
+       return NULL;
+
+    pytc = PyObject_GetAttrString((PyObject *)type, "__gtype__");
+    if (!pytc)
+       return NULL;
+
+    if (!PyObject_TypeCheck(pytc, &PyGTypeWrapper_Type)) {
+       Py_DECREF(pytc);
+       PyErr_SetString(PyExc_TypeError,
+                       "__gtype__ attribute not a typecode");
+       return NULL;
+    }
+
+    gtype = pyg_type_from_object(pytc);
+    Py_DECREF(pytc);
+
+    eclass = G_ENUM_CLASS(g_type_class_ref(gtype));
+
+    /* A check that 0 < value < eclass->n_values was here but got
+     * removed: enumeration values do not need to be consequitive,
+     * e.g. GtkPathPriorityType values are not.
+     */
+
+    values = PyObject_GetAttrString((PyObject *)type, "__enum_values__");
+    if (!values) {
+       g_type_class_unref(eclass);
+       return NULL;
+    }
+
+    /* Note that size of __enum_values__ dictionary can easily be less
+     * than 'n_values'.  This happens if some values of the enum are
+     * numerically equal, e.g. gtk.ANCHOR_N == gtk.ANCHOR_NORTH.
+     * Johan said that "In retrospect, using a dictionary to store the
+     * values might not have been that good", but we need to keep
+     * backward compatibility.
+     */
+    if (!PyDict_Check(values) || (gsize)PyDict_Size(values) > eclass->n_values) {
+       PyErr_SetString(PyExc_TypeError, "__enum_values__ badly formed");
+       Py_DECREF(values);
+       g_type_class_unref(eclass);
+       return NULL;
+    }
+
+    g_type_class_unref(eclass);
+
+    intvalue = PYGLIB_PyLong_FromLong(value);
+    ret = PyDict_GetItem(values, intvalue);
+    Py_DECREF(intvalue);
+    Py_DECREF(values);
+    if (ret)
+        Py_INCREF(ret);
+    else
+       PyErr_Format(PyExc_ValueError, "invalid enum value: %ld", value);
+
+    return ret;
+}
+
+PyObject*
+pyg_enum_from_gtype (GType gtype, int value)
+{
+    PyObject *pyclass, *values, *retval, *intvalue;
+
+    g_return_val_if_fail(gtype != G_TYPE_INVALID, NULL);
+
+    /* Get a wrapper class by:
+     * 1. check for one attached to the gtype
+     * 2. lookup one in a typelib
+     * 3. creating a new one
+     */
+    pyclass = (PyObject*)g_type_get_qdata(gtype, pygenum_class_key);
+    if (!pyclass)
+        pyclass = pygi_type_import_by_g_type(gtype);
+    if (!pyclass)
+        pyclass = pyg_enum_add(NULL, g_type_name(gtype), NULL, gtype);
+    if (!pyclass)
+       return PYGLIB_PyLong_FromLong(value);
+
+    values = PyDict_GetItemString(((PyTypeObject *)pyclass)->tp_dict,
+                                 "__enum_values__");
+    intvalue = PYGLIB_PyLong_FromLong(value);
+    retval = PyDict_GetItem(values, intvalue);
+    if (retval) {
+       Py_INCREF(retval);
+    }
+    else {
+       PyErr_Clear();
+       retval = pyg_enum_val_new(pyclass, gtype, intvalue);
+    }
+    Py_DECREF(intvalue);
+
+    return retval;
+}
+
+/*
+ * pyg_enum_add
+ * Dynamically create a class derived from PyGEnum based on the given GType.
+ */
+PyObject *
+pyg_enum_add (PyObject *   module,
+             const char * typename,
+             const char * strip_prefix,
+             GType        gtype)
+{
+    PyGILState_STATE state;
+    PyObject *instance_dict, *stub, *values, *o;
+    GEnumClass *eclass;
+    guint i;
+
+    g_return_val_if_fail(typename != NULL, NULL);
+    if (!g_type_is_a (gtype, G_TYPE_ENUM)) {
+        PyErr_Format (PyExc_TypeError, "Trying to register gtype '%s' as enum when in fact it is of type '%s'",
+                      g_type_name (gtype), g_type_name (G_TYPE_FUNDAMENTAL (gtype)));
+        return NULL;
+    }
+
+    state = PyGILState_Ensure();
+
+    /* Create a new type derived from GEnum. This is the same as:
+     * >>> stub = type(typename, (GEnum,), {})
+     */
+    instance_dict = PyDict_New();
+    stub = PyObject_CallFunction((PyObject *)&PyType_Type, "s(O)O",
+                                 typename, (PyObject *)&PyGEnum_Type,
+                                 instance_dict);
+    Py_DECREF(instance_dict);
+    if (!stub) {
+       PyErr_SetString(PyExc_RuntimeError, "can't create const");
+       PyGILState_Release(state);
+       return NULL;
+    }
+
+    ((PyTypeObject *)stub)->tp_flags &= ~Py_TPFLAGS_BASETYPE;
+
+    if (module)
+       PyDict_SetItemString(((PyTypeObject *)stub)->tp_dict,
+                            "__module__",
+                            PYGLIB_PyUnicode_FromString(PyModule_GetName(module)));
+
+    g_type_set_qdata(gtype, pygenum_class_key, stub);
+
+    o = pyg_type_wrapper_new(gtype);
+    PyDict_SetItemString(((PyTypeObject *)stub)->tp_dict, "__gtype__", o);
+    Py_DECREF(o);
+
+    if (module) {
+       /* Add it to the module name space */
+       PyModule_AddObject(module, (char*)typename, stub);
+       Py_INCREF(stub);
+    }
+
+    /* Register enum values */
+    eclass = G_ENUM_CLASS(g_type_class_ref(gtype));
+
+    values = PyDict_New();
+    for (i = 0; i < eclass->n_values; i++) {
+       PyObject *item, *intval;
+      
+        intval = PYGLIB_PyLong_FromLong(eclass->values[i].value);
+       item = pyg_enum_val_new(stub, gtype, intval);
+       PyDict_SetItem(values, intval, item);
+        Py_DECREF(intval);
+
+       if (module) {
+           char *prefix;
+
+           prefix = g_strdup(pyg_constant_strip_prefix(eclass->values[i].value_name, strip_prefix));
+           PyModule_AddObject(module, prefix, item);
+           g_free(prefix);
+
+           Py_INCREF(item);
+       }
+    }
+
+    PyDict_SetItemString(((PyTypeObject *)stub)->tp_dict,
+                        "__enum_values__", values);
+    Py_DECREF(values);
+
+    g_type_class_unref(eclass);
+
+    PyGILState_Release(state);
+    return stub;
+}
+
+static PyObject *
+pyg_enum_reduce(PyObject *self, PyObject *args)
+{
+    if (!PyArg_ParseTuple(args, ":GEnum.__reduce__"))
+        return NULL;
+
+    return Py_BuildValue("(O(i)O)", Py_TYPE(self), PYGLIB_PyLong_AsLong(self),
+                         PyObject_GetAttrString(self, "__dict__"));
+}
+
+static PyObject *
+pyg_enum_get_value_name(PyGEnum *self, void *closure)
+{
+  GEnumClass *enum_class;
+  GEnumValue *enum_value;
+  PyObject *retval;
+  gint intvalue;
+
+  if (!pygi_gint_from_py ((PyObject*) self, &intvalue))
+    return NULL;
+
+  enum_class = g_type_class_ref(self->gtype);
+  g_assert(G_IS_ENUM_CLASS(enum_class));
+
+  enum_value = g_enum_get_value(enum_class, intvalue);
+
+  retval = pygi_utf8_to_py (enum_value->value_name);
+  g_type_class_unref(enum_class);
+
+  return retval;
+}
+
+static PyObject *
+pyg_enum_get_value_nick(PyGEnum *self, void *closure)
+{
+  GEnumClass *enum_class;
+  GEnumValue *enum_value;
+  PyObject *retval;
+  gint intvalue;
+
+  if (!pygi_gint_from_py ((PyObject*) self, &intvalue))
+    return NULL;
+
+  enum_class = g_type_class_ref(self->gtype);
+  g_assert(G_IS_ENUM_CLASS(enum_class));
+
+  enum_value = g_enum_get_value(enum_class, intvalue);
+
+  retval = pygi_utf8_to_py (enum_value->value_nick);
+
+  g_type_class_unref(enum_class);
+
+  return retval;
+}
+
+
+static PyMethodDef pyg_enum_methods[] = {
+    { "__reduce__", (PyCFunction)pyg_enum_reduce, METH_VARARGS },
+    { NULL, NULL, 0 }
+};
+
+static PyGetSetDef pyg_enum_getsets[] = {
+    { "value_name", (getter)pyg_enum_get_value_name, (setter)0 },
+    { "value_nick", (getter)pyg_enum_get_value_nick, (setter)0 },
+    { NULL, 0, 0 }
+};
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_enum_register_types(PyObject *d)
+{
+    pygenum_class_key        = g_quark_from_static_string("PyGEnum::class");
+
+    PyGEnum_Type.tp_base = &PYGLIB_PyLong_Type;
+    PyGEnum_Type.tp_new = pyg_enum_new;
+    PyGEnum_Type.tp_hash = PYGLIB_PyLong_Type.tp_hash;
+    PyGEnum_Type.tp_repr = (reprfunc)pyg_enum_repr;
+    PyGEnum_Type.tp_str = (reprfunc)pyg_enum_repr;
+    PyGEnum_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+    PyGEnum_Type.tp_richcompare = (richcmpfunc)pyg_enum_richcompare;
+    PyGEnum_Type.tp_methods = pyg_enum_methods;
+    PyGEnum_Type.tp_getset = pyg_enum_getsets;
+    PYGOBJECT_REGISTER_GTYPE(d, PyGEnum_Type, "GEnum", G_TYPE_ENUM);
+
+    return 0;
+}
diff --git a/gi/pygenum.h b/gi/pygenum.h
new file mode 100644 (file)
index 0000000..5be979a
--- /dev/null
@@ -0,0 +1,49 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGOBJECT_ENUM_H__ 
+#define __PYGOBJECT_ENUM_H__
+
+#include "pygi-python-compat.h"
+
+extern GQuark pygenum_class_key;
+
+#define PyGEnum_Check(x) (PyObject_IsInstance((PyObject *)x, (PyObject *)&PyGEnum_Type) && g_type_is_a(((PyGFlags*)x)->gtype, G_TYPE_ENUM))
+
+typedef struct {
+    PYGLIB_PyLongObject parent;
+    int zero_pad; /* must always be 0 */
+    GType gtype;
+} PyGEnum;
+
+extern PyTypeObject PyGEnum_Type;
+
+PyObject * pyg_enum_add        (PyObject *   module,
+                                const char * type_name,
+                                const char * strip_prefix,
+                                GType        gtype);
+
+PyObject * pyg_enum_from_gtype (GType        gtype,
+                                int          value);
+
+gint pyg_enum_get_value  (GType enum_type, PyObject *obj, gint *val);
+
+int pygi_enum_register_types(PyObject *d);
+
+#endif /* __PYGOBJECT_ENUM_H__ */
diff --git a/gi/pygflags.c b/gi/pygflags.c
new file mode 100644 (file)
index 0000000..01e8a55
--- /dev/null
@@ -0,0 +1,518 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ * Copyright (C) 2004       Johan Dahlin
+ *
+ *   pygflags.c: GFlags wrapper
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "pygi-type.h"
+#include "pygi-util.h"
+#include "pygi-type.h"
+#include "pygflags.h"
+#include "pygboxed.h"
+
+GQuark pygflags_class_key;
+
+PYGLIB_DEFINE_TYPE("gobject.GFlags", PyGFlags_Type, PyGFlags);
+
+static PyObject *
+pyg_flags_val_new(PyObject* subclass, GType gtype, PyObject *intval)
+{
+    PyObject *args, *item;
+    args = Py_BuildValue("(O)", intval);
+    g_assert(PyObject_IsSubclass(subclass, (PyObject*) &PyGFlags_Type));
+    item = PYGLIB_PyLong_Type.tp_new((PyTypeObject*)subclass, args, NULL);
+    Py_DECREF(args);
+    if (!item)
+       return NULL;
+    ((PyGFlags*)item)->gtype = gtype;
+
+    return item;
+}
+
+static PyObject *
+pyg_flags_richcompare(PyGFlags *self, PyObject *other, int op)
+{
+    static char warning[256];
+
+    if (!PYGLIB_PyLong_Check(other)) {
+        Py_INCREF(Py_NotImplemented);
+        return Py_NotImplemented;
+    }
+
+    if (PyObject_TypeCheck(other, &PyGFlags_Type) && ((PyGFlags*)other)->gtype != self->gtype) {
+       g_snprintf(warning, sizeof(warning), "comparing different flags types: %s and %s",
+                  g_type_name(self->gtype), g_type_name(((PyGFlags*)other)->gtype));
+       if (PyErr_Warn(PyExc_Warning, warning))
+           return NULL;
+    }
+
+    return pyg_integer_richcompare((PyObject *)self, other, op);
+}
+
+static char *
+generate_repr(GType gtype, guint value)
+{
+    GFlagsClass *flags_class;
+    char *retval = NULL, *tmp;
+    guint i;
+
+    flags_class = g_type_class_ref(gtype);
+    g_assert(G_IS_FLAGS_CLASS(flags_class));
+
+    for (i = 0; i < flags_class->n_values; i++) {
+       /* Some types (eg GstElementState in GStreamer 0.8) has flags with 0 values,
+         * we're just ignore them for now otherwise they'll always show up
+         */
+        if (flags_class->values[i].value == 0)
+            continue;
+
+        if ((value & flags_class->values[i].value) == flags_class->values[i].value) {
+           if (retval) {
+               tmp = g_strdup_printf("%s | %s", retval, flags_class->values[i].value_name);
+               g_free(retval);
+               retval = tmp;
+           } else {
+               retval = g_strdup_printf("%s", flags_class->values[i].value_name);
+           }
+       }
+    }
+
+    g_type_class_unref(flags_class);
+
+    return retval;
+}
+
+static PyObject *
+pyg_flags_repr(PyGFlags *self)
+{
+    char *tmp, *retval, *module_str, *namespace;
+    PyObject *pyretval, *module;
+
+    tmp = generate_repr(self->gtype, (guint)PYGLIB_PyLong_AsUnsignedLong(self));
+
+    module = PyObject_GetAttrString ((PyObject *)self, "__module__");
+    if (module == NULL)
+        return NULL;
+
+    if (!PYGLIB_PyUnicode_Check (module)) {
+        Py_DECREF (module);
+        return NULL;
+    }
+
+    module_str = PYGLIB_PyUnicode_AsString (module);
+    namespace = g_strrstr (module_str, ".");
+    if (namespace == NULL) {
+        namespace = module_str;
+    } else {
+        namespace += 1;
+    }
+
+    if (tmp)
+        retval = g_strdup_printf("<flags %s of type %s.%s>", tmp,
+                                 namespace, Py_TYPE (self)->tp_name);
+    else
+        retval = g_strdup_printf("<flags %ld of type %s.%s>",
+                                 PYGLIB_PyLong_AsUnsignedLong (self),
+                                 namespace, Py_TYPE (self)->tp_name);
+    g_free(tmp);
+    Py_DECREF (module);
+
+    pyretval = PYGLIB_PyUnicode_FromString(retval);
+    g_free(retval);
+
+    return pyretval;
+}
+
+static PyObject *
+pyg_flags_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { "value", NULL };
+    gulong value;
+    PyObject *pytc, *values, *ret, *pyint;
+    GType gtype;
+    GFlagsClass *eclass;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "k", kwlist, &value))
+       return NULL;
+
+    pytc = PyObject_GetAttrString((PyObject *)type, "__gtype__");
+    if (!pytc)
+       return NULL;
+
+    if (!PyObject_TypeCheck(pytc, &PyGTypeWrapper_Type)) {
+       Py_DECREF(pytc);
+       PyErr_SetString(PyExc_TypeError,
+                       "__gtype__ attribute not a typecode");
+       return NULL;
+    }
+
+    gtype = pyg_type_from_object(pytc);
+    Py_DECREF(pytc);
+
+    eclass = G_FLAGS_CLASS(g_type_class_ref(gtype));
+
+    values = PyObject_GetAttrString((PyObject *)type, "__flags_values__");
+    if (!values) {
+       g_type_class_unref(eclass);
+       return NULL;
+    }
+
+    if (!PyDict_Check(values)) {
+       PyErr_SetString(PyExc_TypeError, "__flags_values__ badly formed");
+       Py_DECREF(values);
+       g_type_class_unref(eclass);
+       return NULL;
+    }
+
+    g_type_class_unref(eclass);
+
+    pyint = PYGLIB_PyLong_FromUnsignedLong(value);
+    ret = PyDict_GetItem(values, pyint);
+    if (!ret) {
+        PyErr_Clear();
+
+        ret = pyg_flags_val_new((PyObject *)type, gtype, pyint);
+        g_assert(ret != NULL);
+    } else {
+        Py_INCREF(ret);
+    }
+
+    Py_DECREF(pyint);
+    Py_DECREF(values);
+
+    return ret;
+}
+
+PyObject*
+pyg_flags_from_gtype (GType gtype, guint value)
+{
+    PyObject *pyclass, *values, *retval, *pyint;
+
+    if (PyErr_Occurred())
+        return PYGLIB_PyLong_FromUnsignedLong(0);
+
+    g_return_val_if_fail(gtype != G_TYPE_INVALID, NULL);
+
+    /* Get a wrapper class by:
+     * 1. check for one attached to the gtype
+     * 2. lookup one in a typelib
+     * 3. creating a new one
+     */
+    pyclass = (PyObject*)g_type_get_qdata(gtype, pygflags_class_key);
+    if (!pyclass)
+        pyclass = pygi_type_import_by_g_type(gtype);
+    if (!pyclass)
+        pyclass = pyg_flags_add(NULL, g_type_name(gtype), NULL, gtype);
+    if (!pyclass)
+       return PYGLIB_PyLong_FromUnsignedLong(value);
+
+    values = PyDict_GetItemString(((PyTypeObject *)pyclass)->tp_dict,
+                                 "__flags_values__");
+    pyint = PYGLIB_PyLong_FromUnsignedLong(value);
+    retval = PyDict_GetItem(values, pyint);
+    if (!retval) {
+       PyErr_Clear();
+
+       retval = pyg_flags_val_new(pyclass, gtype, pyint);
+       g_assert(retval != NULL);
+    } else {
+       Py_INCREF(retval);
+    }
+    Py_DECREF(pyint);
+    
+    return retval;
+}
+
+/*
+ * pyg_flags_add
+ * Dynamically create a class derived from PyGFlags based on the given GType.
+ */
+PyObject *
+pyg_flags_add (PyObject *   module,
+              const char * typename,
+              const char * strip_prefix,
+              GType        gtype)
+{
+    PyGILState_STATE state;
+    PyObject *instance_dict, *stub, *values, *o;
+    GFlagsClass *eclass;
+    guint i;
+
+    g_return_val_if_fail(typename != NULL, NULL);
+    if (!g_type_is_a(gtype, G_TYPE_FLAGS)) {
+        g_warning("Trying to register gtype '%s' as flags when in fact it is of type '%s'",
+                  g_type_name(gtype), g_type_name(G_TYPE_FUNDAMENTAL(gtype)));
+        return NULL;
+    }
+
+    state = PyGILState_Ensure();
+
+    /* Create a new type derived from GFlags. This is the same as:
+     * >>> stub = type(typename, (GFlags,), {})
+     */
+    instance_dict = PyDict_New();
+    stub = PyObject_CallFunction((PyObject *)&PyType_Type, "s(O)O",
+                                 typename, (PyObject *)&PyGFlags_Type,
+                                 instance_dict);
+    Py_DECREF(instance_dict);
+    if (!stub) {
+       PyErr_SetString(PyExc_RuntimeError, "can't create GFlags subtype");
+       PyGILState_Release(state);
+        return NULL;
+    }
+
+    ((PyTypeObject *)stub)->tp_flags &= ~Py_TPFLAGS_BASETYPE;
+
+    if (module) {
+        PyDict_SetItemString(((PyTypeObject *)stub)->tp_dict,
+                             "__module__",
+                             PYGLIB_PyUnicode_FromString(PyModule_GetName(module)));
+
+          /* Add it to the module name space */
+        PyModule_AddObject(module, (char*)typename, stub);
+        Py_INCREF(stub);
+    }
+    g_type_set_qdata(gtype, pygflags_class_key, stub);
+
+    o = pyg_type_wrapper_new(gtype);
+    PyDict_SetItemString(((PyTypeObject *)stub)->tp_dict, "__gtype__", o);
+    Py_DECREF(o);
+
+    /* Register flag values */
+    eclass = G_FLAGS_CLASS(g_type_class_ref(gtype));
+
+    values = PyDict_New();
+    for (i = 0; i < eclass->n_values; i++) {
+      PyObject *item, *intval;
+      
+      intval = PYGLIB_PyLong_FromUnsignedLong(eclass->values[i].value);
+      g_assert(PyErr_Occurred() == NULL);
+      item = pyg_flags_val_new(stub, gtype, intval);
+      PyDict_SetItem(values, intval, item);
+      Py_DECREF(intval);
+
+      if (module) {
+         char *prefix;
+
+         prefix = g_strdup(pyg_constant_strip_prefix(eclass->values[i].value_name, strip_prefix));
+         Py_INCREF(item);
+         PyModule_AddObject(module, prefix, item);
+         g_free(prefix);
+      }
+      Py_DECREF(item);
+    }
+
+    PyDict_SetItemString(((PyTypeObject *)stub)->tp_dict,
+                        "__flags_values__", values);
+    Py_DECREF(values);
+
+    g_type_class_unref(eclass);
+
+    PyGILState_Release(state);
+
+    return stub;
+}
+
+static PyObject *
+pyg_flags_and(PyGFlags *a, PyGFlags *b)
+{
+       if (!PyGFlags_Check(a) || !PyGFlags_Check(b))
+               return PYGLIB_PyLong_Type.tp_as_number->nb_and((PyObject*)a,
+                                                      (PyObject*)b);
+
+       return pyg_flags_from_gtype(a->gtype,
+                                   (guint)(PYGLIB_PyLong_AsUnsignedLong(a) & PYGLIB_PyLong_AsUnsignedLong(b)));
+}
+
+static PyObject *
+pyg_flags_or(PyGFlags *a, PyGFlags *b)
+{
+       if (!PyGFlags_Check(a) || !PyGFlags_Check(b))
+               return PYGLIB_PyLong_Type.tp_as_number->nb_or((PyObject*)a,
+                                                     (PyObject*)b);
+
+       return pyg_flags_from_gtype(a->gtype, (guint)(PYGLIB_PyLong_AsUnsignedLong(a) | PYGLIB_PyLong_AsUnsignedLong(b)));
+}
+
+static PyObject *
+pyg_flags_xor(PyGFlags *a, PyGFlags *b)
+{
+       if (!PyGFlags_Check(a) || !PyGFlags_Check(b))
+               return PYGLIB_PyLong_Type.tp_as_number->nb_xor((PyObject*)a,
+                                                      (PyObject*)b);
+
+       return pyg_flags_from_gtype(a->gtype,
+                                   (guint)(PYGLIB_PyLong_AsUnsignedLong(a) ^ PYGLIB_PyLong_AsUnsignedLong(b)));
+
+}
+
+static PyObject *
+pyg_flags_warn (PyObject *self, PyObject *args)
+{
+    if (PyErr_Warn(PyExc_Warning, "unsupported arithmetic operation for flags type"))
+       return NULL;
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+pyg_flags_get_first_value_name(PyGFlags *self, void *closure)
+{
+  GFlagsClass *flags_class;
+  GFlagsValue *flags_value;
+  PyObject *retval;
+
+  flags_class = g_type_class_ref(self->gtype);
+  g_assert(G_IS_FLAGS_CLASS(flags_class));
+  flags_value = g_flags_get_first_value(flags_class, (guint)PYGLIB_PyLong_AsUnsignedLong(self));
+  if (flags_value)
+      retval = PYGLIB_PyUnicode_FromString(flags_value->value_name);
+  else {
+      retval = Py_None;
+      Py_INCREF(Py_None);
+  }
+  g_type_class_unref(flags_class);
+
+  return retval;
+}
+
+static PyObject *
+pyg_flags_get_first_value_nick(PyGFlags *self, void *closure)
+{
+  GFlagsClass *flags_class;
+  GFlagsValue *flags_value;
+  PyObject *retval;
+
+  flags_class = g_type_class_ref(self->gtype);
+  g_assert(G_IS_FLAGS_CLASS(flags_class));
+
+  flags_value = g_flags_get_first_value(flags_class, (guint)PYGLIB_PyLong_AsUnsignedLong(self));
+  if (flags_value)
+      retval = PYGLIB_PyUnicode_FromString(flags_value->value_nick);
+  else {
+      retval = Py_None;
+      Py_INCREF(Py_None);
+  }
+  g_type_class_unref(flags_class);
+
+  return retval;
+}
+
+static PyObject *
+pyg_flags_get_value_names(PyGFlags *self, void *closure)
+{
+  GFlagsClass *flags_class;
+  PyObject *retval;
+  guint i;
+
+  flags_class = g_type_class_ref(self->gtype);
+  g_assert(G_IS_FLAGS_CLASS(flags_class));
+
+  retval = PyList_New(0);
+  for (i = 0; i < flags_class->n_values; i++) {
+      PyObject *value_name;
+
+      if ((PYGLIB_PyLong_AsUnsignedLong (self) & flags_class->values[i].value) == flags_class->values[i].value) {
+        value_name = PYGLIB_PyUnicode_FromString (flags_class->values[i].value_name);
+        PyList_Append (retval, value_name);
+        Py_DECREF (value_name);
+      }
+  }
+
+  g_type_class_unref(flags_class);
+
+  return retval;
+}
+
+static PyObject *
+pyg_flags_get_value_nicks(PyGFlags *self, void *closure)
+{
+  GFlagsClass *flags_class;
+  PyObject *retval;
+  guint i;
+
+  flags_class = g_type_class_ref(self->gtype);
+  g_assert(G_IS_FLAGS_CLASS(flags_class));
+
+  retval = PyList_New(0);
+  for (i = 0; i < flags_class->n_values; i++)
+      if ((PYGLIB_PyLong_AsUnsignedLong(self) & flags_class->values[i].value) == flags_class->values[i].value) {
+         PyObject *py_nick = PYGLIB_PyUnicode_FromString(flags_class->values[i].value_nick);
+         PyList_Append(retval, py_nick);
+         Py_DECREF (py_nick);
+      }
+
+  g_type_class_unref(flags_class);
+
+  return retval;
+}
+
+static PyGetSetDef pyg_flags_getsets[] = {
+    { "first_value_name", (getter)pyg_flags_get_first_value_name, (setter)0 },
+    { "first_value_nick", (getter)pyg_flags_get_first_value_nick, (setter)0 },
+    { "value_names", (getter)pyg_flags_get_value_names, (setter)0 },
+    { "value_nicks", (getter)pyg_flags_get_value_nicks, (setter)0 },
+    { NULL, 0, 0 }
+};
+
+static PyNumberMethods pyg_flags_as_number = {
+       (binaryfunc)pyg_flags_warn,             /* nb_add */
+       (binaryfunc)pyg_flags_warn,             /* nb_subtract */
+       (binaryfunc)pyg_flags_warn,             /* nb_multiply */
+       (binaryfunc)pyg_flags_warn,             /* nb_divide */
+       (binaryfunc)pyg_flags_warn,             /* nb_remainder */
+#if PY_VERSION_HEX < 0x03000000
+        (binaryfunc)pyg_flags_warn,            /* nb_divmod */
+#endif
+       (ternaryfunc)pyg_flags_warn,            /* nb_power */
+       0,                                      /* nb_negative */
+       0,                                      /* nb_positive */
+       0,                                      /* nb_absolute */
+       0,                                      /* nb_nonzero */
+       0,                                      /* nb_invert */
+       0,                                      /* nb_lshift */
+       0,                                      /* nb_rshift */
+       (binaryfunc)pyg_flags_and,              /* nb_and */
+       (binaryfunc)pyg_flags_xor,              /* nb_xor */
+       (binaryfunc)pyg_flags_or,               /* nb_or */
+};
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_flags_register_types(PyObject *d)
+{
+    pygflags_class_key = g_quark_from_static_string("PyGFlags::class");
+
+    PyGFlags_Type.tp_base = &PYGLIB_PyLong_Type;
+    PyGFlags_Type.tp_new = pyg_flags_new;
+    PyGFlags_Type.tp_hash = PYGLIB_PyLong_Type.tp_hash;
+    PyGFlags_Type.tp_repr = (reprfunc)pyg_flags_repr;
+    PyGFlags_Type.tp_as_number = &pyg_flags_as_number;
+    PyGFlags_Type.tp_str = (reprfunc)pyg_flags_repr;
+    PyGFlags_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+    PyGFlags_Type.tp_richcompare = (richcmpfunc)pyg_flags_richcompare;
+    PyGFlags_Type.tp_getset = pyg_flags_getsets;
+    PYGOBJECT_REGISTER_GTYPE(d, PyGFlags_Type, "GFlags", G_TYPE_FLAGS);
+
+    return 0;
+}
diff --git a/gi/pygflags.h b/gi/pygflags.h
new file mode 100644 (file)
index 0000000..7a8b2c7
--- /dev/null
@@ -0,0 +1,46 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGOBJECT_FLAGS_H__ 
+#define __PYGOBJECT_FLAGS_H__
+
+extern GQuark pygflags_class_key;
+
+typedef struct {
+    PYGLIB_PyLongObject parent;
+    int zero_pad; /* must always be 0 */
+    GType gtype;
+} PyGFlags;
+
+extern PyTypeObject PyGFlags_Type;
+
+#define PyGFlags_Check(x) (PyObject_IsInstance((PyObject *)x, (PyObject *)&PyGFlags_Type) && g_type_is_a(((PyGFlags*)x)->gtype, G_TYPE_FLAGS))
+
+extern PyObject * pyg_flags_add        (PyObject *   module,
+                                        const char * type_name,
+                                        const char * strip_prefix,
+                                        GType        gtype);
+extern PyObject * pyg_flags_from_gtype (GType        gtype,
+                                        guint        value);
+
+gint pyg_flags_get_value (GType flag_type, PyObject *obj, guint *val);
+
+int pygi_flags_register_types(PyObject *d);
+
+#endif /* __PYGOBJECT_FLAGS_H__ */
diff --git a/gi/pygi-argument.c b/gi/pygi-argument.c
new file mode 100644 (file)
index 0000000..76ce8b4
--- /dev/null
@@ -0,0 +1,1272 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+ *
+ *   pygi-argument.c: GIArgument - PyObject conversion functions.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+
+#include <string.h>
+#include <time.h>
+
+#include "pygobject-internal.h"
+
+#include <pygenum.h>
+#include <pygflags.h>
+
+#include "pygi-python-compat.h"
+#include "pygi-argument.h"
+#include "pygi-info.h"
+#include "pygi-value.h"
+#include "pygi-basictype.h"
+#include "pygi-object.h"
+#include "pygi-struct-marshal.h"
+#include "pygi-error.h"
+#include "pygi-foreign.h"
+#include "pygi-type.h"
+#include "pygi-util.h"
+
+
+gboolean
+pygi_argument_to_gssize (GIArgument *arg_in,
+                         GITypeTag  type_tag,
+                         gssize *gssize_out)
+{
+    switch (type_tag) {
+      case GI_TYPE_TAG_INT8:
+          *gssize_out = arg_in->v_int8;
+          return TRUE;
+      case GI_TYPE_TAG_UINT8:
+          *gssize_out = arg_in->v_uint8;
+          return TRUE;
+      case GI_TYPE_TAG_INT16:
+          *gssize_out = arg_in->v_int16;
+          return TRUE;
+      case GI_TYPE_TAG_UINT16:
+          *gssize_out = arg_in->v_uint16;
+          return TRUE;
+      case GI_TYPE_TAG_INT32:
+          *gssize_out = arg_in->v_int32;
+          return TRUE;
+      case GI_TYPE_TAG_UINT32:
+          *gssize_out = arg_in->v_uint32;
+          return TRUE;
+      case GI_TYPE_TAG_INT64:
+          if (arg_in->v_int64 > G_MAXSSIZE || arg_in->v_int64 < G_MINSSIZE) {
+              PyErr_Format (PyExc_TypeError,
+                            "Unable to marshal %s to gssize",
+                            g_type_tag_to_string(type_tag));
+              return FALSE;
+          }
+          *gssize_out = (gssize)arg_in->v_int64;
+          return TRUE;
+      case GI_TYPE_TAG_UINT64:
+          if (arg_in->v_uint64 > G_MAXSSIZE) {
+              PyErr_Format (PyExc_TypeError,
+                            "Unable to marshal %s to gssize",
+                            g_type_tag_to_string(type_tag));
+              return FALSE;
+          }
+          *gssize_out = (gssize)arg_in->v_uint64;
+          return TRUE;
+      default:
+          PyErr_Format (PyExc_TypeError,
+                        "Unable to marshal %s to gssize",
+                        g_type_tag_to_string(type_tag));
+          return FALSE;
+    }
+}
+
+static GITypeTag
+_pygi_get_storage_type (GITypeInfo *type_info)
+{
+    GITypeTag type_tag = g_type_info_get_tag (type_info);
+
+    if (type_tag == GI_TYPE_TAG_INTERFACE) {
+        GIBaseInfo *interface = g_type_info_get_interface (type_info);
+        switch (g_base_info_get_type (interface)) {
+            case GI_INFO_TYPE_ENUM:
+            case GI_INFO_TYPE_FLAGS:
+                type_tag = g_enum_info_get_storage_type ((GIEnumInfo *)interface);
+                break;
+            default:
+                /* FIXME: we might have something to do for other types */
+                break;
+        }
+        g_base_info_unref (interface);
+    }
+    return type_tag;
+}
+
+void
+_pygi_hash_pointer_to_arg (GIArgument *arg,
+                           GITypeInfo *type_info)
+{
+    GITypeTag type_tag = _pygi_get_storage_type (type_info);
+
+    switch (type_tag) {
+        case GI_TYPE_TAG_INT8:
+            arg->v_int8 = (gint8)GPOINTER_TO_INT (arg->v_pointer);
+            break;
+        case GI_TYPE_TAG_INT16:
+            arg->v_int16 = (gint16)GPOINTER_TO_INT (arg->v_pointer);
+            break;
+        case GI_TYPE_TAG_INT32:
+            arg->v_int32 = (gint32)GPOINTER_TO_INT (arg->v_pointer);
+            break;
+        case GI_TYPE_TAG_UINT8:
+            arg->v_uint8 = (guint8)GPOINTER_TO_UINT (arg->v_pointer);
+            break;
+        case GI_TYPE_TAG_UINT16:
+            arg->v_uint16 = (guint16)GPOINTER_TO_UINT (arg->v_pointer);
+            break;
+        case GI_TYPE_TAG_UINT32:
+            arg->v_uint32 = (guint32)GPOINTER_TO_UINT (arg->v_pointer);
+            break;
+        case GI_TYPE_TAG_GTYPE:
+            arg->v_size = GPOINTER_TO_SIZE (arg->v_pointer);
+            break;
+        case GI_TYPE_TAG_UTF8:
+        case GI_TYPE_TAG_FILENAME:
+        case GI_TYPE_TAG_INTERFACE:
+        case GI_TYPE_TAG_ARRAY:
+            break;
+        default:
+            g_critical ("Unsupported type %s", g_type_tag_to_string(type_tag));
+    }
+}
+
+gpointer
+_pygi_arg_to_hash_pointer (const GIArgument *arg,
+                           GITypeInfo       *type_info)
+{
+    GITypeTag type_tag = _pygi_get_storage_type (type_info);
+
+    switch (type_tag) {
+        case GI_TYPE_TAG_INT8:
+            return GINT_TO_POINTER (arg->v_int8);
+        case GI_TYPE_TAG_UINT8:
+            return GINT_TO_POINTER (arg->v_uint8);
+        case GI_TYPE_TAG_INT16:
+            return GINT_TO_POINTER (arg->v_int16);
+        case GI_TYPE_TAG_UINT16:
+            return GINT_TO_POINTER (arg->v_uint16);
+        case GI_TYPE_TAG_INT32:
+            return GINT_TO_POINTER (arg->v_int32);
+        case GI_TYPE_TAG_UINT32:
+            return GINT_TO_POINTER (arg->v_uint32);
+        case GI_TYPE_TAG_GTYPE:
+            return GSIZE_TO_POINTER (arg->v_size);
+        case GI_TYPE_TAG_UTF8:
+        case GI_TYPE_TAG_FILENAME:
+        case GI_TYPE_TAG_INTERFACE:
+        case GI_TYPE_TAG_ARRAY:
+            return arg->v_pointer;
+        default:
+            g_critical ("Unsupported type %s", g_type_tag_to_string(type_tag));
+            return arg->v_pointer;
+    }
+}
+
+
+/**
+ * _pygi_argument_array_length_marshal:
+ * @length_arg_index: Index of length argument in the callables args list.
+ * @user_data1: (type Array(GValue)): Array of GValue arguments to retrieve length
+ * @user_data2: (type GICallableInfo): Callable info to get the argument from.
+ *
+ * Generic marshalling policy for array length arguments in callables.
+ *
+ * Returns: The length of the array or -1 on failure.
+ */
+gssize
+_pygi_argument_array_length_marshal (gsize length_arg_index,
+                                     void *user_data1,
+                                     void *user_data2)
+{
+    GIArgInfo length_arg_info;
+    GITypeInfo length_type_info;
+    GIArgument length_arg;
+    gssize array_len = -1;
+    GValue *values = (GValue *)user_data1;
+    GICallableInfo *callable_info = (GICallableInfo *)user_data2;
+
+    g_callable_info_load_arg (callable_info, (gint)length_arg_index, &length_arg_info);
+    g_arg_info_load_type (&length_arg_info, &length_type_info);
+
+    length_arg = _pygi_argument_from_g_value (&(values[length_arg_index]),
+                                              &length_type_info);
+    if (!pygi_argument_to_gssize (&length_arg,
+                                  g_type_info_get_tag (&length_type_info),
+                                  &array_len)) {
+        return -1;
+    }
+
+    return array_len;
+}
+
+/**
+ * _pygi_argument_to_array
+ * @arg: The argument to convert
+ * @array_length_policy: Closure for marshalling the array length argument when needed.
+ * @user_data1: Generic user data passed to the array_length_policy.
+ * @user_data2: Generic user data passed to the array_length_policy.
+ * @type_info: The type info for @arg
+ * @out_free_array: A return location for a gboolean that indicates whether
+ *                  or not the wrapped GArray should be freed
+ *
+ * Make sure an array type argument is wrapped in a GArray.
+ *
+ * Note: This method can *not* be folded into _pygi_argument_to_object() because
+ * arrays are special in the sense that they might require access to @args in
+ * order to get the length.
+ *
+ * Returns: A GArray wrapping @arg. If @out_free_array has been set to TRUE then
+ *          free the array with g_array_free() without freeing the data members.
+ *          Otherwise don't free the array.
+ */
+GArray *
+_pygi_argument_to_array (GIArgument  *arg,
+                         PyGIArgArrayLengthPolicy array_length_policy,
+                         void        *user_data1,
+                         void        *user_data2,
+                         GITypeInfo  *type_info,
+                         gboolean    *out_free_array)
+{
+    GITypeInfo *item_type_info;
+    gboolean is_zero_terminated;
+    gsize item_size;
+    gssize length;
+    GArray *g_array;
+    
+    g_return_val_if_fail (g_type_info_get_tag (type_info) == GI_TYPE_TAG_ARRAY, NULL);
+
+    if (arg->v_pointer == NULL) {
+        return NULL;
+    }
+    
+    switch (g_type_info_get_array_type (type_info)) {
+        case GI_ARRAY_TYPE_C:
+            is_zero_terminated = g_type_info_is_zero_terminated (type_info);
+            item_type_info = g_type_info_get_param_type (type_info, 0);
+
+            item_size = _pygi_g_type_info_size (item_type_info);
+
+            g_base_info_unref ( (GIBaseInfo *) item_type_info);
+
+            if (is_zero_terminated) {
+                length = g_strv_length (arg->v_pointer);
+            } else {
+                length = g_type_info_get_array_fixed_size (type_info);
+                if (length < 0) {
+                    gint length_arg_pos;
+
+                    if (G_UNLIKELY (array_length_policy == NULL)) {
+                        g_critical ("Unable to determine array length for %p",
+                                    arg->v_pointer);
+                        g_array = g_array_new (is_zero_terminated, FALSE, (guint)item_size);
+                        *out_free_array = TRUE;
+                        return g_array;
+                    }
+
+                    length_arg_pos = g_type_info_get_array_length (type_info);
+                    g_assert (length_arg_pos >= 0);
+
+                    length = array_length_policy (length_arg_pos, user_data1, user_data2);
+                    if (length < 0) {
+                        return NULL;
+                    }
+                }
+            }
+
+            g_assert (length >= 0);
+
+            g_array = g_array_new (is_zero_terminated, FALSE, (guint)item_size);
+
+            g_free (g_array->data);
+            g_array->data = arg->v_pointer;
+            g_array->len = (guint)length;
+            *out_free_array = TRUE;
+            break;
+        case GI_ARRAY_TYPE_ARRAY:
+        case GI_ARRAY_TYPE_BYTE_ARRAY:
+            /* Note: GByteArray is really just a GArray */
+            g_array = arg->v_pointer;
+            *out_free_array = FALSE;
+            break;
+        case GI_ARRAY_TYPE_PTR_ARRAY:
+        {
+            GPtrArray *ptr_array = (GPtrArray*) arg->v_pointer;
+            g_array = g_array_sized_new (FALSE, FALSE,
+                                         sizeof(gpointer),
+                                         ptr_array->len);
+             g_array->data = (char*) ptr_array->pdata;
+             g_array->len = ptr_array->len;
+             *out_free_array = TRUE;
+             break;
+        }
+        default:
+            g_critical ("Unexpected array type %u",
+                        g_type_info_get_array_type (type_info));
+            g_array = NULL;
+            break;
+    }
+
+    return g_array;
+}
+
+GIArgument
+_pygi_argument_from_object (PyObject   *object,
+                            GITypeInfo *type_info,
+                            GITransfer  transfer)
+{
+    GIArgument arg;
+    GITypeTag type_tag;
+    gpointer cleanup_data = NULL;
+
+    memset(&arg, 0, sizeof(GIArgument));
+    type_tag = g_type_info_get_tag (type_info);
+
+    switch (type_tag) {
+        case GI_TYPE_TAG_ARRAY:
+        {
+            Py_ssize_t py_length;
+            guint length, i;
+            gboolean is_zero_terminated;
+            GITypeInfo *item_type_info;
+            gsize item_size;
+            GArray *array;
+            GITransfer item_transfer;
+
+            if (object == Py_None) {
+                arg.v_pointer = NULL;
+                break;
+            }
+
+            /* Note, strings are sequences, but we cannot accept them here */
+            if (!PySequence_Check (object) || 
+#if PY_VERSION_HEX < 0x03000000
+                PyString_Check (object) || 
+#endif
+                PyUnicode_Check (object)) {
+                PyErr_SetString (PyExc_TypeError, "expected sequence");
+                break;
+            }
+
+            py_length = PySequence_Length (object);
+            if (py_length < 0)
+                break;
+
+            if (!pygi_guint_from_pyssize (py_length, &length))
+                break;
+
+            is_zero_terminated = g_type_info_is_zero_terminated (type_info);
+            item_type_info = g_type_info_get_param_type (type_info, 0);
+
+            /* we handle arrays that are really strings specially, see below */
+            if (g_type_info_get_tag (item_type_info) == GI_TYPE_TAG_UINT8)
+               item_size = 1;
+            else
+               item_size = sizeof (GIArgument);
+
+            array = g_array_sized_new (is_zero_terminated, FALSE, (guint)item_size, length);
+            if (array == NULL) {
+                g_base_info_unref ( (GIBaseInfo *) item_type_info);
+                PyErr_NoMemory();
+                break;
+            }
+
+            if (g_type_info_get_tag (item_type_info) == GI_TYPE_TAG_UINT8 &&
+                PYGLIB_PyBytes_Check(object)) {
+
+                memcpy(array->data, PYGLIB_PyBytes_AsString(object), length);
+                array->len = length;
+                goto array_success;
+            }
+
+
+            item_transfer = transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer;
+
+            for (i = 0; i < length; i++) {
+                PyObject *py_item;
+                GIArgument item;
+
+                py_item = PySequence_GetItem (object, i);
+                if (py_item == NULL) {
+                    goto array_item_error;
+                }
+
+                item = _pygi_argument_from_object (py_item, item_type_info, item_transfer);
+
+                Py_DECREF (py_item);
+
+                if (PyErr_Occurred()) {
+                    goto array_item_error;
+                }
+
+                g_array_insert_val (array, i, item);
+                continue;
+
+array_item_error:
+                /* Free everything we have converted so far. */
+                _pygi_argument_release ( (GIArgument *) &array, type_info,
+                                         GI_TRANSFER_NOTHING, GI_DIRECTION_IN);
+                array = NULL;
+
+                _PyGI_ERROR_PREFIX ("Item %u: ", i);
+                break;
+            }
+
+array_success:
+            arg.v_pointer = array;
+
+            g_base_info_unref ( (GIBaseInfo *) item_type_info);
+            break;
+        }
+        case GI_TYPE_TAG_INTERFACE:
+        {
+            GIBaseInfo *info;
+            GIInfoType info_type;
+
+            info = g_type_info_get_interface (type_info);
+            info_type = g_base_info_get_type (info);
+
+            switch (info_type) {
+                case GI_INFO_TYPE_CALLBACK:
+                    /* This should be handled in invoke() */
+                    g_assert_not_reached();
+                    break;
+                case GI_INFO_TYPE_BOXED:
+                case GI_INFO_TYPE_STRUCT:
+                case GI_INFO_TYPE_UNION:
+                {
+                    GType g_type;
+                    PyObject *py_type;
+                    gboolean is_foreign = (info_type == GI_INFO_TYPE_STRUCT) &&
+                                          (g_struct_info_is_foreign ((GIStructInfo *) info));
+
+                    g_type = g_registered_type_info_get_g_type ( (GIRegisteredTypeInfo *) info);
+                    py_type = pygi_type_import_by_gi_info ( (GIBaseInfo *) info);
+
+                    /* Note for G_TYPE_VALUE g_type:
+                     * This will currently leak the GValue that is allocated and
+                     * stashed in arg.v_pointer. Out argument marshaling for caller
+                     * allocated GValues already pass in memory for the GValue.
+                     * Further re-factoring is needed to fix this leak.
+                     * See: https://bugzilla.gnome.org/show_bug.cgi?id=693405
+                     */
+                    pygi_arg_struct_from_py_marshal (object,
+                                                     &arg,
+                                                     NULL, /*arg_name*/
+                                                     info, /*interface_info*/
+                                                     g_type,
+                                                     py_type,
+                                                     transfer,
+                                                     FALSE, /*copy_reference*/
+                                                     is_foreign,
+                                                     g_type_info_is_pointer (type_info));
+
+                    Py_DECREF (py_type);
+                    break;
+                }
+                case GI_INFO_TYPE_ENUM:
+                case GI_INFO_TYPE_FLAGS:
+                {
+                    if (!pygi_gint_from_py (object, &arg.v_int))
+                        break;
+
+                    break;
+                }
+                case GI_INFO_TYPE_INTERFACE:
+                case GI_INFO_TYPE_OBJECT:
+                    /* An error within this call will result in a NULL arg */
+                    pygi_arg_gobject_out_arg_from_py (object, &arg, transfer);
+                    break;
+
+                default:
+                    g_assert_not_reached();
+            }
+            g_base_info_unref (info);
+            break;
+        }
+        case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
+        {
+            Py_ssize_t length;
+            GITypeInfo *item_type_info;
+            GSList *list = NULL;
+            GITransfer item_transfer;
+            Py_ssize_t i;
+
+            if (object == Py_None) {
+                arg.v_pointer = NULL;
+                break;
+            }
+
+            length = PySequence_Length (object);
+            if (length < 0) {
+                break;
+            }
+
+            item_type_info = g_type_info_get_param_type (type_info, 0);
+            g_assert (item_type_info != NULL);
+
+            item_transfer = transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer;
+
+            for (i = length - 1; i >= 0; i--) {
+                PyObject *py_item;
+                GIArgument item;
+
+                py_item = PySequence_GetItem (object, i);
+                if (py_item == NULL) {
+                    goto list_item_error;
+                }
+
+                item = _pygi_argument_from_object (py_item, item_type_info, item_transfer);
+
+                Py_DECREF (py_item);
+
+                if (PyErr_Occurred()) {
+                    goto list_item_error;
+                }
+
+                if (type_tag == GI_TYPE_TAG_GLIST) {
+                    list = (GSList *) g_list_prepend ( (GList *) list, item.v_pointer);
+                } else {
+                    list = g_slist_prepend (list, item.v_pointer);
+                }
+
+                continue;
+
+list_item_error:
+                /* Free everything we have converted so far. */
+                _pygi_argument_release ( (GIArgument *) &list, type_info,
+                                         GI_TRANSFER_NOTHING, GI_DIRECTION_IN);
+                list = NULL;
+
+                _PyGI_ERROR_PREFIX ("Item %zd: ", i);
+                break;
+            }
+
+            arg.v_pointer = list;
+
+            g_base_info_unref ( (GIBaseInfo *) item_type_info);
+
+            break;
+        }
+        case GI_TYPE_TAG_GHASH:
+        {
+            Py_ssize_t length;
+            PyObject *keys;
+            PyObject *values;
+            GITypeInfo *key_type_info;
+            GITypeInfo *value_type_info;
+            GITypeTag key_type_tag;
+            GHashFunc hash_func;
+            GEqualFunc equal_func;
+            GHashTable *hash_table;
+            GITransfer item_transfer;
+            Py_ssize_t i;
+
+
+            if (object == Py_None) {
+                arg.v_pointer = NULL;
+                break;
+            }
+
+            length = PyMapping_Length (object);
+            if (length < 0) {
+                break;
+            }
+
+            keys = PyMapping_Keys (object);
+            if (keys == NULL) {
+                break;
+            }
+
+            values = PyMapping_Values (object);
+            if (values == NULL) {
+                Py_DECREF (keys);
+                break;
+            }
+
+            key_type_info = g_type_info_get_param_type (type_info, 0);
+            g_assert (key_type_info != NULL);
+
+            value_type_info = g_type_info_get_param_type (type_info, 1);
+            g_assert (value_type_info != NULL);
+
+            key_type_tag = g_type_info_get_tag (key_type_info);
+
+            switch (key_type_tag) {
+                case GI_TYPE_TAG_UTF8:
+                case GI_TYPE_TAG_FILENAME:
+                    hash_func = g_str_hash;
+                    equal_func = g_str_equal;
+                    break;
+                default:
+                    hash_func = NULL;
+                    equal_func = NULL;
+            }
+
+            hash_table = g_hash_table_new (hash_func, equal_func);
+            if (hash_table == NULL) {
+                PyErr_NoMemory();
+                goto hash_table_release;
+            }
+
+            item_transfer = transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer;
+
+            for (i = 0; i < length; i++) {
+                PyObject *py_key;
+                PyObject *py_value;
+                GIArgument key;
+                GIArgument value;
+
+                py_key = PyList_GET_ITEM (keys, i);
+                py_value = PyList_GET_ITEM (values, i);
+
+                key = _pygi_argument_from_object (py_key, key_type_info, item_transfer);
+                if (PyErr_Occurred()) {
+                    goto hash_table_item_error;
+                }
+
+                value = _pygi_argument_from_object (py_value, value_type_info, item_transfer);
+                if (PyErr_Occurred()) {
+                    _pygi_argument_release (&key, type_info, GI_TRANSFER_NOTHING, GI_DIRECTION_IN);
+                    goto hash_table_item_error;
+                }
+
+                g_hash_table_insert (hash_table, key.v_pointer,
+                                     _pygi_arg_to_hash_pointer (&value, value_type_info));
+                continue;
+
+hash_table_item_error:
+                /* Free everything we have converted so far. */
+                _pygi_argument_release ( (GIArgument *) &hash_table, type_info,
+                                         GI_TRANSFER_NOTHING, GI_DIRECTION_IN);
+                hash_table = NULL;
+
+                _PyGI_ERROR_PREFIX ("Item %zd: ", i);
+                break;
+            }
+
+            arg.v_pointer = hash_table;
+
+hash_table_release:
+            g_base_info_unref ( (GIBaseInfo *) key_type_info);
+            g_base_info_unref ( (GIBaseInfo *) value_type_info);
+            Py_DECREF (keys);
+            Py_DECREF (values);
+            break;
+        }
+        case GI_TYPE_TAG_ERROR:
+            PyErr_SetString (PyExc_NotImplementedError, "error marshalling is not supported yet");
+            /* TODO */
+            break;
+        default:
+            /* Ignores cleanup data for now. */
+            pygi_marshal_from_py_basic_type (object, &arg, type_tag, transfer, &cleanup_data);
+            break;
+    }
+
+    return arg;
+}
+
+/**
+ * _pygi_argument_to_object:
+ * @arg: The argument to convert to an object.
+ * @type_info: Type info for @arg
+ * @transfer:
+ *
+ * If the argument is of type array, it must be encoded in a GArray, by calling
+ * _pygi_argument_to_array(). This logic can not be folded into this method
+ * as determining array lengths may require access to method call arguments.
+ *
+ * Returns: A PyObject representing @arg
+ */
+PyObject *
+_pygi_argument_to_object (GIArgument  *arg,
+                          GITypeInfo *type_info,
+                          GITransfer transfer)
+{
+    GITypeTag type_tag;
+    PyObject *object = NULL;
+
+    type_tag = g_type_info_get_tag (type_info);
+
+    switch (type_tag) {
+        case GI_TYPE_TAG_VOID:
+        {
+            if (g_type_info_is_pointer (type_info)) {
+                g_warn_if_fail (transfer == GI_TRANSFER_NOTHING);
+                object = PyLong_FromVoidPtr (arg->v_pointer);
+            }
+            break;
+        }
+        case GI_TYPE_TAG_ARRAY:
+        {
+            /* Arrays are assumed to be packed in a GArray */
+            GArray *array;
+            GITypeInfo *item_type_info;
+            GITypeTag item_type_tag;
+            GITransfer item_transfer;
+            gsize i, item_size;
+
+            if (arg->v_pointer == NULL)
+                return PyList_New (0);
+            
+            item_type_info = g_type_info_get_param_type (type_info, 0);
+            g_assert (item_type_info != NULL);
+
+            item_type_tag = g_type_info_get_tag (item_type_info);
+            item_transfer = transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer;
+            
+            array = arg->v_pointer;
+            item_size = g_array_get_element_size (array);
+            
+            if (G_UNLIKELY (item_size > sizeof(GIArgument))) {
+                g_critical ("Stack overflow protection. "
+                            "Can't copy array element into GIArgument.");
+                return PyList_New (0);
+            }
+
+            if (item_type_tag == GI_TYPE_TAG_UINT8) {
+                /* Return as a byte array */
+                object = PYGLIB_PyBytes_FromStringAndSize (array->data, array->len);
+            } else {
+                object = PyList_New (array->len);
+                if (object == NULL) {
+                    g_critical ("Failure to allocate array for %u items", array->len);
+                    g_base_info_unref ( (GIBaseInfo *) item_type_info);
+                    break;
+                }
+
+                for (i = 0; i < array->len; i++) {
+                    GIArgument item = { 0 };
+                    PyObject *py_item;
+                    
+                    memcpy (&item, array->data + i * item_size, item_size);
+
+                    py_item = _pygi_argument_to_object (&item, item_type_info, item_transfer);
+                    if (py_item == NULL) {
+                        Py_CLEAR (object);
+                        _PyGI_ERROR_PREFIX ("Item %zu: ", i);
+                        break;
+                    }
+
+                    PyList_SET_ITEM (object, i, py_item);
+                }
+            }
+
+            g_base_info_unref ( (GIBaseInfo *) item_type_info);
+            break;
+        }
+        case GI_TYPE_TAG_INTERFACE:
+        {
+            GIBaseInfo *info;
+            GIInfoType info_type;
+
+            info = g_type_info_get_interface (type_info);
+            info_type = g_base_info_get_type (info);
+
+            switch (info_type) {
+                case GI_INFO_TYPE_CALLBACK:
+                {
+                    g_assert_not_reached();
+                }
+                case GI_INFO_TYPE_BOXED:
+                case GI_INFO_TYPE_STRUCT:
+                case GI_INFO_TYPE_UNION:
+                {
+                    PyObject *py_type;
+                    GType g_type = g_registered_type_info_get_g_type ( (GIRegisteredTypeInfo *) info);
+                    gboolean is_foreign = (info_type == GI_INFO_TYPE_STRUCT) &&
+                                          (g_struct_info_is_foreign ((GIStructInfo *) info));
+
+                    /* Special case variant and none to force loading from py module. */
+                    if (g_type == G_TYPE_VARIANT || g_type == G_TYPE_NONE) {
+                        py_type = pygi_type_import_by_gi_info (info);
+                    } else {
+                        py_type = pygi_type_get_from_g_type (g_type);
+                    }
+
+                    object = pygi_arg_struct_to_py_marshal (arg,
+                                                            info, /*interface_info*/
+                                                            g_type,
+                                                            py_type,
+                                                            transfer,
+                                                            FALSE, /*is_allocated*/
+                                                            is_foreign);
+
+                    Py_XDECREF (py_type);
+                    break;
+                }
+                case GI_INFO_TYPE_ENUM:
+                case GI_INFO_TYPE_FLAGS:
+                {
+                    GType type;
+
+                    type = g_registered_type_info_get_g_type ( (GIRegisteredTypeInfo *) info);
+
+                    if (type == G_TYPE_NONE) {
+                        /* An enum with a GType of None is an enum without GType */
+                        PyObject *py_type = pygi_type_import_by_gi_info (info);
+                        PyObject *py_args = NULL;
+
+                        if (!py_type)
+                            return NULL;
+
+                        py_args = PyTuple_New (1);
+                        if (PyTuple_SetItem (py_args, 0, pygi_gint_to_py (arg->v_int)) != 0) {
+                            Py_DECREF (py_args);
+                            Py_DECREF (py_type);
+                            return NULL;
+                        }
+
+                        object = PyObject_CallFunction (py_type, "i", arg->v_int);
+
+                        Py_DECREF (py_args);
+                        Py_DECREF (py_type);
+
+                    } else if (info_type == GI_INFO_TYPE_ENUM) {
+                        object = pyg_enum_from_gtype (type, arg->v_int);
+                    } else {
+                        object = pyg_flags_from_gtype (type, arg->v_uint);
+                    }
+
+                    break;
+                }
+                case GI_INFO_TYPE_INTERFACE:
+                case GI_INFO_TYPE_OBJECT:
+                    object = pygi_arg_gobject_to_py_called_from_c (arg, transfer);
+
+                    break;
+                default:
+                    g_assert_not_reached();
+            }
+
+            g_base_info_unref (info);
+            break;
+        }
+        case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
+        {
+            GSList *list;
+            gsize length;
+            GITypeInfo *item_type_info;
+            GITransfer item_transfer;
+            gsize i;
+
+            list = arg->v_pointer;
+            length = g_slist_length (list);
+
+            object = PyList_New (length);
+            if (object == NULL) {
+                break;
+            }
+
+            item_type_info = g_type_info_get_param_type (type_info, 0);
+            g_assert (item_type_info != NULL);
+
+            item_transfer = transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer;
+
+            for (i = 0; list != NULL; list = g_slist_next (list), i++) {
+                GIArgument item;
+                PyObject *py_item;
+
+                item.v_pointer = list->data;
+
+                py_item = _pygi_argument_to_object (&item, item_type_info, item_transfer);
+                if (py_item == NULL) {
+                    Py_CLEAR (object);
+                    _PyGI_ERROR_PREFIX ("Item %zu: ", i);
+                    break;
+                }
+
+                PyList_SET_ITEM (object, i, py_item);
+            }
+
+            g_base_info_unref ( (GIBaseInfo *) item_type_info);
+            break;
+        }
+        case GI_TYPE_TAG_GHASH:
+        {
+            GITypeInfo *key_type_info;
+            GITypeInfo *value_type_info;
+            GITransfer item_transfer;
+            GHashTableIter hash_table_iter;
+            GIArgument key;
+            GIArgument value;
+
+            if (arg->v_pointer == NULL) {
+                object = Py_None;
+                Py_INCREF (object);
+                break;
+            }
+
+            object = PyDict_New();
+            if (object == NULL) {
+                break;
+            }
+
+            key_type_info = g_type_info_get_param_type (type_info, 0);
+            g_assert (key_type_info != NULL);
+            g_assert (g_type_info_get_tag (key_type_info) != GI_TYPE_TAG_VOID);
+
+            value_type_info = g_type_info_get_param_type (type_info, 1);
+            g_assert (value_type_info != NULL);
+            g_assert (g_type_info_get_tag (value_type_info) != GI_TYPE_TAG_VOID);
+
+            item_transfer = transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer;
+
+            g_hash_table_iter_init (&hash_table_iter, (GHashTable *) arg->v_pointer);
+            while (g_hash_table_iter_next (&hash_table_iter, &key.v_pointer, &value.v_pointer)) {
+                PyObject *py_key;
+                PyObject *py_value;
+                int retval;
+
+                py_key = _pygi_argument_to_object (&key, key_type_info, item_transfer);
+                if (py_key == NULL) {
+                    break;
+                }
+
+                _pygi_hash_pointer_to_arg (&value, value_type_info);
+                py_value = _pygi_argument_to_object (&value, value_type_info, item_transfer);
+                if (py_value == NULL) {
+                    Py_DECREF (py_key);
+                    break;
+                }
+
+                retval = PyDict_SetItem (object, py_key, py_value);
+
+                Py_DECREF (py_key);
+                Py_DECREF (py_value);
+
+                if (retval < 0) {
+                    Py_CLEAR (object);
+                    break;
+                }
+            }
+
+            g_base_info_unref ( (GIBaseInfo *) key_type_info);
+            g_base_info_unref ( (GIBaseInfo *) value_type_info);
+            break;
+        }
+        case GI_TYPE_TAG_ERROR:
+        {
+            GError *error = (GError *) arg->v_pointer;
+            if (error != NULL && transfer == GI_TRANSFER_NOTHING) {
+                /* If we have not been transferred the ownership we must copy
+                 * the error, because pygi_error_check() is going to free it.
+                 */
+                error = g_error_copy (error);
+            }
+
+            if (pygi_error_check (&error)) {
+                PyObject *err_type;
+                PyObject *err_value;
+                PyObject *err_trace;
+                PyErr_Fetch (&err_type, &err_value, &err_trace);
+                Py_XDECREF (err_type);
+                Py_XDECREF (err_trace);
+                object = err_value;
+            } else {
+                object = Py_None;
+                Py_INCREF (object);
+                break;
+            }
+            break;
+        }
+        default:
+        {
+            object = pygi_marshal_to_py_basic_type (arg, type_tag, transfer);
+        }
+    }
+
+    return object;
+}
+
+void
+_pygi_argument_release (GIArgument   *arg,
+                        GITypeInfo  *type_info,
+                        GITransfer   transfer,
+                        GIDirection  direction)
+{
+    GITypeTag type_tag;
+    gboolean is_out = (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT);
+
+    type_tag = g_type_info_get_tag (type_info);
+
+    switch (type_tag) {
+        case GI_TYPE_TAG_VOID:
+            /* Don't do anything, it's transparent to the C side */
+            break;
+        case GI_TYPE_TAG_BOOLEAN:
+        case GI_TYPE_TAG_INT8:
+        case GI_TYPE_TAG_UINT8:
+        case GI_TYPE_TAG_INT16:
+        case GI_TYPE_TAG_UINT16:
+        case GI_TYPE_TAG_INT32:
+        case GI_TYPE_TAG_UINT32:
+        case GI_TYPE_TAG_INT64:
+        case GI_TYPE_TAG_UINT64:
+        case GI_TYPE_TAG_FLOAT:
+        case GI_TYPE_TAG_DOUBLE:
+        case GI_TYPE_TAG_GTYPE:
+        case GI_TYPE_TAG_UNICHAR:
+            break;
+        case GI_TYPE_TAG_FILENAME:
+        case GI_TYPE_TAG_UTF8:
+            /* With allow-none support the string could be NULL */
+            if ((arg->v_string != NULL &&
+                    (direction == GI_DIRECTION_IN && transfer == GI_TRANSFER_NOTHING))
+                    || (direction == GI_DIRECTION_OUT && transfer == GI_TRANSFER_EVERYTHING)) {
+                g_free (arg->v_string);
+            }
+            break;
+        case GI_TYPE_TAG_ARRAY:
+        {
+            GArray *array;
+            gsize i;
+
+            if (arg->v_pointer == NULL) {
+                return;
+            }
+
+            array = arg->v_pointer;
+
+            if ( (direction == GI_DIRECTION_IN && transfer != GI_TRANSFER_EVERYTHING)
+                    || (direction == GI_DIRECTION_OUT && transfer == GI_TRANSFER_EVERYTHING)) {
+                GITypeInfo *item_type_info;
+                GITransfer item_transfer;
+
+                item_type_info = g_type_info_get_param_type (type_info, 0);
+
+                item_transfer = direction == GI_DIRECTION_IN ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING;
+
+                /* Free the items */
+                for (i = 0; i < array->len; i++) {
+                    GIArgument item;
+                    memcpy (&item, array->data + (g_array_get_element_size (array) * i), sizeof (GIArgument));
+                    _pygi_argument_release (&item, item_type_info, item_transfer, direction);
+                }
+
+                g_base_info_unref ( (GIBaseInfo *) item_type_info);
+            }
+
+            if ( (direction == GI_DIRECTION_IN && transfer == GI_TRANSFER_NOTHING)
+                    || (direction == GI_DIRECTION_OUT && transfer != GI_TRANSFER_NOTHING)) {
+                g_array_free (array, TRUE);
+            }
+
+            break;
+        }
+        case GI_TYPE_TAG_INTERFACE:
+        {
+            GIBaseInfo *info;
+            GIInfoType info_type;
+
+            info = g_type_info_get_interface (type_info);
+            info_type = g_base_info_get_type (info);
+
+            switch (info_type) {
+                case GI_INFO_TYPE_CALLBACK:
+                    /* TODO */
+                    break;
+                case GI_INFO_TYPE_BOXED:
+                case GI_INFO_TYPE_STRUCT:
+                case GI_INFO_TYPE_UNION:
+                {
+                    GType type;
+
+                    if (arg->v_pointer == NULL) {
+                        return;
+                    }
+
+                    type = g_registered_type_info_get_g_type ( (GIRegisteredTypeInfo *) info);
+
+                    if (g_type_is_a (type, G_TYPE_VALUE)) {
+                        GValue *value;
+
+                        value = arg->v_pointer;
+
+                        if ( (direction == GI_DIRECTION_IN && transfer != GI_TRANSFER_EVERYTHING)
+                                || (direction == GI_DIRECTION_OUT && transfer == GI_TRANSFER_EVERYTHING)) {
+                            g_value_unset (value);
+                        }
+
+                        if ( (direction == GI_DIRECTION_IN && transfer == GI_TRANSFER_NOTHING)
+                                || (direction == GI_DIRECTION_OUT && transfer != GI_TRANSFER_NOTHING)) {
+                            g_slice_free (GValue, value);
+                        }
+                    } else if (g_type_is_a (type, G_TYPE_CLOSURE)) {
+                        if (direction == GI_DIRECTION_IN && transfer == GI_TRANSFER_NOTHING) {
+                            g_closure_unref (arg->v_pointer);
+                        }
+                    } else if (info_type == GI_INFO_TYPE_STRUCT &&
+                               g_struct_info_is_foreign ((GIStructInfo*) info)) {
+                        if (direction == GI_DIRECTION_OUT && transfer == GI_TRANSFER_EVERYTHING) {
+                            pygi_struct_foreign_release (info, arg->v_pointer);
+                        }
+                    } else if (g_type_is_a (type, G_TYPE_BOXED)) {
+                    } else if (g_type_is_a (type, G_TYPE_POINTER) || type == G_TYPE_NONE) {
+                        g_warn_if_fail (!g_type_info_is_pointer (type_info) || transfer == GI_TRANSFER_NOTHING);
+                    }
+
+                    break;
+                }
+                case GI_INFO_TYPE_ENUM:
+                case GI_INFO_TYPE_FLAGS:
+                    break;
+                case GI_INFO_TYPE_INTERFACE:
+                case GI_INFO_TYPE_OBJECT:
+                    if (arg->v_pointer == NULL) {
+                        return;
+                    }
+                    if (is_out && transfer == GI_TRANSFER_EVERYTHING) {
+                        g_object_unref (arg->v_pointer);
+                    }
+                    break;
+                default:
+                    g_assert_not_reached();
+            }
+
+            g_base_info_unref (info);
+            break;
+        }
+        case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
+        {
+            GSList *list;
+
+            if (arg->v_pointer == NULL) {
+                return;
+            }
+
+            list = arg->v_pointer;
+
+            if ( (direction == GI_DIRECTION_IN && transfer != GI_TRANSFER_EVERYTHING)
+                    || (direction == GI_DIRECTION_OUT && transfer == GI_TRANSFER_EVERYTHING)) {
+                GITypeInfo *item_type_info;
+                GITransfer item_transfer;
+                GSList *item;
+
+                item_type_info = g_type_info_get_param_type (type_info, 0);
+                g_assert (item_type_info != NULL);
+
+                item_transfer = direction == GI_DIRECTION_IN ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING;
+
+                /* Free the items */
+                for (item = list; item != NULL; item = g_slist_next (item)) {
+                    _pygi_argument_release ( (GIArgument *) &item->data, item_type_info,
+                                             item_transfer, direction);
+                }
+
+                g_base_info_unref ( (GIBaseInfo *) item_type_info);
+            }
+
+            if ( (direction == GI_DIRECTION_IN && transfer == GI_TRANSFER_NOTHING)
+                    || (direction == GI_DIRECTION_OUT && transfer != GI_TRANSFER_NOTHING)) {
+                if (type_tag == GI_TYPE_TAG_GLIST) {
+                    g_list_free ( (GList *) list);
+                } else {
+                    /* type_tag == GI_TYPE_TAG_GSLIST */
+                    g_slist_free (list);
+                }
+            }
+
+            break;
+        }
+        case GI_TYPE_TAG_GHASH:
+        {
+            GHashTable *hash_table;
+
+            if (arg->v_pointer == NULL) {
+                return;
+            }
+
+            hash_table = arg->v_pointer;
+
+            if (direction == GI_DIRECTION_IN && transfer != GI_TRANSFER_EVERYTHING) {
+                /* We created the table without a destroy function, so keys and
+                 * values need to be released. */
+                GITypeInfo *key_type_info;
+                GITypeInfo *value_type_info;
+                GITransfer item_transfer;
+                GHashTableIter hash_table_iter;
+                gpointer key;
+                gpointer value;
+
+                key_type_info = g_type_info_get_param_type (type_info, 0);
+                g_assert (key_type_info != NULL);
+
+                value_type_info = g_type_info_get_param_type (type_info, 1);
+                g_assert (value_type_info != NULL);
+
+                if (direction == GI_DIRECTION_IN) {
+                    item_transfer = GI_TRANSFER_NOTHING;
+                } else {
+                    item_transfer = GI_TRANSFER_EVERYTHING;
+                }
+
+                g_hash_table_iter_init (&hash_table_iter, hash_table);
+                while (g_hash_table_iter_next (&hash_table_iter, &key, &value)) {
+                    _pygi_argument_release ( (GIArgument *) &key, key_type_info,
+                                             item_transfer, direction);
+                    _pygi_argument_release ( (GIArgument *) &value, value_type_info,
+                                             item_transfer, direction);
+                }
+
+                g_base_info_unref ( (GIBaseInfo *) key_type_info);
+                g_base_info_unref ( (GIBaseInfo *) value_type_info);
+            } else if (direction == GI_DIRECTION_OUT && transfer == GI_TRANSFER_CONTAINER) {
+                /* Be careful to avoid keys and values being freed if the
+                 * callee gave a destroy function. */
+                g_hash_table_steal_all (hash_table);
+            }
+
+            if ( (direction == GI_DIRECTION_IN && transfer == GI_TRANSFER_NOTHING)
+                    || (direction == GI_DIRECTION_OUT && transfer != GI_TRANSFER_NOTHING)) {
+                g_hash_table_unref (hash_table);
+            }
+
+            break;
+        }
+        case GI_TYPE_TAG_ERROR:
+        {
+            GError *error;
+
+            if (arg->v_pointer == NULL) {
+                return;
+            }
+
+            error = * (GError **) arg->v_pointer;
+
+            if (error != NULL) {
+                g_error_free (error);
+            }
+
+            g_slice_free (GError *, arg->v_pointer);
+            break;
+        }
+        default:
+            break;
+    }
+}
+
diff --git a/gi/pygi-argument.h b/gi/pygi-argument.h
new file mode 100644 (file)
index 0000000..2e889dd
--- /dev/null
@@ -0,0 +1,71 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_ARGUMENT_H__
+#define __PYGI_ARGUMENT_H__
+
+#include <Python.h>
+
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+
+/* Private */
+typedef gssize (*PyGIArgArrayLengthPolicy) (gsize item_index,
+                                            void *user_data1,
+                                            void *user_data2);
+
+gssize _pygi_argument_array_length_marshal (gsize length_arg_index,
+                                            void *user_data1,
+                                            void *user_data2);
+
+gpointer _pygi_arg_to_hash_pointer (const GIArgument *arg,
+                                    GITypeInfo       *type_info);
+
+void _pygi_hash_pointer_to_arg (GIArgument *arg,
+                                GITypeInfo *type_info);
+
+GArray* _pygi_argument_to_array (GIArgument  *arg,
+                                 PyGIArgArrayLengthPolicy array_length_policy,
+                                 void        *user_data1,
+                                 void        *user_data2,
+                                 GITypeInfo  *type_info,
+                                 gboolean    *out_free_array);
+
+GIArgument _pygi_argument_from_object (PyObject   *object,
+                                      GITypeInfo *type_info,
+                                      GITransfer  transfer);
+
+PyObject* _pygi_argument_to_object (GIArgument  *arg,
+                                    GITypeInfo *type_info,
+                                    GITransfer  transfer);
+
+void _pygi_argument_release (GIArgument   *arg,
+                             GITypeInfo  *type_info,
+                             GITransfer   transfer,
+                             GIDirection  direction);
+
+gboolean pygi_argument_to_gssize (GIArgument *arg_in,
+                                  GITypeTag  type_tag,
+                                  gssize *gssize_out);
+
+G_END_DECLS
+
+#endif /* __PYGI_ARGUMENT_H__ */
diff --git a/gi/pygi-array.c b/gi/pygi-array.c
new file mode 100644 (file)
index 0000000..073e143
--- /dev/null
@@ -0,0 +1,964 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include <glib.h>
+
+#include "pygi-python-compat.h"
+#include "pygi-array.h"
+#include "pygi-info.h"
+#include "pygi-marshal-cleanup.h"
+#include "pygi-basictype.h"
+#include "pygi-util.h"
+
+/* Needed for _pygi_marshal_cleanup_from_py_interface_struct_gvalue hack */
+#include "pygi-struct-marshal.h"
+
+/*
+ * GArray to Python
+ */
+
+static gboolean
+gi_argument_from_py_ssize_t (GIArgument   *arg_out,
+                             Py_ssize_t    size_in,
+                             GITypeTag     type_tag)
+{
+    switch (type_tag) {
+    case GI_TYPE_TAG_VOID:
+    case GI_TYPE_TAG_BOOLEAN:
+        goto unhandled_type;
+
+    case GI_TYPE_TAG_INT8:
+        if (size_in >= G_MININT8 && size_in <= G_MAXINT8) {
+            arg_out->v_int8 = (gint8)size_in;
+            return TRUE;
+        } else {
+            goto overflow;
+        }
+
+    case GI_TYPE_TAG_UINT8:
+        if (size_in >= 0 && size_in <= G_MAXUINT8) {
+            arg_out->v_uint8 = (guint8)size_in;
+            return TRUE;
+        } else {
+            goto overflow;
+        }
+
+    case GI_TYPE_TAG_INT16:
+        if (size_in >= G_MININT16 && size_in <= G_MAXINT16) {
+            arg_out->v_int16 = (gint16)size_in;
+            return TRUE;
+        } else {
+            goto overflow;
+        }
+
+    case GI_TYPE_TAG_UINT16:
+        if (size_in >= 0 && size_in <= G_MAXUINT16) {
+            arg_out->v_uint16 = (guint16)size_in;
+            return TRUE;
+        } else {
+            goto overflow;
+        }
+
+        /* Ranges assume two's complement */
+    case GI_TYPE_TAG_INT32:
+        if (size_in >= G_MININT32 && size_in <= G_MAXINT32) {
+            arg_out->v_int32 = (gint32)size_in;
+            return TRUE;
+        } else {
+            goto overflow;
+        }
+
+    case GI_TYPE_TAG_UINT32:
+        if (size_in >= 0 && (gsize)size_in <= G_MAXUINT32) {
+            arg_out->v_uint32 = (guint32)size_in;
+            return TRUE;
+        } else {
+            goto overflow;
+        }
+
+    case GI_TYPE_TAG_INT64:
+        arg_out->v_int64 = size_in;
+        return TRUE;
+
+    case GI_TYPE_TAG_UINT64:
+        if (size_in >= 0) {
+            arg_out->v_uint64 = size_in;
+            return TRUE;
+        } else {
+            goto overflow;
+        }
+
+    case GI_TYPE_TAG_FLOAT:
+    case GI_TYPE_TAG_DOUBLE:
+    case GI_TYPE_TAG_GTYPE:
+    case GI_TYPE_TAG_UTF8:
+    case GI_TYPE_TAG_FILENAME:
+    case GI_TYPE_TAG_ARRAY:
+    case GI_TYPE_TAG_INTERFACE:
+    case GI_TYPE_TAG_GLIST:
+    case GI_TYPE_TAG_GSLIST:
+    case GI_TYPE_TAG_GHASH:
+    case GI_TYPE_TAG_ERROR:
+    case GI_TYPE_TAG_UNICHAR:
+    default:
+        goto unhandled_type;
+    }
+
+ overflow:
+    PyErr_Format (PyExc_OverflowError,
+                  "Unable to marshal C Py_ssize_t %zd to %s",
+                  size_in,
+                  g_type_tag_to_string (type_tag));
+    return FALSE;
+
+ unhandled_type:
+    PyErr_Format (PyExc_TypeError,
+                  "Unable to marshal C Py_ssize_t %zd to %s",
+                  size_in,
+                  g_type_tag_to_string (type_tag));
+    return FALSE;
+}
+
+static gboolean
+gi_argument_to_gsize (GIArgument *arg_in,
+                      gsize      *gsize_out,
+                      GITypeTag   type_tag)
+{
+    switch (type_tag) {
+      case GI_TYPE_TAG_INT8:
+          *gsize_out = arg_in->v_int8;
+          return TRUE;
+      case GI_TYPE_TAG_UINT8:
+          *gsize_out = arg_in->v_uint8;
+          return TRUE;
+      case GI_TYPE_TAG_INT16:
+          *gsize_out = arg_in->v_int16;
+          return TRUE;
+      case GI_TYPE_TAG_UINT16:
+          *gsize_out = arg_in->v_uint16;
+          return TRUE;
+      case GI_TYPE_TAG_INT32:
+          *gsize_out = arg_in->v_int32;
+          return TRUE;
+      case GI_TYPE_TAG_UINT32:
+          *gsize_out = arg_in->v_uint32;
+          return TRUE;
+      case GI_TYPE_TAG_INT64:
+          if (arg_in->v_uint64 > G_MAXSIZE) {
+              PyErr_Format (PyExc_TypeError,
+                            "Unable to marshal %s to gsize",
+                            g_type_tag_to_string (type_tag));
+              return FALSE;
+          }
+          *gsize_out = (gsize)arg_in->v_int64;
+          return TRUE;
+      case GI_TYPE_TAG_UINT64:
+          if (arg_in->v_uint64 > G_MAXSIZE) {
+              PyErr_Format (PyExc_TypeError,
+                            "Unable to marshal %s to gsize",
+                            g_type_tag_to_string (type_tag));
+              return FALSE;
+          }
+          *gsize_out = (gsize)arg_in->v_uint64;
+          return TRUE;
+      default:
+          PyErr_Format (PyExc_TypeError,
+                        "Unable to marshal %s to gsize",
+                        g_type_tag_to_string (type_tag));
+          return FALSE;
+    }
+}
+
+static gboolean
+_pygi_marshal_from_py_array (PyGIInvokeState   *state,
+                             PyGICallableCache *callable_cache,
+                             PyGIArgCache      *arg_cache,
+                             PyObject          *py_arg,
+                             GIArgument        *arg,
+                             gpointer          *cleanup_data)
+{
+    PyGIMarshalFromPyFunc from_py_marshaller;
+    guint i = 0;
+    gsize success_count = 0;
+    Py_ssize_t py_length;
+    guint length;
+    guint item_size;
+    gboolean is_ptr_array;
+    GArray *array_ = NULL;
+    PyGISequenceCache *sequence_cache = (PyGISequenceCache *)arg_cache;
+    PyGIArgGArray *array_cache = (PyGIArgGArray *)arg_cache;
+    GITransfer cleanup_transfer = arg_cache->transfer;
+
+
+    if (py_arg == Py_None) {
+        arg->v_pointer = NULL;
+        return TRUE;
+    }
+
+    if (!PySequence_Check (py_arg)) {
+        PyErr_Format (PyExc_TypeError, "Must be sequence, not %s",
+                      Py_TYPE (py_arg)->tp_name);
+        return FALSE;
+    }
+
+    py_length = PySequence_Length (py_arg);
+    if (py_length < 0)
+        return FALSE;
+
+    if (!pygi_guint_from_pyssize (py_length, &length))
+        return FALSE;
+
+    if (array_cache->fixed_size >= 0 &&
+            (guint)array_cache->fixed_size != length) {
+        PyErr_Format (PyExc_ValueError, "Must contain %zd items, not %u",
+                      array_cache->fixed_size, length);
+
+        return FALSE;
+    }
+
+    item_size = (guint)array_cache->item_size;
+    is_ptr_array = (array_cache->array_type == GI_ARRAY_TYPE_PTR_ARRAY);
+    if (is_ptr_array) {
+        array_ = (GArray *)g_ptr_array_sized_new (length);
+    } else {
+        array_ = g_array_sized_new (array_cache->is_zero_terminated,
+                                    TRUE,
+                                    item_size,
+                                    length);
+    }
+
+    if (array_ == NULL) {
+        PyErr_NoMemory ();
+        return FALSE;
+    }
+
+    if (sequence_cache->item_cache->type_tag == GI_TYPE_TAG_UINT8 &&
+        PYGLIB_PyBytes_Check (py_arg)) {
+        gchar *data = PYGLIB_PyBytes_AsString (py_arg);
+
+        /* Avoid making a copy if the data
+         * is not transferred to the C function
+         * and cannot not be modified by it.
+         */
+        if (array_cache->array_type == GI_ARRAY_TYPE_C &&
+            arg_cache->transfer == GI_TRANSFER_NOTHING &&
+            !array_cache->is_zero_terminated) {
+            g_free (array_->data);
+            array_->data = data;
+            cleanup_transfer = GI_TRANSFER_EVERYTHING;
+        } else {
+            memcpy (array_->data, data, length);
+        }
+        array_->len = length;
+        if (array_cache->is_zero_terminated) {
+            /* If array_ has been created with zero_termination, space for the
+             * terminator is properly allocated, so we're not off-by-one here. */
+            array_->data[length] = '\0';
+        }
+        goto array_success;
+    }
+
+    from_py_marshaller = sequence_cache->item_cache->from_py_marshaller;
+    for (i = 0, success_count = 0; i < length; i++) {
+        GIArgument item = {0};
+        gpointer item_cleanup_data = NULL;
+        PyObject *py_item = PySequence_GetItem (py_arg, i);
+        if (py_item == NULL)
+            goto err;
+
+        if (!from_py_marshaller ( state,
+                                  callable_cache,
+                                  sequence_cache->item_cache,
+                                  py_item,
+                                 &item,
+                                 &item_cleanup_data)) {
+            Py_DECREF (py_item);
+            goto err;
+        }
+        Py_DECREF (py_item);
+
+        if (item_cleanup_data != NULL && item_cleanup_data != item.v_pointer) {
+            /* We only support one level of data discrepancy between an items
+             * data and its cleanup data. This is because we only track a single
+             * extra cleanup data pointer per-argument and cannot track the entire
+             * array of items differing data and cleanup_data.
+             * For example, this would fail if trying to marshal an array of
+             * callback closures marked with SCOPE call type where the cleanup data
+             * is different from the items v_pointer, likewise an array of arrays.
+             */
+            PyErr_SetString(PyExc_RuntimeError, "Cannot cleanup item data for array due to "
+                                                "the items data its cleanup data being different.");
+            goto err;
+        }
+
+        /* FIXME: it is much more efficent to have seperate marshaller
+         *        for ptr arrays than doing the evaluation
+         *        and casting each loop iteration
+         */
+        if (is_ptr_array) {
+            g_ptr_array_add((GPtrArray *)array_, item.v_pointer);
+        } else if (sequence_cache->item_cache->is_pointer) {
+            /* if the item is a pointer, simply copy the pointer */
+            g_assert (item_size == sizeof (item.v_pointer));
+            g_array_insert_val (array_, i, item);
+        } else if (sequence_cache->item_cache->type_tag == GI_TYPE_TAG_INTERFACE) {
+            /* Special case handling of flat arrays of gvalue/boxed/struct */
+            PyGIInterfaceCache *item_iface_cache = (PyGIInterfaceCache *) sequence_cache->item_cache;
+            GIBaseInfo *base_info = (GIBaseInfo *) item_iface_cache->interface_info;
+            GIInfoType info_type = g_base_info_get_type (base_info);
+
+            switch (info_type) {
+                case GI_INFO_TYPE_UNION:
+                case GI_INFO_TYPE_STRUCT:
+                {
+                    PyGIArgCache *item_arg_cache = (PyGIArgCache *)item_iface_cache;
+                    PyGIMarshalCleanupFunc from_py_cleanup = item_arg_cache->from_py_cleanup;
+
+                    if (g_type_is_a (item_iface_cache->g_type, G_TYPE_VALUE)) {
+                        /* Special case GValue flat arrays to properly init and copy the contents. */
+                        GValue* dest = (GValue*)(void*)(array_->data + (i * item_size));
+                        if (item.v_pointer != NULL) {
+                            memset (dest, 0, item_size);
+                            g_value_init (dest, G_VALUE_TYPE ((GValue*) item.v_pointer));
+                            g_value_copy ((GValue*) item.v_pointer, dest);
+                        }
+                        /* Manually increment the length because we are manually setting the memory. */
+                        array_->len++;
+
+                    } else {
+                        /* Handles flat arrays of boxed or struct types. */
+                        g_array_insert_vals (array_, i, item.v_pointer, 1);
+                    }
+
+                    /* Cleanup any memory left by the per-item marshaler because
+                     * _pygi_marshal_cleanup_from_py_array will not know about this
+                     * due to "item" being a temporarily marshaled value done on the stack.
+                     */
+                    if (from_py_cleanup)
+                        from_py_cleanup (state, item_arg_cache, py_item, item_cleanup_data, TRUE);
+
+                    break;
+                }
+                default:
+                    g_array_insert_val (array_, i, item);
+            }
+        } else {
+            /* default value copy of a simple type */
+            g_array_insert_val (array_, i, item);
+        }
+
+        success_count++;
+    }
+    goto array_success;
+
+err:
+    if (sequence_cache->item_cache->from_py_cleanup != NULL) {
+        gsize j;
+        PyGIMarshalCleanupFunc cleanup_func =
+            sequence_cache->item_cache->from_py_cleanup;
+
+        /* Only attempt per item cleanup on pointer items */
+        if (sequence_cache->item_cache->is_pointer) {
+            for(j = 0; j < success_count; j++) {
+                PyObject *py_seq_item = PySequence_GetItem (py_arg, j);
+                cleanup_func (state,
+                              sequence_cache->item_cache,
+                              py_seq_item,
+                              is_ptr_array ?
+                                      g_ptr_array_index ((GPtrArray *)array_, j) :
+                                      g_array_index (array_, gpointer, j),
+                              TRUE);
+                Py_DECREF (py_seq_item);
+            }
+        }
+    }
+
+    if (is_ptr_array)
+        g_ptr_array_free ( ( GPtrArray *)array_, TRUE);
+    else
+        g_array_free (array_, TRUE);
+    _PyGI_ERROR_PREFIX ("Item %u: ", i);
+    return FALSE;
+
+array_success:
+    if (array_cache->len_arg_index >= 0) {
+        /* we have an child arg to handle */
+        PyGIArgCache *child_cache =
+            _pygi_callable_cache_get_arg (callable_cache, (guint)array_cache->len_arg_index);
+
+        if (!gi_argument_from_py_ssize_t (&state->args[child_cache->c_arg_index].arg_value,
+                                          length,
+                                          child_cache->type_tag)) {
+            goto err;
+        }
+    }
+
+    if (array_cache->array_type == GI_ARRAY_TYPE_C) {
+        /* In the case of GI_ARRAY_C, we give the data directly as the argument
+         * but keep the array_ wrapper as cleanup data so we don't have to find
+         * it's length again.
+         */
+        arg->v_pointer = array_->data;
+
+        if (cleanup_transfer == GI_TRANSFER_EVERYTHING) {
+            g_array_free (array_, FALSE);
+            *cleanup_data = NULL;
+        } else {
+            *cleanup_data = array_;
+        }
+    } else {
+        arg->v_pointer = array_;
+
+        if (cleanup_transfer == GI_TRANSFER_NOTHING) {
+            /* Free everything in cleanup. */
+            *cleanup_data = array_;
+        } else if (cleanup_transfer == GI_TRANSFER_CONTAINER) {
+            /* Make a shallow copy so we can free the elements later in cleanup
+             * because it is possible invoke will free the list before our cleanup. */
+            *cleanup_data = is_ptr_array ?
+                    (gpointer)g_ptr_array_ref ((GPtrArray *)array_) :
+                    (gpointer)g_array_ref (array_);
+        } else { /* GI_TRANSFER_EVERYTHING */
+            /* No cleanup, everything is given to the callee. */
+            *cleanup_data = NULL;
+        }
+    }
+
+    return TRUE;
+}
+
+static void
+_pygi_marshal_cleanup_from_py_array (PyGIInvokeState *state,
+                                     PyGIArgCache    *arg_cache,
+                                     PyObject        *py_arg,
+                                     gpointer         data,
+                                     gboolean         was_processed)
+{
+    if (was_processed) {
+        GArray *array_ = NULL;
+        GPtrArray *ptr_array_ = NULL;
+        PyGISequenceCache *sequence_cache = (PyGISequenceCache *)arg_cache;
+        PyGIArgGArray *array_cache = (PyGIArgGArray *)arg_cache;
+
+        if (array_cache->array_type == GI_ARRAY_TYPE_PTR_ARRAY) {
+            ptr_array_ = (GPtrArray *) data;
+        } else {
+            array_ = (GArray *) data;
+        }
+
+        /* clean up items first */
+        if (sequence_cache->item_cache->from_py_cleanup != NULL) {
+            gsize i;
+            guint len;
+            PyGIMarshalCleanupFunc cleanup_func =
+                sequence_cache->item_cache->from_py_cleanup;
+
+            g_assert (array_ || ptr_array_);
+            len = (array_ != NULL) ? array_->len : ptr_array_->len;
+
+            for (i = 0; i < len; i++) {
+                gpointer item;
+                PyObject *py_item = NULL;
+
+                /* case 1: GPtrArray */
+                if (ptr_array_ != NULL)
+                    item = g_ptr_array_index (ptr_array_, i);
+                /* case 2: C array or GArray with object pointers */
+                else if (sequence_cache->item_cache->is_pointer)
+                    item = g_array_index (array_, gpointer, i);
+                /* case 3: C array or GArray with simple types or structs */
+                else {
+                    item = array_->data + i * array_cache->item_size;
+                    /* special-case hack: GValue array items do not get slice
+                     * allocated in _pygi_marshal_from_py_array(), so we must
+                     * not try to deallocate it as a slice and thus
+                     * short-circuit cleanup_func. */
+                    if (cleanup_func == pygi_arg_gvalue_from_py_cleanup) {
+                        g_value_unset ((GValue*) item);
+                        continue;
+                    }
+                }
+
+                py_item = PySequence_GetItem (py_arg, i);
+                cleanup_func (state, sequence_cache->item_cache, py_item, item, TRUE);
+                Py_XDECREF (py_item);
+            }
+        }
+
+        /* Only free the array when we didn't transfer ownership */
+        if (array_cache->array_type == GI_ARRAY_TYPE_C) {
+            /* always free the GArray wrapper created in from_py marshaling and
+             * passed back as cleanup_data
+             */
+            g_array_free (array_, arg_cache->transfer == GI_TRANSFER_NOTHING);
+        } else {
+            if (array_ != NULL)
+                g_array_unref (array_);
+            else
+                g_ptr_array_unref (ptr_array_);
+        }
+    }
+}
+
+/*
+ * GArray from Python
+ */
+static PyObject *
+_pygi_marshal_to_py_array (PyGIInvokeState   *state,
+                           PyGICallableCache *callable_cache,
+                           PyGIArgCache      *arg_cache,
+                           GIArgument        *arg,
+                           gpointer          *cleanup_data)
+{
+    GArray *array_;
+    PyObject *py_obj = NULL;
+    PyGISequenceCache *seq_cache = (PyGISequenceCache *)arg_cache;
+    PyGIArgGArray *array_cache = (PyGIArgGArray *)arg_cache;
+    guint processed_items = 0;
+
+     /* GArrays make it easier to iterate over arrays
+      * with different element sizes but requires that
+      * we allocate a GArray if the argument was a C array
+      */
+    if (array_cache->array_type == GI_ARRAY_TYPE_C) {
+        gsize len;
+        if (array_cache->fixed_size >= 0) {
+            g_assert(arg->v_pointer != NULL);
+            len = array_cache->fixed_size;
+        } else if (array_cache->is_zero_terminated) {
+            if (arg->v_pointer == NULL) {
+                len = 0;
+            } else if (seq_cache->item_cache->type_tag == GI_TYPE_TAG_UINT8) {
+                len = strlen (arg->v_pointer);
+            } else {
+                len = g_strv_length ((gchar **)arg->v_pointer);
+            }
+        } else {
+            GIArgument *len_arg = &state->args[array_cache->len_arg_index].arg_value;
+            PyGIArgCache *sub_cache = _pygi_callable_cache_get_arg (callable_cache,
+                                                                    (guint)array_cache->len_arg_index);
+
+            if (!gi_argument_to_gsize (len_arg, &len, sub_cache->type_tag)) {
+                return NULL;
+            }
+        }
+
+        array_ = g_array_new (FALSE,
+                              FALSE,
+                              (guint)array_cache->item_size);
+        if (array_ == NULL) {
+            PyErr_NoMemory ();
+
+            if (arg_cache->transfer == GI_TRANSFER_EVERYTHING && arg->v_pointer != NULL)
+                g_free (arg->v_pointer);
+
+            return NULL;
+        }
+
+        if (array_->data != NULL)
+            g_free (array_->data);
+        array_->data = arg->v_pointer;
+        array_->len = (guint)len;
+    } else {
+        array_ = arg->v_pointer;
+    }
+
+    if (seq_cache->item_cache->type_tag == GI_TYPE_TAG_UINT8) {
+        if (arg->v_pointer == NULL) {
+            py_obj = PYGLIB_PyBytes_FromString ("");
+        } else {
+            py_obj = PYGLIB_PyBytes_FromStringAndSize (array_->data, array_->len);
+        }
+    } else {
+        if (arg->v_pointer == NULL) {
+            py_obj = PyList_New (0);
+        } else {
+            guint i;
+
+            gsize item_size;
+            PyGIMarshalToPyFunc item_to_py_marshaller;
+            PyGIArgCache *item_arg_cache;
+            GPtrArray *item_cleanups;
+
+            py_obj = PyList_New (array_->len);
+            if (py_obj == NULL)
+                goto err;
+
+            item_cleanups = g_ptr_array_sized_new (array_->len);
+            *cleanup_data = item_cleanups;
+
+            item_arg_cache = seq_cache->item_cache;
+            item_to_py_marshaller = item_arg_cache->to_py_marshaller;
+
+            item_size = g_array_get_element_size (array_);
+
+            for (i = 0; i < array_->len; i++) {
+                GIArgument item_arg = {0};
+                PyObject *py_item;
+                gpointer item_cleanup_data = NULL;
+
+                /* If we are receiving an array of pointers, simply assign the pointer
+                 * and move on, letting the per-item marshaler deal with the
+                 * various transfer modes and ref counts (e.g. g_variant_ref_sink).
+                 */
+                if (array_cache->array_type == GI_ARRAY_TYPE_PTR_ARRAY) {
+                    item_arg.v_pointer = g_ptr_array_index ( ( GPtrArray *)array_, i);
+
+                } else if (item_arg_cache->is_pointer) {
+                    item_arg.v_pointer = g_array_index (array_, gpointer, i);
+
+                } else if (item_arg_cache->type_tag == GI_TYPE_TAG_INTERFACE) {
+                    PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *) item_arg_cache;
+
+                    /* FIXME: This probably doesn't work with boxed types or gvalues.
+                     * See fx. _pygi_marshal_from_py_array() */
+                    switch (g_base_info_get_type (iface_cache->interface_info)) {
+                        case GI_INFO_TYPE_STRUCT:
+                            if (arg_cache->transfer == GI_TRANSFER_EVERYTHING &&
+                                       !g_type_is_a (iface_cache->g_type, G_TYPE_BOXED)) {
+                                /* array elements are structs */
+                                gpointer *_struct = g_malloc (item_size);
+                                memcpy (_struct, array_->data + i * item_size,
+                                        item_size);
+                                item_arg.v_pointer = _struct;
+                            } else {
+                                item_arg.v_pointer = array_->data + i * item_size;
+                            }
+                            break;
+                        case GI_INFO_TYPE_ENUM:
+                            memcpy (&item_arg, array_->data + i * item_size, item_size);
+                            break;
+                        default:
+                            item_arg.v_pointer = g_array_index (array_, gpointer, i);
+                            break;
+                    }
+                } else {
+                    memcpy (&item_arg, array_->data + i * item_size, item_size);
+                }
+
+                py_item = item_to_py_marshaller ( state,
+                                                callable_cache,
+                                                item_arg_cache,
+                                                &item_arg,
+                                                &item_cleanup_data);
+
+                g_ptr_array_index (item_cleanups, i) = item_cleanup_data;
+
+                if (py_item == NULL) {
+                    Py_CLEAR (py_obj);
+
+                    if (array_cache->array_type == GI_ARRAY_TYPE_C)
+                        g_array_unref (array_);
+
+                    g_ptr_array_unref (item_cleanups);
+
+                    goto err;
+                }
+                PyList_SET_ITEM (py_obj, i, py_item);
+                processed_items++;
+            }
+        }
+    }
+
+    if (array_cache->array_type == GI_ARRAY_TYPE_C)
+        g_array_free (array_, FALSE);
+
+    return py_obj;
+
+err:
+    if (array_cache->array_type == GI_ARRAY_TYPE_C) {
+        g_array_free (array_, arg_cache->transfer == GI_TRANSFER_EVERYTHING);
+    } else {
+        /* clean up unprocessed items */
+        if (seq_cache->item_cache->to_py_cleanup != NULL) {
+            guint j;
+            PyGIMarshalToPyCleanupFunc cleanup_func = seq_cache->item_cache->to_py_cleanup;
+            for (j = processed_items; j < array_->len; j++) {
+                cleanup_func (state,
+                              seq_cache->item_cache,
+                              NULL,
+                              g_array_index (array_, gpointer, j),
+                              FALSE);
+            }
+        }
+
+        if (arg_cache->transfer == GI_TRANSFER_EVERYTHING)
+            g_array_free (array_, TRUE);
+    }
+
+    return NULL;
+}
+
+static GArray*
+_wrap_c_array (PyGIInvokeState   *state,
+               PyGIArgGArray     *array_cache,
+               gpointer           data)
+{
+    GArray *array_;
+    gsize   len = 0;
+
+    if (array_cache->fixed_size >= 0) {
+        len = array_cache->fixed_size;
+    } else if (array_cache->is_zero_terminated) {
+        len = g_strv_length ((gchar **)data);
+    } else if (array_cache->len_arg_index >= 0) {
+        GIArgument *len_arg = &state->args[array_cache->len_arg_index].arg_value;
+        len = len_arg->v_long;
+    }
+
+    array_ = g_array_new (FALSE,
+                          FALSE,
+                          (guint)array_cache->item_size);
+
+    if (array_ == NULL)
+        return NULL;
+
+    g_free (array_->data);
+    array_->data = data;
+    array_->len = (guint)len;
+
+    return array_;
+}
+
+static void
+_pygi_marshal_cleanup_to_py_array (PyGIInvokeState *state,
+                                   PyGIArgCache    *arg_cache,
+                                   gpointer         cleanup_data,
+                                   gpointer         data,
+                                   gboolean         was_processed)
+{
+    GArray *array_ = NULL;
+    GPtrArray *ptr_array_ = NULL;
+    PyGISequenceCache *sequence_cache = (PyGISequenceCache *)arg_cache;
+    PyGIArgGArray *array_cache = (PyGIArgGArray *)arg_cache;
+    gboolean free_array = FALSE;
+    gboolean free_array_full = TRUE;
+
+    if (arg_cache->transfer == GI_TRANSFER_EVERYTHING ||
+        arg_cache->transfer == GI_TRANSFER_CONTAINER) {
+        free_array = TRUE;
+    }
+
+    /* If this isn't a garray create one to help process variable sized
+       array elements */
+    if (array_cache->array_type == GI_ARRAY_TYPE_C) {
+        array_ = _wrap_c_array (state, array_cache, data);
+
+        if (array_ == NULL)
+            return;
+
+        free_array = TRUE;
+        free_array_full = FALSE;
+    } else if (array_cache->array_type == GI_ARRAY_TYPE_PTR_ARRAY) {
+        ptr_array_ = (GPtrArray *) data;
+    } else {
+        array_ = (GArray *) data;
+    }
+
+    if (sequence_cache->item_cache->to_py_cleanup != NULL) {
+        GPtrArray *item_cleanups = (GPtrArray *) cleanup_data;
+        gsize i;
+        guint len;
+        PyGIMarshalToPyCleanupFunc cleanup_func = sequence_cache->item_cache->to_py_cleanup;
+
+        g_assert (array_ || ptr_array_);
+        len = (array_ != NULL) ? array_->len : ptr_array_->len;
+
+        for (i = 0; i < len; i++) {
+            cleanup_func (state,
+                          sequence_cache->item_cache,
+                          g_ptr_array_index(item_cleanups, i),
+                          (array_ != NULL) ? g_array_index (array_, gpointer, i) : g_ptr_array_index (ptr_array_, i),
+                          was_processed);
+        }
+    }
+
+    if (cleanup_data)
+         g_ptr_array_unref ((GPtrArray *) cleanup_data);
+
+    if (free_array) {
+        if (array_ != NULL)
+            g_array_free (array_, free_array_full);
+        else
+            g_ptr_array_free (ptr_array_, free_array_full);
+    }
+}
+
+static void
+_array_cache_free_func (PyGIArgGArray *cache)
+{
+    if (cache != NULL) {
+        pygi_arg_cache_free (((PyGISequenceCache *)cache)->item_cache);
+        g_slice_free (PyGIArgGArray, cache);
+    }
+}
+
+PyGIArgCache*
+pygi_arg_garray_len_arg_setup (PyGIArgCache *arg_cache,
+                               GITypeInfo *type_info,
+                               PyGICallableCache *callable_cache,
+                               PyGIDirection direction,
+                               gssize arg_index,
+                               gssize *py_arg_index)
+{
+    PyGIArgGArray *seq_cache = (PyGIArgGArray *)arg_cache;
+
+    /* attempt len_arg_index setup for the first time */
+    if (seq_cache->len_arg_index < 0) {
+        seq_cache->len_arg_index = g_type_info_get_array_length (type_info);
+
+        /* offset by self arg for methods and vfuncs */
+        if (seq_cache->len_arg_index >= 0 && callable_cache != NULL) {
+            seq_cache->len_arg_index += callable_cache->args_offset;
+        }
+    }
+
+    if (seq_cache->len_arg_index >= 0) {
+        PyGIArgCache *child_cache = NULL;
+
+        child_cache = _pygi_callable_cache_get_arg (callable_cache,
+                                                    (guint)seq_cache->len_arg_index);
+        if (child_cache == NULL) {
+            child_cache = pygi_arg_cache_alloc ();
+        } else {
+            /* If the "length" arg cache already exists (the length comes before
+             * the array in the argument list), remove it from the to_py_args list
+             * because it does not belong in "to python" return tuple. The length
+             * will implicitly be a part of the returned Python list.
+             */
+            if (direction & PYGI_DIRECTION_TO_PYTHON) {
+                callable_cache->to_py_args =
+                    g_slist_remove (callable_cache->to_py_args, child_cache);
+            }
+
+            /* This is a case where the arg cache already exists and has been
+             * setup by another array argument sharing the same length argument.
+             * See: gi_marshalling_tests_multi_array_key_value_in
+             */
+            if (child_cache->meta_type == PYGI_META_ARG_TYPE_CHILD)
+                return child_cache;
+        }
+
+        /* There is a length argument for this array, so increment the number
+         * of "to python" child arguments when applicable.
+         */
+        if (direction & PYGI_DIRECTION_TO_PYTHON)
+             callable_cache->n_to_py_child_args++;
+
+        child_cache->meta_type = PYGI_META_ARG_TYPE_CHILD;
+        child_cache->direction = direction;
+        child_cache->to_py_marshaller = pygi_marshal_to_py_basic_type_cache_adapter;
+        child_cache->from_py_marshaller = pygi_marshal_from_py_basic_type_cache_adapter;
+        child_cache->py_arg_index = -1;
+
+        /* ugly edge case code:
+         *
+         * When the length comes before the array parameter we need to update
+         * indexes of arguments after the index argument.
+         */
+        if (seq_cache->len_arg_index < arg_index && direction & PYGI_DIRECTION_FROM_PYTHON) {
+            guint i;
+            (*py_arg_index) -= 1;
+            callable_cache->n_py_args -= 1;
+
+            for (i = (guint)seq_cache->len_arg_index + 1;
+                   (gsize)i < _pygi_callable_cache_args_len (callable_cache); i++) {
+                PyGIArgCache *update_cache = _pygi_callable_cache_get_arg (callable_cache, i);
+                if (update_cache == NULL)
+                    break;
+
+                update_cache->py_arg_index -= 1;
+            }
+        }
+
+        _pygi_callable_cache_set_arg (callable_cache, (guint)seq_cache->len_arg_index, child_cache);
+        return child_cache;
+    }
+
+    return NULL;
+}
+
+static gboolean
+pygi_arg_garray_setup (PyGIArgGArray     *sc,
+                       GITypeInfo        *type_info,
+                       GIArgInfo         *arg_info,    /* may be NULL for return arguments */
+                       GITransfer         transfer,
+                       PyGIDirection      direction,
+                       PyGICallableCache *callable_cache)
+{
+    GITypeInfo *item_type_info;
+    PyGIArgCache *arg_cache = (PyGIArgCache *)sc;
+
+    if (!pygi_arg_sequence_setup ((PyGISequenceCache *)sc,
+                                  type_info,
+                                  arg_info,
+                                  transfer,
+                                  direction,
+                                  callable_cache)) {
+        return FALSE;
+    }
+
+    ((PyGIArgCache *)sc)->destroy_notify = (GDestroyNotify)_array_cache_free_func;
+    sc->array_type = g_type_info_get_array_type (type_info);
+    sc->is_zero_terminated = g_type_info_is_zero_terminated (type_info);
+    sc->fixed_size = g_type_info_get_array_fixed_size (type_info);
+    sc->len_arg_index = -1;  /* setup by pygi_arg_garray_len_arg_setup */
+
+    item_type_info = g_type_info_get_param_type (type_info, 0);
+    sc->item_size = _pygi_g_type_info_size (item_type_info);
+    g_base_info_unref ( (GIBaseInfo *)item_type_info);
+
+    if (direction & PYGI_DIRECTION_FROM_PYTHON) {
+        arg_cache->from_py_marshaller = _pygi_marshal_from_py_array;
+        arg_cache->from_py_cleanup = _pygi_marshal_cleanup_from_py_array;
+    }
+
+    if (direction & PYGI_DIRECTION_TO_PYTHON) {
+        arg_cache->to_py_marshaller = _pygi_marshal_to_py_array;
+        arg_cache->to_py_cleanup = _pygi_marshal_cleanup_to_py_array;
+    }
+
+    return TRUE;
+}
+
+PyGIArgCache *
+pygi_arg_garray_new_from_info (GITypeInfo *type_info,
+                               GIArgInfo *arg_info,
+                               GITransfer transfer,
+                               PyGIDirection direction,
+                               PyGICallableCache *callable_cache)
+{
+    PyGIArgGArray *array_cache = g_slice_new0 (PyGIArgGArray);
+    if (array_cache == NULL)
+        return NULL;
+
+    if (!pygi_arg_garray_setup (array_cache,
+                                type_info,
+                                arg_info,
+                                transfer,
+                                direction,
+                                callable_cache)) {
+        pygi_arg_cache_free ( (PyGIArgCache *)array_cache);
+        return NULL;
+    }
+
+    return (PyGIArgCache *)array_cache;
+}
diff --git a/gi/pygi-array.h b/gi/pygi-array.h
new file mode 100644 (file)
index 0000000..718c7cd
--- /dev/null
@@ -0,0 +1,43 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_ARRAY_H__
+#define __PYGI_ARRAY_H__
+
+#include <girepository.h>
+#include "pygi-cache.h"
+
+G_BEGIN_DECLS
+
+PyGIArgCache *pygi_arg_garray_new_from_info  (GITypeInfo        *type_info,
+                                              GIArgInfo         *arg_info,   /* may be null */
+                                              GITransfer         transfer,
+                                              PyGIDirection      direction,
+                                              PyGICallableCache *callable_cache);
+
+PyGIArgCache *pygi_arg_garray_len_arg_setup  (PyGIArgCache      *arg_cache,
+                                              GITypeInfo        *type_info,
+                                              PyGICallableCache *callable_cache,
+                                              PyGIDirection      direction,
+                                              gssize             arg_index,
+                                              gssize            *py_arg_index);
+
+G_END_DECLS
+
+#endif /*__PYGI_ARRAY_H__*/
diff --git a/gi/pygi-basictype.c b/gi/pygi-basictype.c
new file mode 100644 (file)
index 0000000..82b43c5
--- /dev/null
@@ -0,0 +1,1400 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include "pygi-python-compat.h"
+#include "pygi-type.h"
+#include "pygi-basictype.h"
+#include "pygi-argument.h"
+#include "pygi-util.h"
+
+#if defined(G_OS_WIN32)
+#include <float.h>
+static gboolean
+pygi_isfinite (gdouble value) {
+    return _finite (value);
+}
+#else
+#include <math.h>
+static gboolean
+pygi_isfinite (gdouble value) {
+    return isfinite (value);
+}
+#endif
+
+static gboolean
+pygi_gpointer_from_py (PyObject *py_arg, gpointer *result)
+{
+    void* temp;
+
+    if (py_arg == Py_None) {
+        *result = NULL;
+        return TRUE;
+    } else if (PyCapsule_CheckExact (py_arg)) {
+        temp = PyCapsule_GetPointer (py_arg, NULL);
+        if (temp == NULL)
+            return FALSE;
+        *result = temp;
+        return TRUE;
+    } else if (PYGLIB_PyLong_Check(py_arg) || PyLong_Check(py_arg)) {
+        temp = PyLong_AsVoidPtr (py_arg);
+        if (PyErr_Occurred ())
+            return FALSE;
+        *result = temp;
+        return TRUE;
+    } else {
+        PyErr_SetString(PyExc_ValueError,
+                        "Pointer arguments are restricted to integers, capsules, and None. "
+                        "See: https://bugzilla.gnome.org/show_bug.cgi?id=683599");
+        return FALSE;
+    }
+}
+
+static gboolean
+marshal_from_py_void (PyGIInvokeState   *state,
+                      PyGICallableCache *callable_cache,
+                      PyGIArgCache      *arg_cache,
+                      PyObject          *py_arg,
+                      GIArgument        *arg,
+                      gpointer          *cleanup_data)
+{
+    g_warn_if_fail (arg_cache->transfer == GI_TRANSFER_NOTHING);
+
+    if (pygi_gpointer_from_py (py_arg, &(arg->v_pointer))) {
+        *cleanup_data = arg->v_pointer;
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+PyObject *
+pygi_gsize_to_py (gsize value)
+{
+    return PYGLIB_PyLong_FromSize_t (value);
+}
+
+PyObject *
+pygi_gssize_to_py (gssize value)
+{
+    return PYGLIB_PyLong_FromSsize_t (value);
+}
+
+static PyObject *
+base_float_checks (PyObject *object)
+{
+    if (!PyNumber_Check (object)) {
+        PyErr_Format (PyExc_TypeError, "Must be number, not %s",
+                      Py_TYPE (object)->tp_name);
+        return NULL;
+    }
+
+    return PyNumber_Float (object);
+}
+
+gboolean
+pygi_gdouble_from_py (PyObject *py_arg, gdouble *result)
+{
+    PyObject *py_float;
+    gdouble temp;
+
+    py_float = base_float_checks (py_arg);
+    if (py_float == NULL)
+        return FALSE;
+
+    temp = PyFloat_AsDouble (py_float);
+    Py_DECREF (py_float);
+
+    if (PyErr_Occurred ())
+        return FALSE;
+
+    *result = temp;
+
+    return TRUE;
+}
+
+PyObject *
+pygi_gdouble_to_py (gdouble value)
+{
+    return PyFloat_FromDouble (value);
+}
+
+gboolean
+pygi_gfloat_from_py (PyObject *py_arg, gfloat *result)
+{
+    gdouble double_;
+    PyObject *py_float;
+
+    py_float = base_float_checks (py_arg);
+    if (py_float == NULL)
+        return FALSE;
+
+    double_ = PyFloat_AsDouble (py_float);
+    if (PyErr_Occurred ()) {
+        Py_DECREF (py_float);
+        return FALSE;
+    }
+
+    if (pygi_isfinite (double_) && (double_ < -G_MAXFLOAT || double_ > G_MAXFLOAT)) {
+        PyObject *min, *max;
+
+        min = pygi_gfloat_to_py (-G_MAXFLOAT);
+        max = pygi_gfloat_to_py (G_MAXFLOAT);
+        pygi_pyerr_format (
+            PyExc_OverflowError, "%S not in range %S to %S",
+            py_float, min, max);
+        Py_DECREF (min);
+        Py_DECREF (max);
+        Py_DECREF (py_float);
+        return FALSE;
+    }
+
+    Py_DECREF (py_float);
+    *result = (gfloat)double_;
+
+    return TRUE;
+}
+
+PyObject *
+pygi_gfloat_to_py (gfloat value)
+{
+    return PyFloat_FromDouble (value);
+}
+
+gboolean
+pygi_gunichar_from_py (PyObject *py_arg, gunichar *result)
+{
+    Py_ssize_t size;
+    gchar *string_;
+
+    if (py_arg == Py_None) {
+        *result = 0;
+        return FALSE;
+    }
+
+    if (PyUnicode_Check (py_arg)) {
+       PyObject *py_bytes;
+
+       size = PyUnicode_GET_SIZE (py_arg);
+       py_bytes = PyUnicode_AsUTF8String (py_arg);
+       if (!py_bytes)
+           return FALSE;
+
+       string_ = g_strdup(PYGLIB_PyBytes_AsString (py_bytes));
+       Py_DECREF (py_bytes);
+
+#if PY_VERSION_HEX < 0x03000000
+    } else if (PyString_Check (py_arg)) {
+       PyObject *pyuni = PyUnicode_FromEncodedObject (py_arg, "UTF-8", "strict");
+       if (!pyuni)
+           return FALSE;
+
+       size = PyUnicode_GET_SIZE (pyuni);
+       string_ = g_strdup (PyString_AsString(py_arg));
+       Py_DECREF (pyuni);
+#endif
+    } else {
+       PyErr_Format (PyExc_TypeError, "Must be string, not %s",
+                     Py_TYPE (py_arg)->tp_name);
+       return FALSE;
+    }
+
+    if (size != 1) {
+       PyErr_Format (PyExc_TypeError, "Must be a one character string, not %lld characters",
+                     (long long) size);
+       g_free (string_);
+       return FALSE;
+    }
+
+    *result = g_utf8_get_char (string_);
+    g_free (string_);
+
+    return TRUE;
+}
+
+static PyObject *
+pygi_gunichar_to_py (gunichar value)
+{
+    PyObject *py_obj = NULL;
+
+    /* Preserve the bidirectional mapping between 0 and "" */
+    if (value == 0) {
+        py_obj = PYGLIB_PyUnicode_FromString ("");
+    } else if (g_unichar_validate (value)) {
+        gchar utf8[6];
+        gint bytes;
+
+        bytes = g_unichar_to_utf8 (value, utf8);
+        py_obj = PYGLIB_PyUnicode_FromStringAndSize ((char*)utf8, bytes);
+    } else {
+        /* TODO: Convert the error to an exception. */
+        PyErr_Format (PyExc_TypeError,
+                      "Invalid unicode codepoint %" G_GUINT32_FORMAT,
+                      value);
+    }
+
+    return py_obj;
+}
+
+static gboolean
+pygi_gtype_from_py (PyObject *py_arg, GType *type)
+{
+    GType temp = pyg_type_from_object (py_arg);
+
+    if (temp == 0) {
+        PyErr_Format (PyExc_TypeError, "Must be gobject.GType, not %s",
+                      Py_TYPE (py_arg)->tp_name);
+        return FALSE;
+    }
+
+    *type = temp;
+
+    return TRUE;
+}
+
+gboolean
+pygi_utf8_from_py (PyObject *py_arg, gchar **result)
+{
+    gchar *string_;
+
+    if (py_arg == Py_None) {
+        *result = NULL;
+        return TRUE;
+    }
+
+    if (PyUnicode_Check (py_arg)) {
+        PyObject *pystr_obj = PyUnicode_AsUTF8String (py_arg);
+        if (!pystr_obj)
+            return FALSE;
+
+        string_ = g_strdup (PYGLIB_PyBytes_AsString (pystr_obj));
+        Py_DECREF (pystr_obj);
+    }
+#if PY_VERSION_HEX < 0x03000000
+    else if (PyString_Check (py_arg)) {
+        string_ = g_strdup (PyString_AsString (py_arg));
+    }
+#endif
+    else {
+        PyErr_Format (PyExc_TypeError, "Must be string, not %s",
+                      Py_TYPE (py_arg)->tp_name);
+        return FALSE;
+    }
+
+    *result = string_;
+    return TRUE;
+}
+
+G_GNUC_UNUSED
+static gboolean
+filename_from_py_unix (PyObject *py_arg, gchar **result)
+{
+    gchar *filename;
+
+    if (py_arg == Py_None) {
+        *result = NULL;
+        return TRUE;
+    }
+
+    if (PYGLIB_PyBytes_Check (py_arg)) {
+        char *buffer;
+
+        if (PYGLIB_PyBytes_AsStringAndSize (py_arg, &buffer, NULL) == -1)
+            return FALSE;
+
+        filename = g_strdup (buffer);
+    } else if (PyUnicode_Check (py_arg)) {
+        PyObject *bytes;
+        char *buffer;
+
+#if PY_VERSION_HEX < 0x03000000
+        bytes = PyUnicode_AsEncodedString (py_arg, Py_FileSystemDefaultEncoding,
+                                           NULL);
+#else
+        bytes = PyUnicode_EncodeFSDefault (py_arg);
+#endif
+
+        if (!bytes)
+            return FALSE;
+
+        if (PYGLIB_PyBytes_AsStringAndSize (bytes, &buffer, NULL) == -1) {
+            Py_DECREF (bytes);
+            return FALSE;
+        }
+
+        filename = g_strdup (buffer);
+        Py_DECREF (bytes);
+    } else {
+        PyErr_Format (PyExc_TypeError, "Must be bytes, not %s",
+                      Py_TYPE (py_arg)->tp_name);
+        return FALSE;
+    }
+
+    *result = filename;
+    return TRUE;
+}
+
+G_GNUC_UNUSED
+static gboolean
+filename_from_py_win32 (PyObject *py_arg, gchar **result)
+{
+    gchar *filename;
+
+    if (py_arg == Py_None) {
+        *result = NULL;
+        return TRUE;
+    }
+
+#if PY_VERSION_HEX < 0x03000000
+    if (PYGLIB_PyBytes_Check (py_arg)) {
+        char *buffer;
+
+        if (PYGLIB_PyBytes_AsStringAndSize (py_arg, &buffer, NULL) == -1)
+            return FALSE;
+
+        filename = g_strdup (buffer);
+    } else if (PyUnicode_Check (py_arg)) {
+        PyObject *bytes;
+        char *buffer;
+
+        bytes = PyUnicode_AsUTF8String (py_arg);
+        if (!bytes)
+            return FALSE;
+
+        if (PYGLIB_PyBytes_AsStringAndSize (bytes, &buffer, NULL) == -1) {
+            Py_DECREF (bytes);
+            return FALSE;
+        }
+
+        filename = g_strdup (buffer);
+        Py_DECREF (bytes);
+    } else {
+        PyErr_Format (PyExc_TypeError, "Must be unicode, not %s",
+                      Py_TYPE (py_arg)->tp_name);
+        return FALSE;
+    }
+#else
+    if (PYGLIB_PyBytes_Check (py_arg)) {
+        PyObject *uni_arg;
+        gboolean temp_result;
+        char *buffer;
+
+        if (PYGLIB_PyBytes_AsStringAndSize (py_arg, &buffer, NULL) == -1)
+            return FALSE;
+
+        uni_arg = PyUnicode_DecodeFSDefault (buffer);
+        if (!uni_arg)
+            return FALSE;
+        temp_result = filename_from_py_win32 (uni_arg, result);
+        Py_DECREF (uni_arg);
+        return temp_result;
+    } else if (PyUnicode_Check (py_arg)) {
+        PyObject *bytes, *temp_uni;
+        char *buffer;
+
+        /* The roundtrip merges lone surrogates, so we get the same output as
+         * with Py 2. Requires 3.4+ because of https://bugs.python.org/issue27971
+         * Separated lone surrogates can occur when concatenating two paths.
+         */
+        bytes = PyUnicode_AsEncodedString (py_arg, "utf-16-le", "surrogatepass");
+        if (!bytes)
+            return FALSE;
+        temp_uni = PyUnicode_FromEncodedObject (bytes, "utf-16-le", "surrogatepass");
+        Py_DECREF (bytes);
+        if (!temp_uni)
+            return FALSE;
+        /* glib uses utf-8, so encode to that and allow surrogates so we can
+         * represent all possible path values
+         */
+        bytes = PyUnicode_AsEncodedString (temp_uni, "utf-8", "surrogatepass");
+        Py_DECREF (temp_uni);
+        if (!bytes)
+            return FALSE;
+
+        if (PYGLIB_PyBytes_AsStringAndSize (bytes, &buffer, NULL) == -1) {
+            Py_DECREF (bytes);
+            return FALSE;
+        }
+
+        filename = g_strdup (buffer);
+        Py_DECREF (bytes);
+    } else {
+        PyErr_Format (PyExc_TypeError, "Must be str, not %s",
+                      Py_TYPE (py_arg)->tp_name);
+        return FALSE;
+    }
+#endif
+
+    *result = filename;
+    return TRUE;
+}
+
+static gboolean
+pygi_filename_from_py (PyObject *py_arg, gchar **result)
+{
+#ifdef G_OS_WIN32
+    return filename_from_py_win32 (py_arg, result);
+#else
+    return filename_from_py_unix (py_arg, result);
+#endif
+}
+
+static PyObject *
+base_number_checks (PyObject *object)
+{
+    PyObject *number;
+
+    if (!PyNumber_Check (object)) {
+        PyErr_Format (PyExc_TypeError, "Must be number, not %s",
+                      Py_TYPE (object)->tp_name);
+        return NULL;
+    }
+
+#if PY_MAJOR_VERSION < 3
+    {
+        PyObject *tmp = PyNumber_Int (object);
+        if (tmp) {
+            number = PyNumber_Long (tmp);
+            Py_DECREF (tmp);
+        } else {
+            number = PyNumber_Long (object);
+        }
+    }
+#else
+    number = PyNumber_Long (object);
+#endif
+
+    if (number == NULL) {
+        PyErr_SetString (PyExc_TypeError, "expected int argument");
+        return NULL;
+    }
+
+    return number;
+}
+
+gboolean
+pygi_gboolean_from_py (PyObject *object, gboolean *result)
+{
+    int value = PyObject_IsTrue (object);
+    if (value == -1)
+        return FALSE;
+    *result = (gboolean)value;
+    return TRUE;
+}
+
+PyObject *
+pygi_gboolean_to_py (gboolean value)
+{
+    return PyBool_FromLong (value);
+}
+
+/* A super set of pygi_gint8_from_py (also handles unicode) */
+gboolean
+pygi_gschar_from_py (PyObject *object, gint8 *result)
+{
+    if (PyUnicode_Check (object)) {
+        gunichar uni;
+        PyObject *temp;
+        gboolean status;
+
+        if (!pygi_gunichar_from_py (object, &uni))
+            return FALSE;
+
+        temp = pygi_guint32_to_py (uni);
+        status = pygi_gint8_from_py (temp, result);
+        Py_DECREF (temp);
+        return status;
+    } else {
+        /* pygi_gint8_from_py handles numbers and bytes */
+        return pygi_gint8_from_py (object, result);
+    }
+
+    return FALSE;
+}
+
+/* A super set of pygi_guint8_from_py (also handles unicode) */
+gboolean
+pygi_guchar_from_py (PyObject *object, guchar *result)
+{
+    if (PyUnicode_Check (object)) {
+        gunichar uni;
+        PyObject *temp;
+        gboolean status;
+        gint8 codepoint;
+
+        if (!pygi_gunichar_from_py (object, &uni))
+            return FALSE;
+
+        temp = pygi_guint32_to_py (uni);
+        status = pygi_gint8_from_py (temp, &codepoint);
+        Py_DECREF (temp);
+        if (status)
+            *result = (guchar)codepoint;
+        return status;
+    } else {
+        /* pygi_guint8_from_py handles numbers and bytes */
+        return pygi_guint8_from_py (object, result);
+    }
+}
+
+gboolean
+pygi_gint_from_py (PyObject *object, gint *result)
+{
+    long long_value;
+    PyObject *number;
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PYGLIB_PyLong_AsLong (number);
+    if (PyErr_Occurred ()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    } else if (long_value < G_MININT || long_value > G_MAXINT) {
+        goto overflow;
+    }
+
+    Py_DECREF (number);
+    *result = (gint)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %d to %d",
+        number, (int)G_MININT, (int)G_MAXINT);
+    Py_DECREF (number);
+    return FALSE;
+}
+
+PyObject *
+pygi_gint_to_py (gint value)
+{
+    return PYGLIB_PyLong_FromLong (value);
+}
+
+gboolean
+pygi_guint_from_py (PyObject *object, guint *result)
+{
+    unsigned long long_value;
+    PyObject *number;
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PyLong_AsUnsignedLong (number);
+    if (PyErr_Occurred ()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    } else if (long_value > G_MAXUINT) {
+        goto overflow;
+    }
+
+    Py_DECREF (number);
+    *result = (gint)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %ld to %lu",
+        number, (long)0, (unsigned long)G_MAXUINT);
+    Py_DECREF (number);
+    return FALSE;
+}
+
+PyObject *
+pygi_guint_to_py (guint value)
+{
+#if (G_MAXUINT <= LONG_MAX)
+    return PYGLIB_PyLong_FromLong ((long) value);
+#else
+    if (value <= LONG_MAX)
+        return PYGLIB_PyLong_FromLong ((long) value);
+    return PyLong_FromUnsignedLong (value);
+#endif
+}
+
+gboolean
+pygi_glong_from_py (PyObject *object, glong *result)
+{
+    long long_value;
+    PyObject *number;
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PyLong_AsLong (number);
+    if (long_value == -1 && PyErr_Occurred ()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    }
+
+    Py_DECREF (number);
+    *result = (glong)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %ld to %ld",
+        number, (long)G_MINLONG, (long)G_MAXLONG);
+    Py_DECREF (number);
+    return FALSE;
+}
+
+PyObject *
+pygi_glong_to_py (glong value)
+{
+    return PYGLIB_PyLong_FromLong (value);
+}
+
+gboolean
+pygi_gulong_from_py (PyObject *object, gulong *result)
+{
+    unsigned long long_value;
+    PyObject *number;
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PyLong_AsUnsignedLong (number);
+    if (PyErr_Occurred ()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    }
+
+    Py_DECREF (number);
+    *result = (gulong)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %ld to %lu",
+        number, (long)0, (unsigned long)G_MAXULONG);
+    Py_DECREF (number);
+    return FALSE;
+}
+
+PyObject *
+pygi_gulong_to_py (gulong value)
+{
+    if (value <= LONG_MAX)
+        return PYGLIB_PyLong_FromLong ((long) value);
+    else
+        return PyLong_FromUnsignedLong (value);
+}
+
+gboolean
+pygi_gint8_from_py (PyObject *object, gint8 *result)
+{
+    long long_value;
+    PyObject *number;
+
+    if (PYGLIB_PyBytes_Check (object)) {
+        if (PYGLIB_PyBytes_Size (object) != 1) {
+            PyErr_Format (PyExc_TypeError, "Must be a single character");
+            return FALSE;
+        }
+
+        *result = (gint8)(PYGLIB_PyBytes_AsString (object)[0]);
+        return TRUE;
+    }
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PyLong_AsLong (number);
+    if (long_value == -1 && PyErr_Occurred()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    } else if (long_value < G_MININT8 || long_value > G_MAXINT8) {
+        goto overflow;
+    }
+
+    Py_DECREF (number);
+    *result = (gint8)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %ld to %ld",
+        number, (long)G_MININT8, (long)G_MAXINT8);
+    Py_DECREF (number);
+    return FALSE;
+}
+
+PyObject *
+pygi_gint8_to_py (gint8 value)
+{
+    return PYGLIB_PyLong_FromLong (value);
+}
+
+gboolean
+pygi_guint8_from_py (PyObject *object, guint8 *result)
+{
+    long long_value;
+    PyObject *number;
+
+    if (PYGLIB_PyBytes_Check (object)) {
+        if (PYGLIB_PyBytes_Size (object) != 1) {
+            PyErr_Format (PyExc_TypeError, "Must be a single character");
+            return FALSE;
+        }
+
+        *result = (guint8)(PYGLIB_PyBytes_AsString (object)[0]);
+        return TRUE;
+    }
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PyLong_AsLong (number);
+    if (long_value == -1 && PyErr_Occurred()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    } else if (long_value < 0 || long_value > G_MAXUINT8) {
+        goto overflow;
+    }
+
+    Py_DECREF (number);
+    *result = (guint8)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %ld to %ld",
+        number, (long)0, (long)G_MAXUINT8);
+    Py_DECREF (number);
+    return FALSE;
+}
+
+PyObject *
+pygi_guint8_to_py (guint8 value)
+{
+    return PYGLIB_PyLong_FromLong (value);
+}
+
+static gboolean
+pygi_gint16_from_py (PyObject *object, gint16 *result)
+{
+    long long_value;
+    PyObject *number;
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PyLong_AsLong (number);
+    if (long_value == -1 && PyErr_Occurred()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    } else if (long_value < G_MININT16 || long_value > G_MAXINT16) {
+        goto overflow;
+    }
+
+    Py_DECREF (number);
+    *result = (gint16)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %ld to %ld",
+        number, (long)G_MININT16, (long)G_MAXINT16);
+    Py_DECREF (number);
+    return FALSE;
+}
+
+static PyObject *
+pygi_gint16_to_py (gint16 value)
+{
+    return PYGLIB_PyLong_FromLong (value);
+}
+
+static gboolean
+pygi_guint16_from_py (PyObject *object, guint16 *result)
+{
+    long long_value;
+    PyObject *number;
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PyLong_AsLong (number);
+    if (long_value == -1 && PyErr_Occurred()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    } else if (long_value < 0 || long_value > G_MAXUINT16) {
+        goto overflow;
+    }
+
+    Py_DECREF (number);
+    *result = (guint16)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %ld to %ld",
+        number, (long)0, (long)G_MAXUINT16);
+    Py_DECREF (number);
+    return FALSE;
+}
+
+static PyObject *
+pygi_guint16_to_py (guint16 value)
+{
+    return PYGLIB_PyLong_FromLong (value);
+}
+
+static gboolean
+pygi_gint32_from_py (PyObject *object, gint32 *result)
+{
+    long long_value;
+    PyObject *number;
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PyLong_AsLong (number);
+    if (long_value == -1 && PyErr_Occurred()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    } else if (long_value < G_MININT32 || long_value > G_MAXINT32) {
+        goto overflow;
+    }
+
+    Py_DECREF (number);
+    *result = (gint32)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %ld to %ld",
+        number, (long)G_MININT32, (long)G_MAXINT32);
+    Py_DECREF (number);
+    return FALSE;
+}
+
+static PyObject *
+pygi_gint32_to_py (gint32 value)
+{
+    return PYGLIB_PyLong_FromLong (value);
+}
+
+static gboolean
+pygi_guint32_from_py (PyObject *object, guint32 *result)
+{
+    long long long_value;
+    PyObject *number;
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PyLong_AsLongLong (number);
+    if (PyErr_Occurred ()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    } else if (long_value < 0 || long_value > G_MAXUINT32) {
+        goto overflow;
+    }
+
+    Py_DECREF (number);
+    *result = (guint32)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %ld to %lu",
+        number, (long)0, (unsigned long)G_MAXUINT32);
+    Py_DECREF (number);
+    return FALSE;
+}
+
+PyObject *
+pygi_guint32_to_py (guint32 value)
+{
+#if (G_MAXUINT <= LONG_MAX)
+    return PYGLIB_PyLong_FromLong (value);
+#else
+    if (value <= LONG_MAX)
+        return PYGLIB_PyLong_FromLong((long) value);
+    else
+        return PyLong_FromLongLong (value);
+#endif
+}
+
+gboolean
+pygi_gint64_from_py (PyObject *object, gint64 *result)
+{
+    long long long_value;
+    PyObject *number, *min, *max;
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PyLong_AsLongLong (number);
+    if (PyErr_Occurred ()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    } else if (long_value < G_MININT64 || long_value > G_MAXINT64) {
+        goto overflow;
+    }
+
+    Py_DECREF (number);
+    *result = (gint64)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    min = pygi_gint64_to_py (G_MININT64);
+    max = pygi_gint64_to_py (G_MAXINT64);
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %S to %S",
+        number, min, max);
+    Py_DECREF (number);
+    Py_DECREF (min);
+    Py_DECREF (max);
+    return FALSE;
+}
+
+PyObject *
+pygi_gint64_to_py (gint64 value)
+{
+    if (LONG_MIN <= value && value <= LONG_MAX)
+        return PYGLIB_PyLong_FromLong((long) value);
+    else
+        return PyLong_FromLongLong (value);
+}
+
+gboolean
+pygi_guint64_from_py (PyObject *object, guint64 *result)
+{
+    unsigned long long long_value;
+    PyObject *number, *max;
+
+    number = base_number_checks (object);
+    if (number == NULL)
+        return FALSE;
+
+    long_value = PyLong_AsUnsignedLongLong (number);
+    if (PyErr_Occurred ()) {
+        if (PyErr_ExceptionMatches (PyExc_OverflowError))
+            goto overflow;
+        Py_DECREF (number);
+        return FALSE;
+    } else if (long_value > G_MAXUINT64) {
+        goto overflow;
+    }
+
+    Py_DECREF (number);
+    *result = (guint64)long_value;
+    return TRUE;
+
+overflow:
+    PyErr_Clear ();
+    max = pygi_guint64_to_py (G_MAXUINT64);
+    pygi_pyerr_format (
+        PyExc_OverflowError, "%S not in range %ld to %S",
+        number, (long)0, max);
+    Py_DECREF (number);
+    Py_DECREF (max);
+    return FALSE;
+}
+
+PyObject *
+pygi_guint64_to_py (guint64 value)
+{
+    if (value <= LONG_MAX)
+        return PYGLIB_PyLong_FromLong((long) value);
+    else
+        return PyLong_FromUnsignedLongLong (value);
+}
+
+gboolean
+pygi_marshal_from_py_basic_type (PyObject   *object,   /* in */
+                                 GIArgument *arg,      /* out */
+                                 GITypeTag   type_tag,
+                                 GITransfer  transfer,
+                                 gpointer   *cleanup_data /* out */)
+{
+    switch (type_tag) {
+        case GI_TYPE_TAG_VOID:
+            g_warn_if_fail (transfer == GI_TRANSFER_NOTHING);
+            if (pygi_gpointer_from_py (object, &(arg->v_pointer))) {
+                *cleanup_data = arg->v_pointer;
+                return TRUE;
+            }
+            return FALSE;
+
+        case GI_TYPE_TAG_INT8:
+            return pygi_gint8_from_py (object, &(arg->v_int8));
+
+        case GI_TYPE_TAG_UINT8:
+            return pygi_guint8_from_py (object, &(arg->v_uint8));
+
+        case GI_TYPE_TAG_INT16:
+            return pygi_gint16_from_py (object, &(arg->v_int16));
+
+        case GI_TYPE_TAG_UINT16:
+            return pygi_guint16_from_py (object, &(arg->v_uint16));
+
+        case GI_TYPE_TAG_INT32:
+            return pygi_gint32_from_py (object, &(arg->v_int32));
+
+        case GI_TYPE_TAG_UINT32:
+            return pygi_guint32_from_py (object, &(arg->v_uint32));
+
+        case GI_TYPE_TAG_INT64:
+            return pygi_gint64_from_py (object, &(arg->v_int64));
+
+        case GI_TYPE_TAG_UINT64:
+            return pygi_guint64_from_py (object, &(arg->v_uint64));
+
+        case GI_TYPE_TAG_BOOLEAN:
+            return pygi_gboolean_from_py (object, &(arg->v_boolean));
+
+        case GI_TYPE_TAG_FLOAT:
+            return pygi_gfloat_from_py (object, &(arg->v_float));
+
+        case GI_TYPE_TAG_DOUBLE:
+            return pygi_gdouble_from_py (object, &(arg->v_double));
+
+        case GI_TYPE_TAG_GTYPE:
+            return pygi_gtype_from_py (object, &(arg->v_size));
+
+        case GI_TYPE_TAG_UNICHAR:
+            return pygi_gunichar_from_py (object, &(arg->v_uint32));
+
+        case GI_TYPE_TAG_UTF8:
+            if (pygi_utf8_from_py (object, &(arg->v_string))) {
+                *cleanup_data = arg->v_string;
+                return TRUE;
+            }
+            return FALSE;
+
+        case GI_TYPE_TAG_FILENAME:
+            if (pygi_filename_from_py (object, &(arg->v_string))) {
+                *cleanup_data = arg->v_string;
+                return TRUE;
+            }
+            return FALSE;
+
+        default:
+            PyErr_Format (PyExc_TypeError, "Type tag %d not supported",
+                          type_tag);
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+gboolean
+pygi_marshal_from_py_basic_type_cache_adapter (PyGIInvokeState   *state,
+                                               PyGICallableCache *callable_cache,
+                                               PyGIArgCache      *arg_cache,
+                                               PyObject          *py_arg,
+                                               GIArgument        *arg,
+                                               gpointer          *cleanup_data)
+{
+    return pygi_marshal_from_py_basic_type (py_arg,
+                                            arg,
+                                            arg_cache->type_tag,
+                                            arg_cache->transfer,
+                                            cleanup_data);
+}
+
+static void
+marshal_cleanup_from_py_utf8 (PyGIInvokeState *state,
+                              PyGIArgCache    *arg_cache,
+                              PyObject        *py_arg,
+                              gpointer         data,
+                              gboolean         was_processed)
+{
+    /* We strdup strings so free unless ownership is transferred to C. */
+    if (was_processed && arg_cache->transfer == GI_TRANSFER_NOTHING)
+        g_free (data);
+}
+
+static PyObject *
+marshal_to_py_void (PyGIInvokeState   *state,
+                    PyGICallableCache *callable_cache,
+                    PyGIArgCache      *arg_cache,
+                    GIArgument        *arg,
+                    gpointer          *cleanup_data)
+{
+    if (arg_cache->is_pointer) {
+        return PyLong_FromVoidPtr (arg->v_pointer);
+    }
+    Py_RETURN_NONE;
+}
+
+PyObject *
+pygi_utf8_to_py (gchar *value)
+{
+    if (value == NULL) {
+        Py_RETURN_NONE;
+     }
+
+    return PYGLIB_PyUnicode_FromString (value);
+}
+
+PyObject *
+pygi_filename_to_py (gchar *value)
+{
+    PyObject *py_obj;
+
+    if (value == NULL) {
+        Py_RETURN_NONE;
+    }
+
+#if PY_VERSION_HEX < 0x03000000
+    /* On PY2 we return str as is */
+    py_obj = PyString_FromString (value);
+#else
+#ifdef G_OS_WIN32
+    py_obj = PyUnicode_DecodeUTF8 (value, strlen(value),
+                                   "surrogatepass");
+#else
+    py_obj = PyUnicode_DecodeFSDefault (value);
+#endif
+#endif
+
+    return py_obj;
+}
+
+/**
+ * pygi_marshal_to_py_basic_type:
+ * @arg: The argument to convert to an object.
+ * @type_tag: Type tag for @arg
+ * @transfer: Transfer annotation
+ *
+ * Convert the given argument to a Python object. This function
+ * is restricted to simple types that only require the GITypeTag
+ * and GITransfer. For a more complete conversion routine, use:
+ * _pygi_argument_to_object.
+ *
+ * Returns: A PyObject representing @arg or NULL if it cannot convert
+ *          the argument.
+ */
+PyObject *
+pygi_marshal_to_py_basic_type (GIArgument  *arg,
+                               GITypeTag type_tag,
+                               GITransfer transfer)
+{
+    switch (type_tag) {
+        case GI_TYPE_TAG_BOOLEAN:
+            return pygi_gboolean_to_py (arg->v_boolean);
+
+        case GI_TYPE_TAG_INT8:
+            return pygi_gint8_to_py (arg->v_int8);
+
+        case GI_TYPE_TAG_UINT8:
+            return pygi_guint8_to_py (arg->v_uint8);
+
+        case GI_TYPE_TAG_INT16:
+            return pygi_gint16_to_py (arg->v_int16);
+
+        case GI_TYPE_TAG_UINT16:
+            return pygi_guint16_to_py (arg->v_uint16);
+
+        case GI_TYPE_TAG_INT32:
+            return pygi_gint32_to_py (arg->v_int32);
+
+        case GI_TYPE_TAG_UINT32:
+            return pygi_guint32_to_py (arg->v_uint32);
+
+        case GI_TYPE_TAG_INT64:
+            return pygi_gint64_to_py (arg->v_int64);
+
+        case GI_TYPE_TAG_UINT64:
+            return pygi_guint64_to_py (arg->v_uint64);
+
+        case GI_TYPE_TAG_FLOAT:
+            return pygi_gfloat_to_py (arg->v_float);
+
+        case GI_TYPE_TAG_DOUBLE:
+            return pygi_gdouble_to_py (arg->v_double);
+
+        case GI_TYPE_TAG_GTYPE:
+            return pyg_type_wrapper_new ( (GType) arg->v_size);
+
+        case GI_TYPE_TAG_UNICHAR:
+            return pygi_gunichar_to_py (arg->v_uint32);
+
+        case GI_TYPE_TAG_UTF8:
+            return pygi_utf8_to_py (arg->v_string);
+
+        case GI_TYPE_TAG_FILENAME:
+            return pygi_filename_to_py (arg->v_string);
+
+        default:
+            PyErr_Format (PyExc_TypeError, "Type tag %d not supported",
+                          type_tag);
+            return NULL;
+    }
+}
+
+PyObject *
+pygi_marshal_to_py_basic_type_cache_adapter (PyGIInvokeState   *state,
+                                             PyGICallableCache *callable_cache,
+                                             PyGIArgCache      *arg_cache,
+                                             GIArgument        *arg,
+                                             gpointer          *cleanup_data)
+{
+    return pygi_marshal_to_py_basic_type (arg,
+                                          arg_cache->type_tag,
+                                          arg_cache->transfer);
+}
+
+static void
+marshal_cleanup_to_py_utf8 (PyGIInvokeState *state,
+                            PyGIArgCache    *arg_cache,
+                            gpointer         cleanup_data,
+                            gpointer         data,
+                            gboolean         was_processed)
+{
+    /* Python copies the string so we need to free it
+       if the interface is transfering ownership, 
+       whether or not it has been processed yet */
+    if (arg_cache->transfer == GI_TRANSFER_EVERYTHING)
+        g_free (data);
+}
+
+static gboolean
+arg_basic_type_setup_from_info (PyGIArgCache  *arg_cache,
+                                GITypeInfo    *type_info,
+                                GIArgInfo     *arg_info,
+                                GITransfer     transfer,
+                                PyGIDirection  direction)
+{
+    GITypeTag type_tag = g_type_info_get_tag (type_info);
+
+    if (!pygi_arg_base_setup (arg_cache, type_info, arg_info, transfer, direction))
+        return FALSE;
+
+    switch (type_tag) {
+       case GI_TYPE_TAG_VOID:
+           if (direction & PYGI_DIRECTION_FROM_PYTHON)
+                arg_cache->from_py_marshaller = marshal_from_py_void;
+
+           if (direction & PYGI_DIRECTION_TO_PYTHON)
+                arg_cache->to_py_marshaller = marshal_to_py_void;
+
+           break;
+       case GI_TYPE_TAG_BOOLEAN:
+       case GI_TYPE_TAG_INT8:
+       case GI_TYPE_TAG_UINT8:
+       case GI_TYPE_TAG_INT16:
+       case GI_TYPE_TAG_UINT16:
+       case GI_TYPE_TAG_INT32:
+       case GI_TYPE_TAG_UINT32:
+       case GI_TYPE_TAG_INT64:
+       case GI_TYPE_TAG_UINT64:
+       case GI_TYPE_TAG_FLOAT:
+       case GI_TYPE_TAG_DOUBLE:
+       case GI_TYPE_TAG_UNICHAR:
+       case GI_TYPE_TAG_GTYPE:
+           if (direction & PYGI_DIRECTION_FROM_PYTHON)
+               arg_cache->from_py_marshaller = pygi_marshal_from_py_basic_type_cache_adapter;
+
+           if (direction & PYGI_DIRECTION_TO_PYTHON)
+               arg_cache->to_py_marshaller = pygi_marshal_to_py_basic_type_cache_adapter;
+
+           break;
+       case GI_TYPE_TAG_UTF8:
+       case GI_TYPE_TAG_FILENAME:
+           if (direction & PYGI_DIRECTION_FROM_PYTHON) {
+                arg_cache->from_py_marshaller = pygi_marshal_from_py_basic_type_cache_adapter;
+                arg_cache->from_py_cleanup = marshal_cleanup_from_py_utf8;
+           }
+
+           if (direction & PYGI_DIRECTION_TO_PYTHON) {
+                arg_cache->to_py_marshaller = pygi_marshal_to_py_basic_type_cache_adapter;
+                arg_cache->to_py_cleanup = marshal_cleanup_to_py_utf8;
+           }
+
+           break;
+       default:
+           g_assert_not_reached ();
+    }
+
+    return TRUE;
+}
+
+PyGIArgCache *
+pygi_arg_basic_type_new_from_info (GITypeInfo   *type_info,
+                                   GIArgInfo    *arg_info,
+                                   GITransfer    transfer,
+                                   PyGIDirection direction)
+{
+    gboolean res = FALSE;
+    PyGIArgCache *arg_cache = pygi_arg_cache_alloc ();
+
+    res = arg_basic_type_setup_from_info (arg_cache,
+                                          type_info,
+                                          arg_info,
+                                          transfer,
+                                          direction);
+    if (res) {
+        return arg_cache;
+    } else {
+        pygi_arg_cache_free (arg_cache);
+        return NULL;
+    }
+}
diff --git a/gi/pygi-basictype.h b/gi/pygi-basictype.h
new file mode 100644 (file)
index 0000000..04d520a
--- /dev/null
@@ -0,0 +1,89 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_ARG_BASICTYPE_H__
+#define __PYGI_ARG_BASICTYPE_H__
+
+#include <girepository.h>
+#include "pygi-cache.h"
+
+G_BEGIN_DECLS
+
+gboolean pygi_marshal_from_py_basic_type                (PyObject      *object,     /* in */
+                                                         GIArgument    *arg,        /* out */
+                                                         GITypeTag      type_tag,
+                                                         GITransfer     transfer,
+                                                         gpointer      *cleanup_data);
+gboolean pygi_marshal_from_py_basic_type_cache_adapter  (PyGIInvokeState   *state,
+                                                         PyGICallableCache *callable_cache,
+                                                         PyGIArgCache      *arg_cache,
+                                                         PyObject          *py_arg,
+                                                         GIArgument        *arg,
+                                                         gpointer          *cleanup_data);
+
+PyObject *pygi_marshal_to_py_basic_type                (GIArgument    *arg,        /* in */
+                                                        GITypeTag      type_tag,
+                                                        GITransfer     transfer);
+PyObject *pygi_marshal_to_py_basic_type_cache_adapter  (PyGIInvokeState   *state,
+                                                        PyGICallableCache *callable_cache,
+                                                        PyGIArgCache      *arg_cache,
+                                                        GIArgument        *arg,
+                                                        gpointer          *cleanup_data);
+
+PyGIArgCache *pygi_arg_basic_type_new_from_info        (GITypeInfo    *type_info,
+                                                        GIArgInfo     *arg_info,   /* may be null */
+                                                        GITransfer     transfer,
+                                                        PyGIDirection  direction);
+
+PyObject *pygi_gint64_to_py (gint64 value);
+PyObject *pygi_guint64_to_py (guint64 value);
+PyObject *pygi_gfloat_to_py (gfloat value);
+PyObject *pygi_gdouble_to_py (gdouble value);
+PyObject *pygi_gboolean_to_py (gboolean value);
+PyObject *pygi_gint8_to_py (gint8 value);
+PyObject *pygi_guint8_to_py (guint8 value);
+PyObject *pygi_utf8_to_py (gchar *value);
+PyObject *pygi_gint_to_py (gint value);
+PyObject *pygi_glong_to_py (glong value);
+PyObject *pygi_guint_to_py (guint value);
+PyObject *pygi_gulong_to_py (gulong value);
+PyObject *pygi_filename_to_py (gchar *value);
+PyObject *pygi_gsize_to_py (gsize value);
+PyObject *pygi_gssize_to_py (gssize value);
+PyObject *pygi_guint32_to_py (guint32 value);
+
+gboolean pygi_gboolean_from_py (PyObject *object, gboolean *result);
+gboolean pygi_gint64_from_py (PyObject *object, gint64 *result);
+gboolean pygi_guint64_from_py (PyObject *object, guint64 *result);
+gboolean pygi_gfloat_from_py (PyObject *py_arg, gfloat *result);
+gboolean pygi_gdouble_from_py (PyObject *py_arg, gdouble *result);
+gboolean pygi_utf8_from_py (PyObject *py_arg, gchar **result);
+gboolean pygi_glong_from_py (PyObject *object, glong *result);
+gboolean pygi_gulong_from_py (PyObject *object, gulong *result);
+gboolean pygi_gint_from_py (PyObject *object, gint *result);
+gboolean pygi_guint_from_py (PyObject *object, guint *result);
+gboolean pygi_gunichar_from_py (PyObject *py_arg, gunichar *result);
+gboolean pygi_gint8_from_py (PyObject *object, gint8 *result);
+gboolean pygi_gschar_from_py (PyObject *object, gint8 *result);
+gboolean pygi_guint8_from_py (PyObject *object, guint8 *result);
+gboolean pygi_guchar_from_py (PyObject *object, guchar *result);
+
+G_END_DECLS
+
+#endif /*__PYGI_ARG_BASICTYPE_H__*/
diff --git a/gi/pygi-boxed.c b/gi/pygi-boxed.c
new file mode 100644 (file)
index 0000000..6a48159
--- /dev/null
@@ -0,0 +1,262 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2009 Simon van der Linden <svdlinden@src.gnome.org>
+ *
+ *   pygi-boxed.c: wrapper to handle registered structures.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pygi-boxed.h"
+#include "pygi-info.h"
+#include "pygboxed.h"
+#include "pygi-type.h"
+#include "pygi-basictype.h"
+#include "pygi-python-compat.h"
+
+#include <girepository.h>
+
+static void
+boxed_dealloc (PyGIBoxed *self)
+{
+    Py_TYPE (self)->tp_free ((PyObject *)self);
+}
+
+static PyObject *
+boxed_del (PyGIBoxed *self)
+{
+    GType g_type;
+    gpointer boxed = pyg_boxed_get_ptr (self);
+
+    if ( ( (PyGBoxed *) self)->free_on_dealloc && boxed != NULL) {
+        if (self->slice_allocated) {
+            g_slice_free1 (self->size, boxed);
+            self->slice_allocated = FALSE;
+            self->size = 0;
+        } else {
+            g_type = pyg_type_from_object ( (PyObject *) self);
+            g_boxed_free (g_type, boxed);
+        }
+    }
+    pyg_boxed_set_ptr (self, NULL);
+
+    Py_RETURN_NONE;
+}
+
+void *
+pygi_boxed_alloc (GIBaseInfo *info, gsize *size_out)
+{
+    gpointer boxed = NULL;
+    gsize size = 0;
+
+    switch (g_base_info_get_type (info)) {
+        case GI_INFO_TYPE_UNION:
+            size = g_union_info_get_size ( (GIUnionInfo *) info);
+            break;
+        case GI_INFO_TYPE_BOXED:
+        case GI_INFO_TYPE_STRUCT:
+            size = g_struct_info_get_size ( (GIStructInfo *) info);
+            break;
+        default:
+            PyErr_Format (PyExc_TypeError,
+                          "info should be Boxed or Union, not '%d'",
+                          g_base_info_get_type (info));
+            return NULL;
+    }
+
+    if (size == 0) {
+        PyErr_Format (PyExc_TypeError,
+            "boxed cannot be created directly; try using a constructor, see: help(%s.%s)",
+            g_base_info_get_namespace (info),
+            g_base_info_get_name (info));
+        return NULL;
+    }
+
+    if( size_out != NULL)
+        *size_out = size;
+
+    boxed = g_slice_alloc0 (size);
+    if (boxed == NULL)
+        PyErr_NoMemory();
+    return boxed;
+}
+
+static PyObject *
+boxed_new (PyTypeObject *type,
+            PyObject     *args,
+            PyObject     *kwargs)
+{
+    GIBaseInfo *info;
+    gsize size = 0;
+    gpointer boxed;
+    PyGIBoxed *self = NULL;
+
+    info = _pygi_object_get_gi_info ( (PyObject *) type, &PyGIBaseInfo_Type);
+    if (info == NULL) {
+        if (PyErr_ExceptionMatches (PyExc_AttributeError)) {
+            PyErr_Format (PyExc_TypeError, "missing introspection information");
+        }
+        return NULL;
+    }
+
+    boxed = pygi_boxed_alloc (info, &size);
+    if (boxed == NULL) {
+        goto out;
+    }
+
+    self = (PyGIBoxed *) pygi_boxed_new (type, boxed, TRUE, size);
+    if (self == NULL) {
+        g_slice_free1 (size, boxed);
+        goto out;
+    }
+
+    self->size = size;
+    self->slice_allocated = TRUE;
+
+out:
+    g_base_info_unref (info);
+
+    return (PyObject *) self;
+}
+
+static int
+boxed_init (PyObject *self,
+             PyObject *args,
+             PyObject *kwargs)
+{
+    static char *kwlist[] = { NULL };
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs, "", kwlist)) {
+        PyErr_Clear ();
+        PyErr_Warn (PyExc_DeprecationWarning,
+                "Passing arguments to gi.types.Boxed.__init__() is deprecated. "
+                "All arguments passed will be ignored.");
+    }
+
+    /* Don't call PyGBoxed's init, which raises an exception. */
+    return 0;
+}
+
+PYGLIB_DEFINE_TYPE("gi.Boxed", PyGIBoxed_Type, PyGIBoxed);
+
+PyObject *
+pygi_boxed_new (PyTypeObject *type,
+                gpointer      boxed,
+                gboolean      free_on_dealloc,
+                gsize         allocated_slice)
+{
+    PyGIBoxed *self;
+
+    if (!boxed) {
+        Py_RETURN_NONE;
+    }
+
+    if (!PyType_IsSubtype (type, &PyGIBoxed_Type)) {
+        PyErr_SetString (PyExc_TypeError, "must be a subtype of gi.Boxed");
+        return NULL;
+    }
+
+    self = (PyGIBoxed *) type->tp_alloc (type, 0);
+    if (self == NULL) {
+        return NULL;
+    }
+
+    ( (PyGBoxed *) self)->gtype = pyg_type_from_object ( (PyObject *) type);
+    ( (PyGBoxed *) self)->free_on_dealloc = free_on_dealloc;
+    pyg_boxed_set_ptr (self, boxed);
+    if (allocated_slice > 0) {
+        self->size = allocated_slice;
+        self->slice_allocated = TRUE;
+    } else {
+        self->size = 0;
+        self->slice_allocated = FALSE;
+    }
+
+    return (PyObject *) self;
+}
+
+static PyObject *
+boxed_get_free_on_dealloc(PyGIBoxed *self, void *closure)
+{
+  return pygi_gboolean_to_py( ((PyGBoxed *)self)->free_on_dealloc );
+}
+
+static PyObject *
+boxed_get_is_valid (PyGIBoxed *self, void *closure)
+{
+  return pygi_gboolean_to_py (pyg_boxed_get_ptr (self) != NULL);
+}
+
+/**
+ * pygi_boxed_copy_in_place:
+ *
+ * Replace the boxed pointer held by this wrapper with a boxed copy
+ * freeing the previously held pointer (when free_on_dealloc is TRUE).
+ * This can be used in cases where Python is passed a reference which
+ * it does not own and the wrapper is held by the Python program
+ * longer than the duration of a callback it was passed to.
+ */
+void
+pygi_boxed_copy_in_place (PyGIBoxed *self)
+{
+    PyGBoxed *pygboxed = (PyGBoxed *)self;
+    gpointer ptr = pyg_boxed_get_ptr (self);
+    gpointer copy = NULL;
+
+    if (ptr)
+        copy = g_boxed_copy (pygboxed->gtype, ptr);
+
+    boxed_del (self);
+    pyg_boxed_set_ptr (pygboxed, copy);
+    pygboxed->free_on_dealloc = TRUE;
+}
+
+static PyGetSetDef pygi_boxed_getsets[] = {
+    { "_free_on_dealloc", (getter)boxed_get_free_on_dealloc, (setter)0 },
+    { "_is_valid", (getter)boxed_get_is_valid, (setter)0 },
+    { NULL, 0, 0 }
+};
+
+static PyMethodDef boxed_methods[] = {
+    { "__del__", (PyCFunction)boxed_del, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_boxed_register_types (PyObject *m)
+{
+    Py_TYPE(&PyGIBoxed_Type) = &PyType_Type;
+    g_assert (Py_TYPE (&PyGBoxed_Type) != NULL);
+    PyGIBoxed_Type.tp_base = &PyGBoxed_Type;
+    PyGIBoxed_Type.tp_new = (newfunc) boxed_new;
+    PyGIBoxed_Type.tp_init = (initproc) boxed_init;
+    PyGIBoxed_Type.tp_dealloc = (destructor) boxed_dealloc;
+    PyGIBoxed_Type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE);
+    PyGIBoxed_Type.tp_getset = pygi_boxed_getsets;
+    PyGIBoxed_Type.tp_methods = boxed_methods;
+
+    if (PyType_Ready (&PyGIBoxed_Type) < 0)
+        return -1;
+    Py_INCREF ((PyObject *) &PyGIBoxed_Type);
+    if (PyModule_AddObject (m, "Boxed", (PyObject *) &PyGIBoxed_Type) < 0) {
+        Py_DECREF ((PyObject *) &PyGIBoxed_Type);
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/gi/pygi-boxed.h b/gi/pygi-boxed.h
new file mode 100644 (file)
index 0000000..4e5efb9
--- /dev/null
@@ -0,0 +1,50 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2009 Simon van der Linden <svdlinden@src.gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_BOXED_H__
+#define __PYGI_BOXED_H__
+
+#include <Python.h>
+#include <girepository.h>
+#include "pygobject-internal.h"
+
+G_BEGIN_DECLS
+
+typedef struct {
+    PyGBoxed base;
+    gboolean slice_allocated;
+    gsize size;
+} PyGIBoxed;
+
+extern PyTypeObject PyGIBoxed_Type;
+
+PyObject * pygi_boxed_new (PyTypeObject *type,
+                           gpointer      boxed,
+                           gboolean      free_on_dealloc,
+                           gsize         allocated_slice);
+
+void * pygi_boxed_alloc (GIBaseInfo *info, gsize *size);
+
+void pygi_boxed_copy_in_place  (PyGIBoxed *self);
+
+int pygi_boxed_register_types (PyObject *m);
+
+G_END_DECLS
+
+#endif /* __PYGI_BOXED_H__ */
diff --git a/gi/pygi-cache.c b/gi/pygi-cache.c
new file mode 100644 (file)
index 0000000..667ccf6
--- /dev/null
@@ -0,0 +1,1185 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
+ * Copyright (C) 2013 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include <girepository.h>
+
+#include "pygi-python-compat.h"
+#include "pygi-type.h"
+#include "pygi-info.h"
+#include "pygi-cache.h"
+#include "pygi-marshal-cleanup.h"
+#include "pygi-type.h"
+#include "pygi-hashtable.h"
+#include "pygi-basictype.h"
+#include "pygi-list.h"
+#include "pygi-array.h"
+#include "pygi-closure.h"
+#include "pygi-error.h"
+#include "pygi-object.h"
+#include "pygi-struct-marshal.h"
+#include "pygi-enum-marshal.h"
+#include "pygi-resulttuple.h"
+#include "pygi-invoke.h"
+
+
+/* _arg_info_default_value
+ * info:
+ * arg: (out): GIArgument to fill in with default value.
+ *
+ * This is currently a place holder API which only supports "allow-none" pointer args.
+ * Once defaults are part of the GI API, we can replace this with: g_arg_info_default_value
+ * https://bugzilla.gnome.org/show_bug.cgi?id=558620
+ *
+ * Returns: TRUE if the given argument supports a default value and was filled in.
+ */
+static gboolean
+_arg_info_default_value (GIArgInfo *info, GIArgument *arg)
+{
+    if (g_arg_info_may_be_null (info)) {
+        arg->v_pointer = NULL;
+        return TRUE;
+    }
+    return FALSE;
+}
+
+/* pygi_arg_base_setup:
+ * arg_cache: argument cache to initialize
+ * type_info: source for type related attributes to cache
+ * arg_info: (allow-none): source for argument related attributes to cache
+ * transfer: transfer mode to store in the argument cache
+ * direction: marshaling direction to store in the cache
+ *
+ * Initializer for PyGIArgCache
+ *
+ * Returns: TRUE on success and FALSE on failure
+ */
+gboolean
+pygi_arg_base_setup (PyGIArgCache *arg_cache,
+                     GITypeInfo   *type_info,
+                     GIArgInfo    *arg_info,  /* may be NULL for return arguments */
+                     GITransfer    transfer,
+                     PyGIDirection direction)
+{
+    arg_cache->direction = direction;
+    arg_cache->transfer = transfer;
+    arg_cache->py_arg_index = -1;
+    arg_cache->c_arg_index = -1;
+
+    if (type_info != NULL) {
+        arg_cache->is_pointer = g_type_info_is_pointer (type_info);
+        arg_cache->type_tag = g_type_info_get_tag (type_info);
+        g_base_info_ref ( (GIBaseInfo *) type_info);
+        arg_cache->type_info = type_info;
+    }
+
+    if (arg_info != NULL) {
+        if (!arg_cache->has_default) {
+            /* It is possible has_default was set somewhere else */
+            arg_cache->has_default = _arg_info_default_value (arg_info,
+                                                              &arg_cache->default_value);
+        }
+        arg_cache->arg_name = g_base_info_get_name ((GIBaseInfo *) arg_info);
+        arg_cache->allow_none = g_arg_info_may_be_null (arg_info);
+
+        if (arg_cache->type_tag == GI_TYPE_TAG_INTERFACE || arg_cache->type_tag == GI_TYPE_TAG_ARRAY)
+            arg_cache->is_caller_allocates = g_arg_info_is_caller_allocates (arg_info);
+        else
+            arg_cache->is_caller_allocates = FALSE;
+    }
+    return TRUE;
+}
+
+void
+pygi_arg_cache_free (PyGIArgCache *cache)
+{
+    if (cache == NULL)
+        return;
+
+    if (cache->type_info != NULL)
+        g_base_info_unref ( (GIBaseInfo *)cache->type_info);
+    if (cache->destroy_notify)
+        cache->destroy_notify (cache);
+    else
+        g_slice_free (PyGIArgCache, cache);
+}
+
+/* PyGIInterfaceCache */
+
+static void
+_interface_cache_free_func (PyGIInterfaceCache *cache)
+{
+    if (cache != NULL) {
+        Py_XDECREF (cache->py_type);
+        if (cache->type_name != NULL)
+            g_free (cache->type_name);
+        if (cache->interface_info != NULL)
+            g_base_info_unref ( (GIBaseInfo *)cache->interface_info);
+        g_slice_free (PyGIInterfaceCache, cache);
+    }
+}
+
+/* pygi_arg_interface_setup:
+ * arg_cache: argument cache to initialize
+ * type_info: source for type related attributes to cache
+ * arg_info: (allow-none): source for argument related attributes to cache
+ * transfer: transfer mode to store in the argument cache
+ * direction: marshaling direction to store in the cache
+ * iface_info: interface info to cache
+ *
+ * Initializer for PyGIInterfaceCache
+ *
+ * Returns: TRUE on success and FALSE on failure
+ */
+gboolean
+pygi_arg_interface_setup (PyGIInterfaceCache *iface_cache,
+                          GITypeInfo         *type_info,
+                          GIArgInfo          *arg_info,    /* may be NULL for return arguments */
+                          GITransfer          transfer,
+                          PyGIDirection       direction,
+                          GIInterfaceInfo    *iface_info)
+{
+    if (!pygi_arg_base_setup ((PyGIArgCache *)iface_cache,
+                              type_info,
+                              arg_info,
+                              transfer,
+                              direction)) {
+        return FALSE;
+    }
+
+    ( (PyGIArgCache *)iface_cache)->destroy_notify = (GDestroyNotify)_interface_cache_free_func;
+
+    g_base_info_ref ( (GIBaseInfo *)iface_info);
+    iface_cache->interface_info = iface_info;
+    iface_cache->arg_cache.type_tag = GI_TYPE_TAG_INTERFACE;
+    iface_cache->type_name = _pygi_g_base_info_get_fullname (iface_info);
+    iface_cache->g_type = g_registered_type_info_get_g_type ( (GIRegisteredTypeInfo *)iface_info);
+    iface_cache->py_type = pygi_type_import_by_gi_info ( (GIBaseInfo *) iface_info);
+
+    if (iface_cache->py_type == NULL) {
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+PyGIArgCache *
+pygi_arg_interface_new_from_info (GITypeInfo         *type_info,
+                                  GIArgInfo          *arg_info,    /* may be NULL for return arguments */
+                                  GITransfer          transfer,
+                                  PyGIDirection       direction,
+                                  GIInterfaceInfo    *iface_info)
+{
+    PyGIInterfaceCache *ic;
+
+    ic = g_slice_new0 (PyGIInterfaceCache);
+    if (!pygi_arg_interface_setup (ic,
+                                   type_info,
+                                   arg_info,
+                                   transfer,
+                                   direction,
+                                   iface_info)) {
+        pygi_arg_cache_free ((PyGIArgCache *)ic);
+        return NULL;
+    }
+
+    return (PyGIArgCache *)ic;
+}
+
+/* PyGISequenceCache */
+
+static void
+_sequence_cache_free_func (PyGISequenceCache *cache)
+{
+    if (cache != NULL) {
+        pygi_arg_cache_free (cache->item_cache);
+        g_slice_free (PyGISequenceCache, cache);
+    }
+}
+
+/* pygi_arg_sequence_setup:
+ * sc: sequence cache to initialize
+ * type_info: source for type related attributes to cache
+ * arg_info: (allow-none): source for argument related attributes to cache
+ * transfer: transfer mode to store in the argument cache
+ * direction: marshaling direction to store in the cache
+ * iface_info: interface info to cache
+ *
+ * Initializer for PyGISequenceCache used for holding list and array argument
+ * caches.
+ *
+ * Returns: TRUE on success and FALSE on failure
+ */
+gboolean
+pygi_arg_sequence_setup (PyGISequenceCache  *sc,
+                         GITypeInfo         *type_info,
+                         GIArgInfo          *arg_info,    /* may be NULL for return arguments */
+                         GITransfer          transfer,
+                         PyGIDirection       direction,
+                         PyGICallableCache  *callable_cache)
+{
+    GITypeInfo *item_type_info;
+    GITransfer item_transfer;
+
+    if (!pygi_arg_base_setup ((PyGIArgCache *)sc,
+                              type_info,
+                              arg_info,
+                              transfer,
+                              direction)) {
+        return FALSE;
+    }
+
+    sc->arg_cache.destroy_notify = (GDestroyNotify)_sequence_cache_free_func;
+    item_type_info = g_type_info_get_param_type (type_info, 0);
+    item_transfer =
+        transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer;
+
+    sc->item_cache = pygi_arg_cache_new (item_type_info,
+                                         NULL,
+                                         item_transfer,
+                                         direction,
+                                         callable_cache,
+                                         0, 0);
+
+    g_base_info_unref ( (GIBaseInfo *)item_type_info);
+
+    if (sc->item_cache == NULL) {
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+PyGIArgCache *
+pygi_arg_cache_alloc (void)
+{
+    return g_slice_new0 (PyGIArgCache);
+}
+
+static PyGIArgCache *
+_arg_cache_new_for_interface (GIInterfaceInfo   *iface_info,
+                              GITypeInfo        *type_info,
+                              GIArgInfo         *arg_info,
+                              GITransfer         transfer,
+                              PyGIDirection      direction,
+                              PyGICallableCache *callable_cache)
+{
+    GIInfoType info_type;
+
+    info_type = g_base_info_get_type ( (GIBaseInfo *)iface_info);
+
+    switch (info_type) {
+        case GI_INFO_TYPE_CALLBACK:
+            return pygi_arg_callback_new_from_info (type_info,
+                                                    arg_info,
+                                                    transfer,
+                                                    direction,
+                                                    iface_info,
+                                                    callable_cache);
+        case GI_INFO_TYPE_OBJECT:
+        case GI_INFO_TYPE_INTERFACE:
+            return pygi_arg_gobject_new_from_info (type_info,
+                                                   arg_info,
+                                                   transfer,
+                                                   direction,
+                                                   iface_info,
+                                                   callable_cache);
+        case GI_INFO_TYPE_BOXED:
+        case GI_INFO_TYPE_STRUCT:
+        case GI_INFO_TYPE_UNION:
+            return pygi_arg_struct_new_from_info (type_info,
+                                                  arg_info,
+                                                  transfer,
+                                                  direction,
+                                                  iface_info);
+        case GI_INFO_TYPE_ENUM:
+            return pygi_arg_enum_new_from_info (type_info,
+                                                arg_info,
+                                                transfer,
+                                                direction,
+                                                iface_info);
+        case GI_INFO_TYPE_FLAGS:
+            return pygi_arg_flags_new_from_info (type_info,
+                                                 arg_info,
+                                                 transfer,
+                                                 direction,
+                                                 iface_info);
+        default:
+            g_assert_not_reached ();
+    }
+
+    return NULL;
+}
+
+PyGIArgCache *
+pygi_arg_cache_new (GITypeInfo *type_info,
+                    GIArgInfo *arg_info,     /* may be null */
+                    GITransfer transfer,
+                    PyGIDirection direction,
+                    PyGICallableCache *callable_cache,
+                    gssize c_arg_index,
+                    gssize py_arg_index)
+{
+    PyGIArgCache *arg_cache = NULL;
+    GITypeTag type_tag;
+
+    type_tag = g_type_info_get_tag (type_info);
+
+    switch (type_tag) {
+       case GI_TYPE_TAG_VOID:
+       case GI_TYPE_TAG_BOOLEAN:
+       case GI_TYPE_TAG_INT8:
+       case GI_TYPE_TAG_UINT8:
+       case GI_TYPE_TAG_INT16:
+       case GI_TYPE_TAG_UINT16:
+       case GI_TYPE_TAG_INT32:
+       case GI_TYPE_TAG_UINT32:
+       case GI_TYPE_TAG_INT64:
+       case GI_TYPE_TAG_UINT64:
+       case GI_TYPE_TAG_FLOAT:
+       case GI_TYPE_TAG_DOUBLE:
+       case GI_TYPE_TAG_UNICHAR:
+       case GI_TYPE_TAG_GTYPE:
+       case GI_TYPE_TAG_UTF8:
+       case GI_TYPE_TAG_FILENAME:
+           arg_cache = pygi_arg_basic_type_new_from_info (type_info,
+                                                          arg_info,
+                                                          transfer,
+                                                          direction);
+           break;
+
+       case GI_TYPE_TAG_ARRAY:
+           {
+               arg_cache = pygi_arg_garray_new_from_info (type_info,
+                                                          arg_info,
+                                                          transfer,
+                                                          direction,
+                                                          callable_cache);
+               if (arg_cache == NULL)
+                   return NULL;
+
+               pygi_arg_garray_len_arg_setup (arg_cache,
+                                              type_info,
+                                              callable_cache,
+                                              direction,
+                                              c_arg_index,
+                                              &py_arg_index);
+           }
+           break;
+
+       case GI_TYPE_TAG_GLIST:
+           arg_cache = pygi_arg_glist_new_from_info (type_info,
+                                                     arg_info,
+                                                     transfer,
+                                                     direction,
+                                                     callable_cache);
+           break;
+
+       case GI_TYPE_TAG_GSLIST:
+           arg_cache = pygi_arg_gslist_new_from_info (type_info,
+                                                      arg_info,
+                                                      transfer,
+                                                      direction,
+                                                      callable_cache);
+           break;
+
+       case GI_TYPE_TAG_GHASH:
+           arg_cache = pygi_arg_hash_table_new_from_info (type_info,
+                                                          arg_info,
+                                                          transfer,
+                                                          direction,
+                                                          callable_cache);
+           break;
+
+       case GI_TYPE_TAG_INTERFACE:
+           {
+               GIInterfaceInfo *interface_info = g_type_info_get_interface (type_info);
+               arg_cache = _arg_cache_new_for_interface (interface_info,
+                                                         type_info,
+                                                         arg_info,
+                                                         transfer,
+                                                         direction,
+                                                         callable_cache);
+
+               g_base_info_unref ( (GIBaseInfo *)interface_info);
+           }
+           break;
+
+       case GI_TYPE_TAG_ERROR:
+           arg_cache = pygi_arg_gerror_new_from_info (type_info,
+                                                      arg_info,
+                                                      transfer,
+                                                      direction);
+           break;
+       default:
+           break;
+    }
+
+    if (arg_cache != NULL) {
+        arg_cache->py_arg_index = py_arg_index;
+        arg_cache->c_arg_index = c_arg_index;
+    }
+
+    return arg_cache;
+}
+
+/* PyGICallableCache */
+
+static PyGIDirection
+_pygi_get_direction (PyGICallableCache *callable_cache, GIDirection gi_direction)
+{
+    /* For vfuncs and callbacks our marshalling directions are reversed */
+    if (gi_direction == GI_DIRECTION_INOUT) {
+        return PYGI_DIRECTION_BIDIRECTIONAL;
+    } else if (gi_direction == GI_DIRECTION_IN) {
+        if (callable_cache->calling_context != PYGI_CALLING_CONTEXT_IS_FROM_PY)
+            return PYGI_DIRECTION_TO_PYTHON;
+        return PYGI_DIRECTION_FROM_PYTHON;
+    } else {
+        if (callable_cache->calling_context != PYGI_CALLING_CONTEXT_IS_FROM_PY)
+            return PYGI_DIRECTION_FROM_PYTHON;
+        return PYGI_DIRECTION_TO_PYTHON;
+    }
+}
+
+/* Generate the cache for the callable's arguments */
+static gboolean
+_callable_cache_generate_args_cache_real (PyGICallableCache *callable_cache,
+                                          GICallableInfo *callable_info)
+{
+    gint i;
+    guint arg_index;
+    GITypeInfo *return_info;
+    GITransfer return_transfer;
+    PyGIArgCache *return_cache;
+    PyGIDirection return_direction;
+       gssize last_explicit_arg_index;
+    PyObject *tuple_names;
+    GSList *arg_cache_item;
+    PyTypeObject* resulttuple_type;
+
+    /* Return arguments are always considered out */
+    return_direction = _pygi_get_direction (callable_cache, GI_DIRECTION_OUT);
+
+    /* cache the return arg */
+    return_info =
+        g_callable_info_get_return_type (callable_info);
+    return_transfer =
+        g_callable_info_get_caller_owns (callable_info);
+    return_cache =
+        pygi_arg_cache_new (return_info,
+                            NULL,
+                            return_transfer,
+                            return_direction,
+                            callable_cache,
+                            -1,
+                            -1);
+    if (return_cache == NULL)
+        return FALSE;
+
+    return_cache->is_skipped = g_callable_info_skip_return (callable_info);
+    callable_cache->return_cache = return_cache;
+    g_base_info_unref (return_info);
+
+    callable_cache->user_data_index = -1;
+
+    for (i = 0, arg_index = (guint)callable_cache->args_offset;
+         arg_index < _pygi_callable_cache_args_len (callable_cache);
+         i++, arg_index++) {
+        PyGIArgCache *arg_cache = NULL;
+        GIArgInfo *arg_info;
+        PyGIDirection direction;
+
+        arg_info = g_callable_info_get_arg (callable_info, i);
+
+        /* This only happens when dealing with callbacks */
+        if (g_arg_info_get_closure (arg_info) == i) {
+            callable_cache->user_data_index = i;
+
+            arg_cache = pygi_arg_cache_alloc ();
+            _pygi_callable_cache_set_arg (callable_cache, arg_index, arg_cache);
+
+            direction = _pygi_get_direction (callable_cache, GI_DIRECTION_IN);
+            arg_cache->direction = direction;
+            arg_cache->meta_type = PYGI_META_ARG_TYPE_CLOSURE;
+            arg_cache->c_arg_index = i;
+            arg_cache->is_pointer = TRUE;
+
+        } else {
+            GITypeInfo *type_info;
+
+            direction = _pygi_get_direction (callable_cache,
+                                             g_arg_info_get_direction (arg_info));
+            type_info = g_arg_info_get_type (arg_info);
+
+            /* must be an child arg filled in by its owner
+             * and continue
+             * fill in it's c_arg_index, add to the in count
+             */
+            arg_cache = _pygi_callable_cache_get_arg (callable_cache, arg_index);
+            if (arg_cache != NULL) {
+                /* ensure c_arg_index always aligns with callable_cache->args_cache
+                 * and all of the various PyGIInvokeState arrays. */
+                arg_cache->c_arg_index = arg_index;
+
+                if (arg_cache->meta_type == PYGI_META_ARG_TYPE_CHILD_WITH_PYARG) {
+                    arg_cache->py_arg_index = callable_cache->n_py_args;
+                    callable_cache->n_py_args++;
+                }
+
+                if (direction & PYGI_DIRECTION_TO_PYTHON) {
+                    callable_cache->n_to_py_args++;
+                }
+
+                arg_cache->type_tag = g_type_info_get_tag (type_info);
+
+            } else {
+                GITransfer transfer;
+                gssize py_arg_index = -1;
+
+                transfer = g_arg_info_get_ownership_transfer (arg_info);
+
+                if (direction & PYGI_DIRECTION_FROM_PYTHON) {
+                    py_arg_index = callable_cache->n_py_args;
+                    callable_cache->n_py_args++;
+                }
+
+                arg_cache =
+                    pygi_arg_cache_new (type_info,
+                                        arg_info,
+                                        transfer,
+                                        direction,
+                                        callable_cache,
+                                        arg_index,
+                                        py_arg_index);
+
+                if (arg_cache == NULL) {
+                    g_base_info_unref( (GIBaseInfo *)type_info);
+                    g_base_info_unref( (GIBaseInfo *)arg_info);
+                    return FALSE;
+                }
+
+
+                if (direction & PYGI_DIRECTION_TO_PYTHON) {
+                    callable_cache->n_to_py_args++;
+
+                    callable_cache->to_py_args =
+                        g_slist_append (callable_cache->to_py_args, arg_cache);
+                }
+
+                _pygi_callable_cache_set_arg (callable_cache, arg_index, arg_cache);
+            }
+
+            g_base_info_unref (type_info);
+        }
+
+        /* Ensure arguments always have a name when available */
+        arg_cache->arg_name = g_base_info_get_name ((GIBaseInfo *) arg_info);
+
+        g_base_info_unref ( (GIBaseInfo *)arg_info);
+
+    }
+
+    if (callable_cache->arg_name_hash == NULL) {
+        callable_cache->arg_name_hash = g_hash_table_new (g_str_hash, g_str_equal);
+    } else {
+        g_hash_table_remove_all (callable_cache->arg_name_hash);
+    }
+    callable_cache->n_py_required_args = 0;
+    callable_cache->user_data_varargs_index = -1;
+
+    last_explicit_arg_index = -1;
+
+    /* Reverse loop through all the arguments to setup arg_name_list/hash
+     * and find the number of required arguments */
+    for (i=(_pygi_callable_cache_args_len (callable_cache))-1; i >= 0; i--) {
+        PyGIArgCache *arg_cache = _pygi_callable_cache_get_arg (callable_cache, i);
+
+        if (arg_cache->meta_type != PYGI_META_ARG_TYPE_CHILD &&
+                arg_cache->meta_type != PYGI_META_ARG_TYPE_CLOSURE &&
+                arg_cache->direction & PYGI_DIRECTION_FROM_PYTHON) {
+
+            /* Setup arg_name_list and arg_name_hash */
+            gpointer arg_name = (gpointer)arg_cache->arg_name;
+            callable_cache->arg_name_list = g_slist_prepend (callable_cache->arg_name_list,
+                                                             arg_name);
+            if (arg_name != NULL) {
+                g_hash_table_insert (callable_cache->arg_name_hash,
+                                     arg_name,
+                                     GINT_TO_POINTER(i));
+            }
+
+            /* The first tail argument without a default will force all the preceding
+             * argument defaults off. This limits support of default args to the
+             * tail of an args list.
+             */
+            if (callable_cache->n_py_required_args > 0) {
+                arg_cache->has_default = FALSE;
+                callable_cache->n_py_required_args += 1;
+            } else if (!arg_cache->has_default) {
+                callable_cache->n_py_required_args += 1;
+            }
+
+            if (last_explicit_arg_index == -1) {
+                last_explicit_arg_index = i;
+
+                /* If the last "from python" argument in the args list is a child
+                 * with pyarg (currently only callback user_data). Set it to eat
+                 * variable args in the callable cache.
+                 */
+                if (arg_cache->meta_type == PYGI_META_ARG_TYPE_CHILD_WITH_PYARG)
+                    callable_cache->user_data_varargs_index = i;
+            }
+        }
+    }
+
+    if (!return_cache->is_skipped && return_cache->type_tag != GI_TYPE_TAG_VOID) {
+        callable_cache->has_return = TRUE;
+    }
+
+    tuple_names = PyList_New (0);
+    if (callable_cache->has_return) {
+        PyList_Append (tuple_names, Py_None);
+    }
+
+    arg_cache_item = callable_cache->to_py_args;
+    while (arg_cache_item) {
+        const gchar *arg_name = ((PyGIArgCache *)arg_cache_item->data)->arg_name;
+        PyObject *arg_string = PYGLIB_PyUnicode_FromString (arg_name);
+        PyList_Append (tuple_names, arg_string);
+        Py_DECREF (arg_string);
+        arg_cache_item = arg_cache_item->next;
+    }
+
+    /* No need to create a tuple type if there aren't multiple values */
+    if (PyList_Size (tuple_names) > 1) {
+        resulttuple_type = pygi_resulttuple_new_type (tuple_names);
+        if (resulttuple_type == NULL) {
+            Py_DECREF (tuple_names);
+            return FALSE;
+        } else {
+            callable_cache->resulttuple_type = resulttuple_type;
+        }
+    }
+    Py_DECREF (tuple_names);
+
+    return TRUE;
+}
+
+static void
+_callable_cache_deinit_real (PyGICallableCache *cache)
+{
+    g_clear_pointer (&cache->to_py_args, g_slist_free);
+    g_clear_pointer (&cache->arg_name_list, g_slist_free);
+    g_clear_pointer (&cache->arg_name_hash, g_hash_table_unref);
+    g_clear_pointer (&cache->args_cache, g_ptr_array_unref);
+    Py_CLEAR (cache->resulttuple_type);
+
+    g_clear_pointer (&cache->return_cache, pygi_arg_cache_free);
+}
+
+static gboolean
+_callable_cache_init (PyGICallableCache *cache,
+                      GICallableInfo *callable_info)
+{
+    gint n_args;
+    GIBaseInfo *container;
+
+    if (cache->deinit == NULL)
+        cache->deinit = _callable_cache_deinit_real;
+
+    if (cache->generate_args_cache == NULL)
+        cache->generate_args_cache = _callable_cache_generate_args_cache_real;
+
+    cache->name = g_base_info_get_name ((GIBaseInfo *) callable_info);
+    cache->namespace = g_base_info_get_namespace ((GIBaseInfo *) callable_info);
+    container = g_base_info_get_container ((GIBaseInfo *) callable_info);
+    cache->container_name = NULL;
+    /* https://bugzilla.gnome.org/show_bug.cgi?id=709456 */
+    if (container != NULL && g_base_info_get_type (container) != GI_INFO_TYPE_TYPE) {
+        cache->container_name = g_base_info_get_name (container);
+    }
+    cache->throws = g_callable_info_can_throw_gerror ((GIBaseInfo *) callable_info);
+
+    if (g_base_info_is_deprecated (callable_info)) {
+        const gchar *deprecated = g_base_info_get_attribute (callable_info, "deprecated");
+        gchar *warning;
+        gchar *full_name = pygi_callable_cache_get_full_name (cache);
+        if (deprecated != NULL)
+            warning = g_strdup_printf ("%s is deprecated: %s",
+                                       full_name,
+                                       deprecated);
+        else
+            warning = g_strdup_printf ("%s is deprecated",
+                                       full_name);
+        g_free (full_name);
+        PyErr_WarnEx (PyExc_DeprecationWarning, warning, 0);
+        g_free (warning);
+    }
+
+    n_args = (gint)cache->args_offset + g_callable_info_get_n_args (callable_info);
+
+    if (n_args >= 0) {
+        cache->args_cache = g_ptr_array_new_full (n_args, (GDestroyNotify) pygi_arg_cache_free);
+        g_ptr_array_set_size (cache->args_cache, n_args);
+    }
+
+    if (!cache->generate_args_cache (cache, callable_info)) {
+        _callable_cache_deinit_real (cache);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+gchar *
+pygi_callable_cache_get_full_name (PyGICallableCache *cache)
+{
+    if (cache->container_name != NULL) {
+        return g_strjoin (".",
+                          cache->namespace,
+                          cache->container_name,
+                          cache->name,
+                          NULL);
+    } else {
+        return g_strjoin (".",
+                          cache->namespace,
+                          cache->name,
+                          NULL);
+    }
+}
+
+void
+pygi_callable_cache_free (PyGICallableCache *cache)
+{
+    cache->deinit (cache);
+    g_free (cache);
+}
+
+/* PyGIFunctionCache */
+
+static PyObject *
+_function_cache_invoke_real (PyGIFunctionCache *function_cache,
+                             PyGIInvokeState *state,
+                             PyObject *py_args,
+                             PyObject *py_kwargs)
+{
+    return pygi_invoke_c_callable (function_cache, state,
+                                   py_args, py_kwargs);
+}
+
+static void
+_function_cache_deinit_real (PyGICallableCache *callable_cache)
+{
+    g_function_invoker_destroy (&((PyGIFunctionCache *) callable_cache)->invoker);
+
+    _callable_cache_deinit_real (callable_cache);
+}
+
+static gboolean
+_function_cache_init (PyGIFunctionCache *function_cache,
+                      GICallableInfo *callable_info)
+{
+    PyGICallableCache *callable_cache = (PyGICallableCache *) function_cache;
+    GIFunctionInvoker *invoker = &function_cache->invoker;
+    GError *error = NULL;
+
+    callable_cache->calling_context = PYGI_CALLING_CONTEXT_IS_FROM_PY;
+
+    if (callable_cache->deinit == NULL)
+        callable_cache->deinit = _function_cache_deinit_real;
+
+    if (function_cache->invoke == NULL)
+        function_cache->invoke = _function_cache_invoke_real;
+
+    if (!_callable_cache_init (callable_cache, callable_info))
+        return FALSE;
+
+    /* Set by PyGICCallbackCache and PyGIVFuncCache */
+    if (invoker->native_address == NULL) {
+        if (g_function_info_prep_invoker ((GIFunctionInfo *) callable_info,
+                                          invoker,
+                                          &error)) {
+            return TRUE;
+        }
+    } else {
+        if (g_function_invoker_new_for_address (invoker->native_address,
+                                                (GIFunctionInfo *) callable_info,
+                                                invoker,
+                                                &error)) {
+            return TRUE;
+        }
+    }
+
+    if (!pygi_error_check (&error)) {
+        PyErr_Format (PyExc_RuntimeError,
+                      "unknown error creating invoker for %s",
+                      g_base_info_get_name ((GIBaseInfo *) callable_info));
+    }
+
+    _callable_cache_deinit_real (callable_cache);
+    return FALSE;
+}
+
+PyGIFunctionCache *
+pygi_function_cache_new (GICallableInfo *info)
+{
+    PyGIFunctionCache *function_cache;
+
+    function_cache = g_new0 (PyGIFunctionCache, 1);
+
+    if (!_function_cache_init (function_cache, info)) {
+        g_free (function_cache);
+        return NULL;
+    }
+
+    return function_cache;
+}
+
+PyObject *
+pygi_function_cache_invoke (PyGIFunctionCache *function_cache,
+                            PyObject *py_args,
+                            PyObject *py_kwargs)
+{
+    PyGIInvokeState state = { 0, };
+
+    return function_cache->invoke (function_cache, &state,
+                                   py_args, py_kwargs);
+}
+
+/* PyGICCallbackCache */
+
+PyGIFunctionCache *
+pygi_ccallback_cache_new (GICallableInfo *info,
+                          GCallback function_ptr)
+{
+    PyGICCallbackCache *ccallback_cache;
+    PyGIFunctionCache *function_cache;
+
+    ccallback_cache = g_new0 (PyGICCallbackCache, 1);
+    function_cache = (PyGIFunctionCache *) ccallback_cache;
+
+    function_cache->invoker.native_address = function_ptr;
+
+    if (!_function_cache_init (function_cache, info)) {
+        g_free (ccallback_cache);
+        return NULL;
+     }
+
+    return function_cache;
+}
+
+PyObject *
+pygi_ccallback_cache_invoke (PyGICCallbackCache *ccallback_cache,
+                             PyObject *py_args,
+                             PyObject *py_kwargs,
+                             gpointer user_data)
+{
+    PyGIFunctionCache *function_cache = (PyGIFunctionCache *) ccallback_cache;
+    PyGIInvokeState state = { 0, };
+
+    state.user_data = user_data;
+
+    return function_cache->invoke (function_cache, &state,
+                                   py_args, py_kwargs);
+}
+
+/* PyGIConstructorCache */
+
+static PyObject *
+_constructor_cache_invoke_real (PyGIFunctionCache *function_cache,
+                                PyGIInvokeState *state,
+                                PyObject *py_args,
+                                PyObject *py_kwargs)
+{
+    PyGICallableCache *cache = (PyGICallableCache *) function_cache;
+    PyObject *constructor_class;
+    PyObject *ret;
+
+    constructor_class = PyTuple_GetItem (py_args, 0);
+    if (constructor_class == NULL) {
+        gchar *full_name = pygi_callable_cache_get_full_name (cache);
+        PyErr_Clear ();
+        PyErr_Format (PyExc_TypeError,
+                      "Constructors require the class to be passed in as an argument, "
+                      "No arguments passed to the %s constructor.",
+                      full_name);
+        g_free (full_name);
+
+        return FALSE;
+    }
+
+    py_args = PyTuple_GetSlice (py_args, 1, PyTuple_Size (py_args));
+    ret = _function_cache_invoke_real (function_cache, state,
+                                       py_args, py_kwargs);
+    Py_DECREF (py_args);
+
+    if (ret == NULL || cache->return_cache->is_skipped)
+        return ret;
+
+    if (ret != Py_None) {
+        if (!PyTuple_Check (ret))
+            return ret;
+
+        if (PyTuple_GET_ITEM (ret, 0) != Py_None)
+            return ret;
+    }
+
+    PyErr_SetString (PyExc_TypeError, "constructor returned NULL");
+
+    Py_DECREF (ret);
+    return NULL;
+}
+
+PyGIFunctionCache *
+pygi_constructor_cache_new (GICallableInfo *info)
+{
+    PyGIConstructorCache *constructor_cache;
+    PyGIFunctionCache *function_cache;
+
+    constructor_cache = g_new0 (PyGIConstructorCache, 1);
+    function_cache = (PyGIFunctionCache *) constructor_cache;
+
+    function_cache->invoke = _constructor_cache_invoke_real;
+
+    if (!_function_cache_init (function_cache, info)) {
+        g_free (constructor_cache);
+        return NULL;
+    }
+
+    return function_cache;
+}
+
+/* PyGIFunctionWithInstanceCache */
+
+static gboolean
+_function_with_instance_cache_generate_args_cache_real (PyGICallableCache *callable_cache,
+                                                        GICallableInfo *callable_info)
+{
+    GIInterfaceInfo *interface_info;
+    PyGIArgCache *instance_cache;
+    GITransfer transfer;
+
+    interface_info = g_base_info_get_container ((GIBaseInfo *) callable_info);
+    transfer = g_callable_info_get_instance_ownership_transfer (callable_info);
+
+    instance_cache =
+        _arg_cache_new_for_interface (interface_info,
+                                      NULL,
+                                      NULL,
+                                      transfer,
+                                      PYGI_DIRECTION_FROM_PYTHON,
+                                      callable_cache);
+
+    if (instance_cache == NULL)
+        return FALSE;
+
+    /* Because we are not supplied a GITypeInfo for instance arguments,
+     * assume some defaults. */
+    instance_cache->is_pointer = TRUE;
+    instance_cache->py_arg_index = 0;
+    instance_cache->c_arg_index = 0;
+
+    _pygi_callable_cache_set_arg (callable_cache, 0, instance_cache);
+
+    callable_cache->n_py_args++;
+
+    return _callable_cache_generate_args_cache_real (callable_cache,
+                                                     callable_info);
+}
+
+static gboolean
+_function_with_instance_cache_init (PyGIFunctionWithInstanceCache *fwi_cache,
+                                    GICallableInfo *info)
+{
+    PyGICallableCache *callable_cache = (PyGICallableCache *) fwi_cache;
+
+    callable_cache->args_offset += 1;
+    callable_cache->generate_args_cache = _function_with_instance_cache_generate_args_cache_real;
+
+    return _function_cache_init ((PyGIFunctionCache *) fwi_cache, info);
+}
+
+/* PyGIMethodCache */
+
+PyGIFunctionCache *
+pygi_method_cache_new (GICallableInfo *info)
+{
+    PyGIMethodCache *method_cache;
+    PyGIFunctionWithInstanceCache *fwi_cache;
+
+    method_cache = g_new0 (PyGIMethodCache, 1);
+    fwi_cache = (PyGIFunctionWithInstanceCache *) method_cache;
+
+    if (!_function_with_instance_cache_init (fwi_cache, info)) {
+        g_free (method_cache);
+        return NULL;
+    }
+
+    return (PyGIFunctionCache *) method_cache;
+}
+
+/* PyGIVFuncCache */
+
+static PyObject *
+_vfunc_cache_invoke_real (PyGIFunctionCache *function_cache,
+                          PyGIInvokeState *state,
+                          PyObject *py_args,
+                          PyObject *py_kwargs)
+{
+    PyGIVFuncCache *vfunc_cache = (PyGIVFuncCache *) function_cache;
+    PyObject *py_gtype;
+    GType implementor_gtype;
+    GError *error = NULL;
+    PyObject *ret;
+
+    py_gtype = PyTuple_GetItem (py_args, 0);
+    if (py_gtype == NULL) {
+        PyErr_SetString (PyExc_TypeError,
+                         "need the GType of the implementor class");
+        return FALSE;
+    }
+
+    implementor_gtype = pyg_type_from_object (py_gtype);
+    if (implementor_gtype == G_TYPE_INVALID)
+        return FALSE;
+
+    /* vfunc addresses are pulled into the state at call time and cannot be
+     * cached because the call site can specify a different portion of the
+     * class hierarchy. e.g. Object.do_func vs. SubObject.do_func might
+     * retrieve a different vfunc address but GI gives us the same vfunc info.
+     */
+    state->function_ptr = g_vfunc_info_get_address ((GIVFuncInfo *) vfunc_cache->info,
+                                                    implementor_gtype,
+                                                    &error);
+    if (pygi_error_check (&error)) {
+        return FALSE;
+    }
+
+    py_args = PyTuple_GetSlice (py_args, 1, PyTuple_Size (py_args));
+    ret = _function_cache_invoke_real (function_cache, state,
+                                       py_args, py_kwargs);
+    Py_DECREF (py_args);
+
+    return ret;
+}
+
+static void
+_vfunc_cache_deinit_real (PyGICallableCache *callable_cache)
+{
+    g_base_info_unref (((PyGIVFuncCache *) callable_cache)->info);
+
+    _function_cache_deinit_real (callable_cache);
+}
+
+PyGIFunctionCache *
+pygi_vfunc_cache_new (GICallableInfo *info)
+{
+    PyGIVFuncCache *vfunc_cache;
+    PyGIFunctionCache *function_cache;
+    PyGIFunctionWithInstanceCache *fwi_cache;
+
+    vfunc_cache = g_new0 (PyGIVFuncCache, 1);
+    function_cache = (PyGIFunctionCache *) vfunc_cache;
+    fwi_cache = (PyGIFunctionWithInstanceCache *) vfunc_cache;
+
+    ((PyGICallableCache *) vfunc_cache)->deinit = _vfunc_cache_deinit_real;
+
+    /* This must be non-NULL for _function_cache_init() to create the
+     * invoker, the real address will be set in _vfunc_cache_invoke_real().
+     */
+    function_cache->invoker.native_address = (gpointer) 0xdeadbeef;
+
+    function_cache->invoke = _vfunc_cache_invoke_real;
+
+    if (!_function_with_instance_cache_init (fwi_cache, info)) {
+        g_free (vfunc_cache);
+        return NULL;
+    }
+
+    /* Required by _vfunc_cache_invoke_real() */
+    vfunc_cache->info = g_base_info_ref ((GIBaseInfo *) info);
+
+    return function_cache;
+}
+
+/* PyGIClosureCache */
+
+PyGIClosureCache *
+pygi_closure_cache_new (GICallableInfo *info)
+{
+    gssize i;
+    PyGIClosureCache *closure_cache;
+    PyGICallableCache *callable_cache;
+
+    closure_cache = g_new0 (PyGIClosureCache, 1);
+    callable_cache = (PyGICallableCache *) closure_cache;
+
+    callable_cache->calling_context = PYGI_CALLING_CONTEXT_IS_FROM_C;
+
+    if (!_callable_cache_init (callable_cache, info)) {
+        g_free (closure_cache);
+        return NULL;
+    }
+
+    /* For backwards compatibility closures include the array's length.
+     *
+     * See: https://bugzilla.gnome.org/show_bug.cgi?id=652115
+     */
+    for (i = 0; (gsize)i < _pygi_callable_cache_args_len (callable_cache); i++) {
+        PyGIArgCache *arg_cache;
+        PyGIArgGArray *garray_cache;
+        PyGIArgCache *len_arg_cache;
+
+        arg_cache = g_ptr_array_index (callable_cache->args_cache, i);
+        if (arg_cache->type_tag != GI_TYPE_TAG_ARRAY)
+            continue;
+
+        garray_cache = (PyGIArgGArray *) arg_cache;
+        if (garray_cache->len_arg_index == -1)
+            continue;
+
+        len_arg_cache = g_ptr_array_index (callable_cache->args_cache,
+                                           garray_cache->len_arg_index);
+        len_arg_cache->meta_type = PYGI_META_ARG_TYPE_PARENT;
+    }
+
+    /* Prevent guessing multiple user data arguments.
+     * This is required because some versions of GI
+     * do not recognize user_data/data arguments correctly.
+     */
+    if (callable_cache->user_data_index == -1) {
+        for (i = 0; (gsize)i < _pygi_callable_cache_args_len (callable_cache); i++) {
+            PyGIArgCache *arg_cache;
+
+            arg_cache = g_ptr_array_index (callable_cache->args_cache, i);
+
+            if (arg_cache->direction == PYGI_DIRECTION_TO_PYTHON &&
+                arg_cache->type_tag == GI_TYPE_TAG_VOID &&
+                arg_cache->is_pointer) {
+
+                callable_cache->user_data_index = i;
+                break;
+            }
+        }
+    }
+
+    return closure_cache;
+}
diff --git a/gi/pygi-cache.h b/gi/pygi-cache.h
new file mode 100644 (file)
index 0000000..fc5a616
--- /dev/null
@@ -0,0 +1,336 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
+ * Copyright (C) 2013 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_CACHE_H__
+#define __PYGI_CACHE_H__
+
+#include <Python.h>
+#include <girepository.h>
+#include <girffi.h>
+
+#include "pygi-invoke-state-struct.h"
+
+G_BEGIN_DECLS
+
+typedef struct _PyGIArgCache PyGIArgCache;
+typedef struct _PyGICallableCache PyGICallableCache;
+typedef struct _PyGIFunctionCache PyGIFunctionCache;
+typedef struct _PyGIVFuncCache PyGIVFuncCache;
+
+typedef PyGIFunctionCache PyGICCallbackCache;
+typedef PyGIFunctionCache PyGIConstructorCache;
+typedef PyGIFunctionCache PyGIFunctionWithInstanceCache;
+typedef PyGIFunctionCache PyGIMethodCache;
+typedef PyGICallableCache PyGIClosureCache;
+
+typedef gboolean (*PyGIMarshalFromPyFunc) (PyGIInvokeState   *state,
+                                           PyGICallableCache *callable_cache,
+                                           PyGIArgCache      *arg_cache,
+                                           PyObject          *py_arg,
+                                           GIArgument        *arg,
+                                           gpointer          *cleanup_data);
+
+typedef PyObject *(*PyGIMarshalToPyFunc) (PyGIInvokeState   *state,
+                                          PyGICallableCache *callable_cache,
+                                          PyGIArgCache      *arg_cache,
+                                          GIArgument        *arg,
+                                          gpointer          *cleanup_data);
+
+typedef void (*PyGIMarshalCleanupFunc) (PyGIInvokeState *state,
+                                        PyGIArgCache    *arg_cache,
+                                        PyObject        *py_arg,
+                                        gpointer         data,
+                                        gboolean         was_processed);
+
+typedef void (*PyGIMarshalToPyCleanupFunc) (PyGIInvokeState *state,
+                                            PyGIArgCache    *arg_cache,
+                                            gpointer         cleanup_data,
+                                            gpointer         data,
+                                            gboolean         was_processed);
+
+/* Argument meta types denote how we process the argument:
+ *  - PYGI_META_ARG_TYPE_PARENT - parents may or may not have children
+ *    but are always processed via the normal marshaller for their
+ *    actual GI type.  If they have children the marshaller will
+ *    also handle marshalling the children.
+ *  - PYGI_META_ARG_TYPE_CHILD - Children without python argument are
+ *    ignored by the marshallers and handled directly by their parents
+ *    marshaller.
+ *  - Children with pyargs (PYGI_META_ARG_TYPE_CHILD_WITH_PYARG) are processed
+ *    the same as other child args but also have an index into the 
+ *    python parameters passed to the invoker
+ */
+typedef enum {
+    PYGI_META_ARG_TYPE_PARENT,
+    PYGI_META_ARG_TYPE_CHILD,
+    PYGI_META_ARG_TYPE_CHILD_WITH_PYARG,
+    PYGI_META_ARG_TYPE_CLOSURE,
+} PyGIMetaArgType;
+
+/*
+ * Argument direction types denotes how we marshal,
+ * e.g. to Python or from Python or both.
+ */
+typedef enum {
+    PYGI_DIRECTION_TO_PYTHON     = 1 << 0,
+    PYGI_DIRECTION_FROM_PYTHON   = 1 << 1,
+    PYGI_DIRECTION_BIDIRECTIONAL = PYGI_DIRECTION_TO_PYTHON | PYGI_DIRECTION_FROM_PYTHON
+ } PyGIDirection;
+
+/*
+ * In PyGI IN and OUT arguments mean different things depending on the context
+ * of the callable, e.g. is it a callback that is being called from C or a
+ * function that is being called from Python.
+ */
+typedef enum {
+    PYGI_CALLING_CONTEXT_IS_FROM_C,
+    PYGI_CALLING_CONTEXT_IS_FROM_PY
+} PyGICallingContext;
+
+
+struct _PyGIArgCache
+{
+    const gchar *arg_name;
+
+    PyGIMetaArgType meta_type;
+    gboolean is_pointer;
+    gboolean is_caller_allocates;
+    gboolean is_skipped;
+    gboolean allow_none;
+    gboolean has_default;
+
+    PyGIDirection direction;
+    GITransfer transfer;
+    GITypeTag type_tag;
+    GITypeInfo *type_info;
+
+    PyGIMarshalFromPyFunc from_py_marshaller;
+    PyGIMarshalToPyFunc to_py_marshaller;
+
+    PyGIMarshalCleanupFunc from_py_cleanup;
+    PyGIMarshalToPyCleanupFunc to_py_cleanup;
+
+    GDestroyNotify destroy_notify;
+
+    gssize c_arg_index;
+    gssize py_arg_index;
+
+    /* Set when has_default is true. */
+    GIArgument default_value;
+};
+
+typedef struct _PyGISequenceCache
+{
+    PyGIArgCache arg_cache;
+    PyGIArgCache *item_cache;
+} PyGISequenceCache;
+
+typedef struct _PyGIArgGArray
+{
+    PyGISequenceCache seq_cache;
+    gssize fixed_size;
+    gssize len_arg_index;
+    gboolean is_zero_terminated;
+    gsize item_size;
+    GIArrayType array_type;
+} PyGIArgGArray;
+
+typedef struct _PyGIInterfaceCache
+{
+    PyGIArgCache arg_cache;
+    gboolean is_foreign;
+    GType g_type;
+    PyObject *py_type;
+    GIInterfaceInfo *interface_info;
+    gchar *type_name;
+} PyGIInterfaceCache;
+
+struct _PyGICallableCache
+{
+    const gchar *name;
+    const gchar *container_name;
+    const gchar *namespace;
+
+    PyGICallingContext calling_context;
+
+    PyGIArgCache *return_cache;
+    GPtrArray *args_cache;
+    GSList *to_py_args;
+    GSList *arg_name_list; /* for keyword arg matching */
+    GHashTable *arg_name_hash;
+    gboolean throws;
+
+    /* Index of user_data arg passed to a callable. */
+    gssize user_data_index;
+
+    /* Index of user_data arg that can eat variable args passed to a callable. */
+    gssize user_data_varargs_index;
+
+    /* Number of args already added */
+    gssize args_offset;
+
+    /* Number of out args passed to g_function_info_invoke.
+     * This is used for the length of PyGIInvokeState.out_values */
+    gssize n_to_py_args;
+
+    /* If the callable return value gets used */
+    gboolean has_return;
+
+    /* The type used for returning multiple values or NULL */
+    PyTypeObject* resulttuple_type;
+
+    /* Number of out args for g_function_info_invoke that will be skipped
+     * when marshaling to Python due to them being implicitly available
+     * (list/array length).
+     */
+    gssize n_to_py_child_args;
+
+    /* Number of Python arguments expected for invoking the gi function. */
+    gssize n_py_args;
+
+    /* Minimum number of args required to call the callable from Python.
+     * This count does not include args with defaults. */
+    gssize n_py_required_args;
+
+    void     (*deinit)              (PyGICallableCache *callable_cache);
+
+    gboolean (*generate_args_cache) (PyGICallableCache *callable_cache,
+                                     GICallableInfo *callable_info);
+};
+
+struct _PyGIFunctionCache {
+    PyGICallableCache callable_cache;
+
+    /* An invoker with ffi_cif already setup */
+    GIFunctionInvoker invoker;
+
+    PyObject *(*invoke) (PyGIFunctionCache *function_cache,
+                         PyGIInvokeState *state,
+                         PyObject *py_args,
+                         PyObject *py_kwargs);
+} ;
+
+struct _PyGIVFuncCache {
+    PyGIFunctionWithInstanceCache fwi_cache;
+
+    GIBaseInfo *info;
+};
+
+
+gboolean
+pygi_arg_base_setup      (PyGIArgCache *arg_cache,
+                          GITypeInfo   *type_info,
+                          GIArgInfo    *arg_info,  /* may be NULL for return arguments */
+                          GITransfer    transfer,
+                          PyGIDirection direction);
+
+gboolean
+pygi_arg_interface_setup (PyGIInterfaceCache *iface_cache,
+                          GITypeInfo         *type_info,
+                          GIArgInfo          *arg_info,  /* may be NULL for return arguments */
+                          GITransfer          transfer,
+                          PyGIDirection       direction,
+                          GIInterfaceInfo    *iface_info);
+
+gboolean
+pygi_arg_sequence_setup  (PyGISequenceCache  *sc,
+                          GITypeInfo         *type_info,
+                          GIArgInfo          *arg_info,    /* may be NULL for return arguments */
+                          GITransfer          transfer,
+                          PyGIDirection       direction,
+                          PyGICallableCache  *callable_cache);
+
+PyGIArgCache *
+pygi_arg_interface_new_from_info (GITypeInfo         *type_info,
+                                  GIArgInfo          *arg_info,     /* may be NULL for return arguments */
+                                  GITransfer          transfer,
+                                  PyGIDirection       direction,
+                                  GIInterfaceInfo    *iface_info);
+
+PyGIArgCache *
+pygi_arg_cache_alloc     (void);
+
+PyGIArgCache *
+pygi_arg_cache_new       (GITypeInfo *type_info,
+                          GIArgInfo *arg_info,
+                          GITransfer transfer,
+                          PyGIDirection direction,
+                          PyGICallableCache *callable_cache,
+                          /* will be removed */
+                          gssize c_arg_index,
+                          gssize py_arg_index);
+
+void
+pygi_arg_cache_free      (PyGIArgCache *cache);
+
+void
+pygi_callable_cache_free    (PyGICallableCache *cache);
+
+gchar *
+pygi_callable_cache_get_full_name (PyGICallableCache *cache);
+
+PyGIFunctionCache *
+pygi_function_cache_new     (GICallableInfo *info);
+
+PyObject *
+pygi_function_cache_invoke  (PyGIFunctionCache *function_cache,
+                             PyObject *py_args,
+                             PyObject *py_kwargs);
+
+PyGIFunctionCache *
+pygi_ccallback_cache_new    (GICallableInfo *info,
+                             GCallback function_ptr);
+
+PyObject *
+pygi_ccallback_cache_invoke (PyGIFunctionCache *function_cache,
+                             PyObject *py_args,
+                             PyObject *py_kwargs,
+                             gpointer user_data);
+
+PyGIFunctionCache *
+pygi_constructor_cache_new  (GICallableInfo *info);
+
+PyGIFunctionCache *
+pygi_method_cache_new       (GICallableInfo *info);
+
+PyGIFunctionCache *
+pygi_vfunc_cache_new        (GICallableInfo *info);
+
+PyGIClosureCache *
+pygi_closure_cache_new      (GICallableInfo *info);
+
+inline static guint
+_pygi_callable_cache_args_len (PyGICallableCache *cache) {
+    return ((cache)->args_cache)->len;
+}
+
+inline static PyGIArgCache *
+_pygi_callable_cache_get_arg (PyGICallableCache *cache, guint index) {
+    return (PyGIArgCache *) g_ptr_array_index (cache->args_cache, index);
+}
+
+inline static void
+_pygi_callable_cache_set_arg (PyGICallableCache *cache, guint index, PyGIArgCache *arg_cache) {
+    cache->args_cache->pdata[index] = arg_cache;
+}
+
+G_END_DECLS
+
+#endif /* __PYGI_CACHE_H__ */
diff --git a/gi/pygi-ccallback.c b/gi/pygi-ccallback.c
new file mode 100644 (file)
index 0000000..cd003e9
--- /dev/null
@@ -0,0 +1,109 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>, Red Hat, Inc.
+ *
+ *   pygi-boxed-closure.c: wrapper to handle GClosure box types with C callbacks.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pygi-python-compat.h"
+#include "pygi-ccallback.h"
+
+#include <girepository.h>
+
+
+static PyObject *
+_ccallback_call(PyGICCallback *self, PyObject *args, PyObject *kwargs)
+{
+    PyObject *result;
+
+    if (self->cache == NULL) {
+        self->cache = (PyGICCallbackCache *)pygi_ccallback_cache_new (self->info,
+                                                                      self->callback);
+        if (self->cache == NULL)
+            return NULL;
+    }
+
+    result = pygi_ccallback_cache_invoke (self->cache,
+                                          args,
+                                          kwargs,
+                                          self->user_data);
+    return result;
+}
+
+PYGLIB_DEFINE_TYPE("gi.CCallback", PyGICCallback_Type, PyGICCallback);
+
+PyObject *
+_pygi_ccallback_new (GCallback callback,
+                     gpointer user_data,
+                     GIScopeType scope,
+                     GIFunctionInfo *info,
+                     GDestroyNotify destroy_notify)
+{
+    PyGICCallback *self;
+
+    if (!callback) {
+        Py_RETURN_NONE;
+    }
+
+    self = (PyGICCallback *) PyGICCallback_Type.tp_alloc (&PyGICCallback_Type, 0);
+    if (self == NULL) {
+        return NULL;
+    }
+
+    self->callback = (GCallback) callback;
+    self->user_data = user_data;
+    self->scope = scope;
+    self->destroy_notify_func = destroy_notify;
+    self->info = g_base_info_ref( (GIBaseInfo *) info);
+
+    return (PyObject *) self;
+}
+
+static void
+_ccallback_dealloc (PyGICCallback *self)
+{
+    g_base_info_unref ( (GIBaseInfo *)self->info);
+
+    if (self->cache != NULL) {
+        pygi_callable_cache_free ( (PyGICallableCache *)self->cache);
+    }
+
+    Py_TYPE (self)->tp_free ((PyObject *)self);
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_ccallback_register_types (PyObject *m)
+{
+    Py_TYPE(&PyGICCallback_Type) = &PyType_Type;
+    PyGICCallback_Type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE);
+    PyGICCallback_Type.tp_dealloc = (destructor) _ccallback_dealloc;
+    PyGICCallback_Type.tp_call = (ternaryfunc) _ccallback_call;
+
+
+    if (PyType_Ready (&PyGICCallback_Type) < 0)
+        return -1;
+    Py_INCREF ((PyObject *) &PyGICCallback_Type);
+    if (PyModule_AddObject (m, "CCallback", (PyObject *) &PyGICCallback_Type) < 0) {
+        Py_INCREF ((PyObject *) &PyGICCallback_Type);
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/gi/pygi-ccallback.h b/gi/pygi-ccallback.h
new file mode 100644 (file)
index 0000000..7b8439d
--- /dev/null
@@ -0,0 +1,50 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_CCLOSURE_H__
+#define __PYGI_CCLOSURE_H__
+
+#include <Python.h>
+#include "pygi-cache.h"
+
+G_BEGIN_DECLS
+
+typedef struct {
+    PyObject_HEAD
+    GCallback callback;
+    GIFunctionInfo *info;
+    gpointer user_data;
+    GIScopeType scope;
+    GDestroyNotify destroy_notify_func;
+    PyGICCallbackCache *cache;
+} PyGICCallback;
+
+extern PyTypeObject PyGICCallback_Type;
+
+PyObject * _pygi_ccallback_new (GCallback       callback,
+                                gpointer        user_data,
+                                GIScopeType     scope,
+                                GIFunctionInfo *info,
+                                GDestroyNotify  destroy_notify);
+
+int pygi_ccallback_register_types (PyObject *m);
+
+G_END_DECLS
+
+#endif /* __PYGI_CCLOSURE_H__ */
diff --git a/gi/pygi-closure.c b/gi/pygi-closure.c
new file mode 100644 (file)
index 0000000..3299d1b
--- /dev/null
@@ -0,0 +1,957 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ *   pygi-closure.c: PyGI C Closure functions
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pygi-closure.h"
+#include "pygi-error.h"
+#include "pygi-marshal-cleanup.h"
+#include "pygi-invoke.h"
+#include "pygi-ccallback.h"
+#include "pygi-info.h"
+
+extern PyObject *_PyGIDefaultArgPlaceholder;
+
+typedef struct _PyGICallbackCache
+{
+    PyGIArgCache arg_cache;
+    gssize user_data_index;
+    gssize destroy_notify_index;
+    GIScopeType scope;
+    GIInterfaceInfo *interface_info;
+    PyGIClosureCache *closure_cache;
+} PyGICallbackCache;
+
+/* This maintains a list of closures which can be free'd whenever
+   as they have been called.  We will free them on the next
+   library function call.
+ */
+static GSList* async_free_list;
+
+static void
+_pygi_closure_assign_pyobj_to_retval (gpointer retval,
+                                      GIArgument *arg,
+                                      PyGIArgCache *arg_cache)
+{
+    if (retval == NULL)
+        return;
+
+    switch (arg_cache->type_tag) {
+        case GI_TYPE_TAG_BOOLEAN:
+           *((ffi_sarg *) retval) = arg->v_boolean;
+           break;
+        case GI_TYPE_TAG_INT8:
+           *((ffi_sarg *) retval) = arg->v_int8;
+           break;
+        case GI_TYPE_TAG_UINT8:
+           *((ffi_arg *) retval) = arg->v_uint8;
+           break;
+        case GI_TYPE_TAG_INT16:
+           *((ffi_sarg *) retval) = arg->v_int16;
+           break;
+        case GI_TYPE_TAG_UINT16:
+           *((ffi_arg *) retval) = arg->v_uint16;
+           break;
+        case GI_TYPE_TAG_INT32:
+           *((ffi_sarg *) retval) = arg->v_int32;
+           break;
+        case GI_TYPE_TAG_UINT32:
+           *((ffi_arg *) retval) = arg->v_uint32;
+           break;
+        case GI_TYPE_TAG_INT64:
+           *((ffi_sarg *) retval) = arg->v_int64;
+           break;
+        case GI_TYPE_TAG_UINT64:
+           *((ffi_arg *) retval) = arg->v_uint64;
+           break;
+        case GI_TYPE_TAG_FLOAT:
+           *((gfloat *) retval) = arg->v_float;
+           break;
+        case GI_TYPE_TAG_DOUBLE:
+           *((gdouble *) retval) = arg->v_double;
+           break;
+        case GI_TYPE_TAG_GTYPE:
+           *((ffi_arg *) retval) = arg->v_size;
+           break;
+        case GI_TYPE_TAG_UNICHAR:
+            *((ffi_arg *) retval) = arg->v_uint32;
+            break;
+        case GI_TYPE_TAG_INTERFACE:
+            {
+                GIBaseInfo *interface_info;
+
+                interface_info = ((PyGIInterfaceCache *) arg_cache)->interface_info;
+
+                switch (g_base_info_get_type (interface_info)) {
+                case GI_INFO_TYPE_ENUM:
+                    *(ffi_sarg *) retval = arg->v_int;
+                    break;
+                case GI_INFO_TYPE_FLAGS:
+                    *(ffi_arg *) retval = arg->v_uint;
+                    break;
+                default:
+                    *(ffi_arg *) retval = arg->v_pointer;
+                    break;
+                }
+
+                break;
+            }
+        default:
+            *(ffi_arg *) retval = arg->v_pointer;
+            break;
+      }
+}
+
+static void
+_pygi_closure_assign_pyobj_to_out_argument (gpointer out_arg,
+                                            GIArgument *arg,
+                                            PyGIArgCache *arg_cache)
+{
+    if (out_arg == NULL)
+        return;
+
+    switch (arg_cache->type_tag) {
+        case GI_TYPE_TAG_BOOLEAN:
+           *((gboolean *) out_arg) = arg->v_boolean;
+           break;
+        case GI_TYPE_TAG_INT8:
+           *((gint8 *) out_arg) = arg->v_int8;
+           break;
+        case GI_TYPE_TAG_UINT8:
+           *((guint8 *) out_arg) = arg->v_uint8;
+           break;
+        case GI_TYPE_TAG_INT16:
+           *((gint16 *) out_arg) = arg->v_int16;
+           break;
+        case GI_TYPE_TAG_UINT16:
+           *((guint16 *) out_arg) = arg->v_uint16;
+           break;
+        case GI_TYPE_TAG_INT32:
+           *((gint32 *) out_arg) = arg->v_int32;
+           break;
+        case GI_TYPE_TAG_UINT32:
+           *((guint32 *) out_arg) = arg->v_uint32;
+           break;
+        case GI_TYPE_TAG_INT64:
+           *((gint64 *) out_arg) = arg->v_int64;
+           break;
+        case GI_TYPE_TAG_UINT64:
+           *((guint64 *) out_arg) = arg->v_uint64;
+           break;
+        case GI_TYPE_TAG_FLOAT:
+           *((gfloat *) out_arg) = arg->v_float;
+           break;
+        case GI_TYPE_TAG_DOUBLE:
+           *((gdouble *) out_arg) = arg->v_double;
+           break;
+        case GI_TYPE_TAG_GTYPE:
+           *((GType *) out_arg) = arg->v_size;
+           break;
+        case GI_TYPE_TAG_UNICHAR:
+            *((guint32 *) out_arg) = arg->v_uint32;
+            break;
+        case GI_TYPE_TAG_INTERFACE:
+        {
+           GIBaseInfo *interface_info;
+
+           interface_info = ((PyGIInterfaceCache *) arg_cache)->interface_info;
+
+           switch (g_base_info_get_type (interface_info)) {
+           case GI_INFO_TYPE_ENUM:
+               *(gint *) out_arg = arg->v_int;
+               break;
+           case GI_INFO_TYPE_FLAGS:
+               *(guint *) out_arg = arg->v_uint;
+               break;
+           case GI_INFO_TYPE_STRUCT:
+               if (!arg_cache->is_pointer) {
+                   if (arg->v_pointer != NULL) {
+                       gsize item_size = _pygi_g_type_info_size (arg_cache->type_info);
+                       memcpy (out_arg, arg->v_pointer, item_size);
+                   }
+                   break;
+               }
+               *((gpointer *) out_arg) = arg->v_pointer;
+               break;
+           default:
+               *((gpointer *) out_arg) = arg->v_pointer;
+               break;
+           }
+           break;
+        }
+
+        default:
+           *((gpointer *) out_arg) = arg->v_pointer;
+           break;
+      }
+}
+
+static void
+_pygi_closure_convert_ffi_arguments (PyGIInvokeArgState *state,
+                                     PyGICallableCache *cache,
+                                     void **args)
+{
+    guint i;
+
+    for (i = 0; i < _pygi_callable_cache_args_len (cache); i++) {
+        PyGIArgCache *arg_cache = g_ptr_array_index (cache->args_cache, i);
+        gpointer arg_pointer;
+
+        if (arg_cache->direction & PYGI_DIRECTION_FROM_PYTHON) {
+            state[i].arg_value.v_pointer = * (gpointer *) args[i];
+
+            if (state[i].arg_value.v_pointer == NULL)
+                continue;
+
+            state[i].arg_pointer.v_pointer = state[i].arg_value.v_pointer;
+            arg_pointer = state[i].arg_value.v_pointer;
+        } else {
+            arg_pointer = args[i];
+        }
+
+        switch (arg_cache->type_tag) {
+            case GI_TYPE_TAG_BOOLEAN:
+                state[i].arg_value.v_boolean = * (gboolean *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_INT8:
+                state[i].arg_value.v_int8 = * (gint8 *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_UINT8:
+                state[i].arg_value.v_uint8 = * (guint8 *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_INT16:
+                state[i].arg_value.v_int16 = * (gint16 *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_UINT16:
+                state[i].arg_value.v_uint16 = * (guint16 *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_INT32:
+                state[i].arg_value.v_int32 = * (gint32 *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_UINT32:
+                state[i].arg_value.v_uint32 = * (guint32 *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_INT64:
+                state[i].arg_value.v_int64 = * (gint64 *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_UINT64:
+                state[i].arg_value.v_uint64 = * (guint64 *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_FLOAT:
+                state[i].arg_value.v_float = * (gfloat *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_DOUBLE:
+                state[i].arg_value.v_double = * (gdouble *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_UTF8:
+                state[i].arg_value.v_string = * (gchar **) arg_pointer;
+                break;
+            case GI_TYPE_TAG_INTERFACE:
+            {
+                GIBaseInfo *interface;
+                GIInfoType interface_type;
+
+                interface = ((PyGIInterfaceCache *) arg_cache)->interface_info;
+                interface_type = g_base_info_get_type (interface);
+
+                if (interface_type == GI_INFO_TYPE_ENUM) {
+                    state[i].arg_value.v_int = * (gint *) arg_pointer;
+                } else if (interface_type == GI_INFO_TYPE_FLAGS) {
+                    state[i].arg_value.v_uint = * (guint *) arg_pointer;
+                } else {
+                    state[i].arg_value.v_pointer = * (gpointer *) arg_pointer;
+                }
+                break;
+            }
+            case GI_TYPE_TAG_UNICHAR:
+                state[i].arg_value.v_uint32 = * (guint32 *) arg_pointer;
+                break;
+            case GI_TYPE_TAG_ERROR:
+            case GI_TYPE_TAG_GHASH:
+            case GI_TYPE_TAG_GLIST:
+            case GI_TYPE_TAG_GSLIST:
+            case GI_TYPE_TAG_ARRAY:
+            case GI_TYPE_TAG_VOID:
+                state[i].arg_value.v_pointer = * (gpointer *) arg_pointer;
+                break;
+            default:
+                g_warning ("Unhandled type tag %s",
+                           g_type_tag_to_string (arg_cache->type_tag));
+                state[i].arg_value.v_pointer = 0;
+        }
+    }
+
+    if (cache->throws) {
+        gssize error_index = _pygi_callable_cache_args_len (cache);
+
+        state[error_index].arg_value.v_pointer = * (gpointer *) args[error_index];
+    }
+}
+
+static gboolean
+_invoke_state_init_from_cache (PyGIInvokeState *state,
+                               PyGIClosureCache *closure_cache,
+                               void **args)
+{
+    PyGICallableCache *cache = (PyGICallableCache *) closure_cache;
+
+    state->n_args = _pygi_callable_cache_args_len (cache);
+    state->n_py_in_args = state->n_args;
+
+    /* Increment after setting the number of Python input args */
+    if (cache->throws) {
+        state->n_args++;
+    }
+
+    state->py_in_args = PyTuple_New (state->n_py_in_args);
+    if (state->py_in_args == NULL) {
+        PyErr_NoMemory ();
+        return FALSE;
+    }
+
+    state->args = NULL;
+    state->error = NULL;
+
+    if (!_pygi_invoke_arg_state_init (state)) {
+        return FALSE;
+    }
+
+    state->ffi_args = NULL;
+
+    _pygi_closure_convert_ffi_arguments (state->args, cache, args);
+    return TRUE;
+}
+
+static void
+_invoke_state_clear (PyGIInvokeState *state)
+{
+    _pygi_invoke_arg_state_free (state);
+    Py_XDECREF (state->py_in_args);
+}
+
+static gboolean
+_pygi_closure_convert_arguments (PyGIInvokeState *state,
+                                 PyGIClosureCache *closure_cache)
+{
+    PyGICallableCache *cache = (PyGICallableCache *) closure_cache;
+    gssize n_in_args = 0;
+    gssize i;
+
+    for (i = 0; (gsize)i < _pygi_callable_cache_args_len (cache); i++) {
+        PyGIArgCache *arg_cache;
+
+        arg_cache = g_ptr_array_index (cache->args_cache, i);
+
+        if (arg_cache->direction & PYGI_DIRECTION_TO_PYTHON) {
+            PyObject *value;
+
+            if (cache->user_data_index == i) {
+                if (state->user_data == NULL) {
+                    /* user_data can be NULL for connect functions which don't accept
+                     * user_data or as the default for user_data in the middle of function
+                     * arguments.
+                     */
+                    Py_INCREF (Py_None);
+                    value = Py_None;
+                } else {
+                    /* Extend the callbacks args with user_data as variable args. */
+                    gssize j, user_data_len;
+                    PyObject *py_user_data = state->user_data;
+
+                    if (!PyTuple_Check (py_user_data)) {
+                        PyErr_SetString (PyExc_TypeError, "expected tuple for callback user_data");
+                        return FALSE;
+                    }
+
+                    user_data_len = PyTuple_Size (py_user_data);
+                    _PyTuple_Resize (&state->py_in_args,
+                                     state->n_py_in_args + user_data_len - 1);
+
+                    for (j = 0; j < user_data_len; j++, n_in_args++) {
+                        value = PyTuple_GetItem (py_user_data, j);
+                        Py_INCREF (value);
+                        PyTuple_SET_ITEM (state->py_in_args, n_in_args, value);
+                    }
+                    /* We can assume user_data args are never going to be inout,
+                     * so just continue here.
+                     */
+                    continue;
+                }
+            } else if (arg_cache->meta_type != PYGI_META_ARG_TYPE_PARENT) {
+                continue;
+            } else {
+                gpointer cleanup_data = NULL;
+
+                value = arg_cache->to_py_marshaller (state,
+                                                     cache,
+                                                     arg_cache,
+                                                     &state->args[i].arg_value,
+                                                     &cleanup_data);
+                state->args[i].to_py_arg_cleanup_data = cleanup_data;
+
+                if (value == NULL) {
+                    pygi_marshal_cleanup_args_to_py_parameter_fail (state,
+                                                                    cache,
+                                                                    i);
+                    return FALSE;
+                }
+            }
+
+            PyTuple_SET_ITEM (state->py_in_args, n_in_args, value);
+            n_in_args++;
+        }
+    }
+
+    if (_PyTuple_Resize (&state->py_in_args, n_in_args) == -1)
+        return FALSE;
+
+    return TRUE;
+}
+
+static gboolean
+_pygi_closure_set_out_arguments (PyGIInvokeState *state,
+                                 PyGICallableCache *cache,
+                                 PyObject *py_retval,
+                                 void *resp)
+{
+    gssize i;
+    gssize i_py_retval = 0;
+    gboolean success;
+
+    if (cache->return_cache->type_tag != GI_TYPE_TAG_VOID) {
+        PyObject *item = py_retval;
+
+        if (PyTuple_Check (py_retval)) {
+            item = PyTuple_GET_ITEM (py_retval, 0);
+        }
+
+        success = cache->return_cache->from_py_marshaller (state,
+                                                           cache,
+                                                           cache->return_cache,
+                                                           item,
+                                                           &state->return_arg,
+                                                           &state->args[0].arg_cleanup_data);
+
+        if (!success) {
+            pygi_marshal_cleanup_args_return_fail (state,
+                                                   cache);
+            return FALSE;
+        }
+
+        _pygi_closure_assign_pyobj_to_retval (resp, &state->return_arg,
+                                              cache->return_cache);
+        i_py_retval++;
+    }
+
+    for (i = 0; (gsize)i < _pygi_callable_cache_args_len (cache); i++) {
+        PyGIArgCache *arg_cache = g_ptr_array_index (cache->args_cache, i);
+
+        if (arg_cache->direction & PYGI_DIRECTION_FROM_PYTHON) {
+            PyObject *item = py_retval;
+
+            if (arg_cache->type_tag == GI_TYPE_TAG_ERROR) {
+                * (GError **) state->args[i].arg_pointer.v_pointer = NULL;
+                continue;
+            }
+
+            if (PyTuple_Check (py_retval)) {
+                item = PyTuple_GET_ITEM (py_retval, i_py_retval);
+            } else if (i_py_retval != 0) {
+                pygi_marshal_cleanup_args_to_py_parameter_fail (state,
+                                                                cache,
+                                                                i_py_retval);
+                return FALSE;
+            }
+
+            success = arg_cache->from_py_marshaller (state,
+                                                     cache,
+                                                     arg_cache,
+                                                     item,
+                                                     &state->args[i].arg_value,
+                                                     &state->args[i_py_retval].arg_cleanup_data);
+
+            if (!success) {
+                pygi_marshal_cleanup_args_to_py_parameter_fail (state,
+                                                                cache,
+                                                                i_py_retval);
+                return FALSE;
+            }
+
+            _pygi_closure_assign_pyobj_to_out_argument (state->args[i].arg_pointer.v_pointer,
+                                                        &state->args[i].arg_value, arg_cache);
+
+            i_py_retval++;
+        }
+    }
+
+    return TRUE;
+}
+
+static void
+_pygi_closure_clear_retvals (PyGIInvokeState *state,
+                             PyGICallableCache *cache,
+                             gpointer resp)
+{
+    gsize i;
+    GIArgument arg = { 0, };
+
+    if (cache->return_cache->type_tag != GI_TYPE_TAG_VOID) {
+        _pygi_closure_assign_pyobj_to_retval (resp, &arg,
+                                              cache->return_cache);
+    }
+
+    for (i = 0; i < _pygi_callable_cache_args_len (cache); i++) {
+        PyGIArgCache *arg_cache = g_ptr_array_index (cache->args_cache, i);
+
+        if (arg_cache->direction & PYGI_DIRECTION_FROM_PYTHON) {
+            _pygi_closure_assign_pyobj_to_out_argument (state->args[i].arg_pointer.v_pointer,
+                                                        &arg, arg_cache);
+        }
+    }
+
+    if (cache->throws) {
+        gssize error_index = state->n_args - 1;
+        GError **error = (GError **) state->args[error_index].arg_value.v_pointer;
+
+        if (error != NULL) {
+            pygi_gerror_exception_check (error);
+        }
+    }
+}
+
+static void
+_pygi_invoke_closure_clear_py_data(PyGICClosure *invoke_closure)
+{
+    PyGILState_STATE state = PyGILState_Ensure();
+
+    Py_CLEAR (invoke_closure->function);
+    Py_CLEAR (invoke_closure->user_data);
+
+    PyGILState_Release (state);
+}
+
+void
+_pygi_closure_handle (ffi_cif *cif,
+                      void    *result,
+                      void   **args,
+                      void    *data)
+{
+    PyGILState_STATE py_state;
+    PyGICClosure *closure = data;
+    PyObject *retval;
+    gboolean success;
+    PyGIInvokeState state = { 0, };
+
+    /* Ignore closures when Python is not initialized. This can happen in cases
+     * where calling Python implemented vfuncs can happen at shutdown time.
+     * See: https://bugzilla.gnome.org/show_bug.cgi?id=722562 */
+    if (!Py_IsInitialized()) {
+        return;
+    }
+
+    /* Lock the GIL as we are coming into this code without the lock and we
+      may be executing python code */
+    py_state = PyGILState_Ensure ();
+
+    if (closure->cache == NULL)
+        goto end;
+
+    state.user_data = closure->user_data;
+
+    _invoke_state_init_from_cache (&state, closure->cache, args);
+
+    if (!_pygi_closure_convert_arguments (&state, closure->cache)) {
+        _pygi_closure_clear_retvals (&state, closure->cache, result);
+        goto end;
+    }
+
+    retval = PyObject_CallObject ( (PyObject *) closure->function, state.py_in_args);
+
+    if (retval == NULL) {
+        _pygi_closure_clear_retvals (&state, closure->cache, result);
+        goto end;
+    }
+
+    pygi_marshal_cleanup_args_to_py_marshal_success (&state, closure->cache);
+    success = _pygi_closure_set_out_arguments (&state, closure->cache, retval, result);
+
+    if (!success) {
+        pygi_marshal_cleanup_args_from_py_marshal_success (&state, closure->cache);
+        _pygi_closure_clear_retvals (&state, closure->cache, result);
+    }
+
+    Py_DECREF (retval);
+
+end:
+
+    if (PyErr_Occurred ())
+        PyErr_Print ();
+
+    /* Now that the closure has finished we can make a decision about how
+       to free it.  Scope call gets free'd at the end of wrap_g_function_info_invoke.
+       Scope notified will be freed when the notify is called.
+       Scope async closures free only their python data now and the closure later
+       during the next creation of a closure. This minimizes potential ref leaks
+       at least in regards to the python objects.
+       (you can't free the closure you are currently using!)
+    */
+    switch (closure->scope) {
+        case GI_SCOPE_TYPE_CALL:
+        case GI_SCOPE_TYPE_NOTIFIED:
+            break;
+        case GI_SCOPE_TYPE_ASYNC:
+            /* Append this PyGICClosure to a list of closure that we will free
+               after we're done with this function invokation */
+            _pygi_invoke_closure_clear_py_data(closure);
+            async_free_list = g_slist_prepend (async_free_list, closure);
+            break;
+        default:
+            g_error ("Invalid scope reached inside %s.  Possibly a bad annotation?",
+                     g_base_info_get_name (closure->info));
+    }
+
+    _invoke_state_clear (&state);
+    PyGILState_Release (py_state);
+}
+
+void _pygi_invoke_closure_free (gpointer data)
+{
+    PyGICClosure* invoke_closure = (PyGICClosure *) data;
+
+    g_callable_info_free_closure (invoke_closure->info,
+                                  invoke_closure->closure);
+
+    if (invoke_closure->info)
+        g_base_info_unref ( (GIBaseInfo*) invoke_closure->info);
+
+    invoke_closure->cache = NULL;
+
+    _pygi_invoke_closure_clear_py_data(invoke_closure);
+
+    g_slice_free (PyGICClosure, invoke_closure);
+}
+
+
+PyGICClosure*
+_pygi_make_native_closure (GICallableInfo* info,
+                           PyGIClosureCache *cache,
+                           GIScopeType scope,
+                           PyObject *py_function,
+                           gpointer py_user_data)
+{
+    PyGICClosure *closure;
+    ffi_closure *fficlosure;
+
+    /* Begin by cleaning up old async functions */
+    g_slist_free_full (async_free_list, (GDestroyNotify) _pygi_invoke_closure_free);
+    async_free_list = NULL;
+
+    /* Build the closure itself */
+    closure = g_slice_new0 (PyGICClosure);
+    closure->info = (GICallableInfo *) g_base_info_ref ( (GIBaseInfo *) info);
+    closure->function = py_function;
+    closure->user_data = py_user_data;
+    closure->cache = cache;
+
+    Py_INCREF (py_function);
+    Py_XINCREF (closure->user_data);
+
+    fficlosure =
+        g_callable_info_prepare_closure (info, &closure->cif, _pygi_closure_handle,
+                                         closure);
+    closure->closure = fficlosure;
+
+    /* Give the closure the information it needs to determine when
+       to free itself later */
+    closure->scope = scope;
+
+    return closure;
+}
+
+/* _pygi_destroy_notify_dummy:
+ *
+ * Dummy method used in the occasion when a method has a GDestroyNotify
+ * argument without user data.
+ */
+static void
+_pygi_destroy_notify_dummy (gpointer data) {
+}
+
+static gboolean
+_pygi_marshal_from_py_interface_callback (PyGIInvokeState   *state,
+                                          PyGICallableCache *callable_cache,
+                                          PyGIArgCache      *arg_cache,
+                                          PyObject          *py_arg,
+                                          GIArgument        *arg,
+                                          gpointer          *cleanup_data)
+{
+    GICallableInfo *callable_info;
+    PyGICClosure *closure;
+    PyGIArgCache *user_data_cache = NULL;
+    PyGIArgCache *destroy_cache = NULL;
+    PyGICallbackCache *callback_cache;
+    PyObject *py_user_data = NULL;
+
+    callback_cache = (PyGICallbackCache *)arg_cache;
+
+    if (callback_cache->user_data_index > 0) {
+        user_data_cache = _pygi_callable_cache_get_arg (callable_cache, (guint)callback_cache->user_data_index);
+        if (user_data_cache->py_arg_index < state->n_py_in_args) {
+            /* py_user_data is a borrowed reference. */
+            py_user_data = PyTuple_GetItem (state->py_in_args, user_data_cache->py_arg_index);
+            if (!py_user_data)
+                return FALSE;
+            /* NULL out user_data if it was not supplied and the default arg placeholder
+             * was used instead.
+             */
+            if (py_user_data == _PyGIDefaultArgPlaceholder) {
+                py_user_data = NULL;
+            } else if (callable_cache->user_data_varargs_index < 0) {
+                /* For non-variable length user data, place the user data in a
+                 * single item tuple which is concatenated to the callbacks arguments.
+                 * This allows callback input arg marshaling to always expect a
+                 * tuple for user data. Note the
+                 */
+                py_user_data = Py_BuildValue("(O)", py_user_data, NULL);
+            } else {
+                /* increment the ref borrowed from PyTuple_GetItem above */
+                Py_INCREF (py_user_data);
+            }
+        }
+    }
+
+    if (py_arg == Py_None) {
+        return TRUE;
+    }
+
+    if (!PyCallable_Check (py_arg)) {
+        PyErr_Format (PyExc_TypeError,
+                      "Callback needs to be a function or method not %s",
+                      Py_TYPE (py_arg)->tp_name);
+
+        return FALSE;
+    }
+
+    callable_info = (GICallableInfo *)callback_cache->interface_info;
+
+    closure = _pygi_make_native_closure (
+        callable_info, callback_cache->closure_cache, callback_cache->scope,
+        py_arg, py_user_data);
+    arg->v_pointer = closure->closure;
+
+    /* always decref the user data as _pygi_make_native_closure adds its own ref */
+    Py_XDECREF (py_user_data);
+
+    /* The PyGICClosure instance is used as user data passed into the C function.
+     * The return trip to python will marshal this back and pull the python user data out.
+     */
+    if (user_data_cache != NULL) {
+        state->args[user_data_cache->c_arg_index].arg_value.v_pointer = closure;
+    }
+
+    /* Setup a GDestroyNotify callback if this method supports it along with
+     * a user data field. The user data field is a requirement in order
+     * free resources and ref counts associated with this arguments closure.
+     * In case a user data field is not available, show a warning giving
+     * explicit information and setup a dummy notification to avoid a crash
+     * later on in _pygi_destroy_notify_callback_closure.
+     */
+    if (callback_cache->destroy_notify_index > 0) {
+        destroy_cache = _pygi_callable_cache_get_arg (callable_cache, (guint)callback_cache->destroy_notify_index);
+    }
+
+    if (destroy_cache) {
+        if (user_data_cache != NULL) {
+            state->args[destroy_cache->c_arg_index].arg_value.v_pointer = _pygi_invoke_closure_free;
+        } else {
+            char *full_name = pygi_callable_cache_get_full_name (callable_cache);
+            gchar *msg = g_strdup_printf("Callables passed to %s will leak references because "
+                                         "the method does not support a user_data argument. "
+                                         "See: https://bugzilla.gnome.org/show_bug.cgi?id=685598",
+                                         full_name);
+            g_free (full_name);
+            if (PyErr_WarnEx(PyExc_RuntimeWarning, msg, 2)) {
+                g_free(msg);
+                _pygi_invoke_closure_free(closure);
+                return FALSE;
+            }
+            g_free(msg);
+            state->args[destroy_cache->c_arg_index].arg_value.v_pointer = _pygi_destroy_notify_dummy;
+        }
+    }
+
+    /* Use the PyGIClosure as data passed to cleanup for GI_SCOPE_TYPE_CALL. */
+    *cleanup_data = closure;
+
+    return TRUE;
+}
+
+static PyObject *
+_pygi_marshal_to_py_interface_callback (PyGIInvokeState   *state,
+                                        PyGICallableCache *callable_cache,
+                                        PyGIArgCache      *arg_cache,
+                                        GIArgument        *arg,
+                                        gpointer          *arg_cleanup_data)
+{
+    PyGICallbackCache *callback_cache = (PyGICallbackCache *) arg_cache;
+    gssize user_data_index;
+    gssize destroy_notify_index;
+    gpointer user_data = NULL;
+    GDestroyNotify destroy_notify = NULL;
+
+    user_data_index = callback_cache->user_data_index;
+    destroy_notify_index = callback_cache->destroy_notify_index;
+
+    if (user_data_index != -1)
+        user_data = state->args[user_data_index].arg_value.v_pointer;
+
+    if (destroy_notify_index != -1)
+        destroy_notify = state->args[destroy_notify_index].arg_value.v_pointer;
+
+    return _pygi_ccallback_new (arg->v_pointer,
+                                user_data,
+                                callback_cache->scope,
+                                (GIFunctionInfo *) callback_cache->interface_info,
+                                destroy_notify);
+}
+
+static void
+_callback_cache_free_func (PyGICallbackCache *cache)
+{
+    if (cache != NULL) {
+        if (cache->interface_info != NULL)
+            g_base_info_unref ( (GIBaseInfo *)cache->interface_info);
+
+        if (cache->closure_cache != NULL) {
+            pygi_callable_cache_free ((PyGICallableCache *) cache->closure_cache);
+            cache->closure_cache = NULL;
+        }
+
+        g_slice_free (PyGICallbackCache, cache);
+    }
+}
+
+static void
+_pygi_marshal_cleanup_from_py_interface_callback (PyGIInvokeState *state,
+                                                  PyGIArgCache    *arg_cache,
+                                                  PyObject        *py_arg,
+                                                  gpointer         data,
+                                                  gboolean         was_processed)
+{
+    PyGICallbackCache *callback_cache = (PyGICallbackCache *)arg_cache;
+
+    if (was_processed && callback_cache->scope == GI_SCOPE_TYPE_CALL) {
+        _pygi_invoke_closure_free (data);
+    }
+}
+
+static gboolean
+pygi_arg_callback_setup_from_info (PyGICallbackCache  *arg_cache,
+                                   GITypeInfo         *type_info,
+                                   GIArgInfo          *arg_info,   /* may be null */
+                                   GITransfer          transfer,
+                                   PyGIDirection       direction,
+                                   GIInterfaceInfo    *iface_info,
+                                   PyGICallableCache  *callable_cache)
+{
+    PyGIArgCache *cache = (PyGIArgCache *)arg_cache;
+    gssize child_offset = 0;
+
+    if (!pygi_arg_base_setup ((PyGIArgCache *)arg_cache,
+                              type_info,
+                              arg_info,
+                              transfer,
+                              direction)) {
+        return FALSE;
+    }
+
+    if (callable_cache != NULL)
+        child_offset = callable_cache->args_offset;
+
+    ( (PyGIArgCache *)arg_cache)->destroy_notify = (GDestroyNotify)_callback_cache_free_func;
+
+    arg_cache->user_data_index = g_arg_info_get_closure (arg_info);
+    if (arg_cache->user_data_index != -1)
+        arg_cache->user_data_index += child_offset;
+
+    arg_cache->destroy_notify_index = g_arg_info_get_destroy (arg_info);
+    if (arg_cache->destroy_notify_index != -1)
+        arg_cache->destroy_notify_index += child_offset;
+
+    if (arg_cache->user_data_index >= 0) {
+        PyGIArgCache *user_data_arg_cache = pygi_arg_cache_alloc ();
+        user_data_arg_cache->meta_type = PYGI_META_ARG_TYPE_CHILD_WITH_PYARG;
+        user_data_arg_cache->direction = direction;
+        user_data_arg_cache->has_default = TRUE; /* always allow user data with a NULL default. */
+        _pygi_callable_cache_set_arg (callable_cache, (guint)arg_cache->user_data_index,
+                                      user_data_arg_cache);
+    }
+
+    if (arg_cache->destroy_notify_index >= 0) {
+        PyGIArgCache *destroy_arg_cache = pygi_arg_cache_alloc ();
+        destroy_arg_cache->meta_type = PYGI_META_ARG_TYPE_CHILD;
+        destroy_arg_cache->direction = direction;
+        _pygi_callable_cache_set_arg (callable_cache, (guint)arg_cache->destroy_notify_index,
+                                      destroy_arg_cache);
+    }
+
+    arg_cache->scope = g_arg_info_get_scope (arg_info);
+    g_base_info_ref( (GIBaseInfo *)iface_info);
+    arg_cache->interface_info = iface_info;
+
+    if (direction & PYGI_DIRECTION_FROM_PYTHON) {
+        arg_cache->closure_cache = pygi_closure_cache_new (arg_cache->interface_info);
+        cache->from_py_marshaller = _pygi_marshal_from_py_interface_callback;
+        cache->from_py_cleanup = _pygi_marshal_cleanup_from_py_interface_callback;
+    }
+
+    if (direction & PYGI_DIRECTION_TO_PYTHON) {
+        cache->to_py_marshaller = _pygi_marshal_to_py_interface_callback;
+    }
+
+    return TRUE;
+}
+
+PyGIArgCache *
+pygi_arg_callback_new_from_info  (GITypeInfo        *type_info,
+                                  GIArgInfo         *arg_info,   /* may be null */
+                                  GITransfer         transfer,
+                                  PyGIDirection      direction,
+                                  GIInterfaceInfo   *iface_info,
+                                  PyGICallableCache *callable_cache)
+{
+    gboolean res = FALSE;
+    PyGICallbackCache *callback_cache;
+
+    callback_cache = g_slice_new0 (PyGICallbackCache);
+    if (callback_cache == NULL)
+        return NULL;
+
+    res = pygi_arg_callback_setup_from_info (callback_cache,
+                                             type_info,
+                                             arg_info,
+                                             transfer,
+                                             direction,
+                                             iface_info,
+                                             callable_cache);
+    if (res) {
+        return (PyGIArgCache *)callback_cache;
+    } else {
+        pygi_arg_cache_free ((PyGIArgCache *)callback_cache);
+        return NULL;
+    }
+}
diff --git a/gi/pygi-closure.h b/gi/pygi-closure.h
new file mode 100644 (file)
index 0000000..30da2cf
--- /dev/null
@@ -0,0 +1,67 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_CLOSURE_H__
+#define __PYGI_CLOSURE_H__
+
+#include <Python.h>
+#include <girffi.h>
+#include <ffi.h>
+
+#include "pygi-cache.h"
+
+G_BEGIN_DECLS
+
+
+/* Private */
+
+typedef struct _PyGICClosure
+{
+    GICallableInfo *info;
+    PyObject *function;
+
+    ffi_closure *closure;
+    ffi_cif cif;
+
+    GIScopeType scope;
+
+    PyObject* user_data;
+
+    PyGIClosureCache *cache;
+} PyGICClosure;
+
+void _pygi_closure_handle (ffi_cif *cif, void *result, void
+                           **args, void *userdata);
+
+void _pygi_invoke_closure_free (gpointer user_data);
+
+PyGICClosure* _pygi_make_native_closure (GICallableInfo* info,
+                                         PyGIClosureCache *cache,
+                                         GIScopeType scope,
+                                         PyObject *function,
+                                         gpointer user_data);
+
+PyGIArgCache *pygi_arg_callback_new_from_info  (GITypeInfo        *type_info,
+                                                GIArgInfo         *arg_info,   /* may be null */
+                                                GITransfer         transfer,
+                                                PyGIDirection      direction,
+                                                GIInterfaceInfo   *iface_info,
+                                                PyGICallableCache *callable_cache);
+
+G_END_DECLS
+
+#endif /* __PYGI_CLOSURE_H__ */
diff --git a/gi/pygi-enum-marshal.c b/gi/pygi-enum-marshal.c
new file mode 100644 (file)
index 0000000..fc33089
--- /dev/null
@@ -0,0 +1,408 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include <glib.h>
+
+#include "pygi-python-compat.h"
+#include "pygi-enum-marshal.h"
+#include "pygi-type.h"
+#include "pygenum.h"
+#include "pygflags.h"
+
+static gboolean
+gi_argument_from_c_long (GIArgument *arg_out,
+                         long        c_long_in,
+                         GITypeTag   type_tag)
+{
+    switch (type_tag) {
+      case GI_TYPE_TAG_INT8:
+          arg_out->v_int8 = (gint8)c_long_in;
+          return TRUE;
+      case GI_TYPE_TAG_UINT8:
+          arg_out->v_uint8 = (guint8)c_long_in;
+          return TRUE;
+      case GI_TYPE_TAG_INT16:
+          arg_out->v_int16 = (gint16)c_long_in;
+          return TRUE;
+      case GI_TYPE_TAG_UINT16:
+          arg_out->v_uint16 = (guint16)c_long_in;
+          return TRUE;
+      case GI_TYPE_TAG_INT32:
+          arg_out->v_int32 = (gint32)c_long_in;
+          return TRUE;
+      case GI_TYPE_TAG_UINT32:
+          arg_out->v_uint32 = (guint32)c_long_in;
+          return TRUE;
+      case GI_TYPE_TAG_INT64:
+          arg_out->v_int64 = (gint64)c_long_in;
+          return TRUE;
+      case GI_TYPE_TAG_UINT64:
+          arg_out->v_uint64 = (guint64)c_long_in;
+          return TRUE;
+      default:
+          PyErr_Format (PyExc_TypeError,
+                        "Unable to marshal C long %ld to %s",
+                        c_long_in,
+                        g_type_tag_to_string (type_tag));
+          return FALSE;
+    }
+}
+
+static gboolean
+gi_argument_to_c_long (GIArgument *arg_in,
+                       long *c_long_out,
+                       GITypeTag type_tag)
+{
+    switch (type_tag) {
+      case GI_TYPE_TAG_INT8:
+          *c_long_out = arg_in->v_int8;
+          return TRUE;
+      case GI_TYPE_TAG_UINT8:
+          *c_long_out = arg_in->v_uint8;
+          return TRUE;
+      case GI_TYPE_TAG_INT16:
+          *c_long_out = arg_in->v_int16;
+          return TRUE;
+      case GI_TYPE_TAG_UINT16:
+          *c_long_out = arg_in->v_uint16;
+          return TRUE;
+      case GI_TYPE_TAG_INT32:
+          *c_long_out = arg_in->v_int32;
+          return TRUE;
+      case GI_TYPE_TAG_UINT32:
+          *c_long_out = arg_in->v_uint32;
+          return TRUE;
+      case GI_TYPE_TAG_INT64:
+          if (arg_in->v_int64 > G_MAXLONG || arg_in->v_int64 < G_MINLONG) {
+              PyErr_Format (PyExc_TypeError,
+                            "Unable to marshal %s to C long",
+                            g_type_tag_to_string(type_tag));
+              return FALSE;
+          }
+          *c_long_out = (glong)arg_in->v_int64;
+          return TRUE;
+      case GI_TYPE_TAG_UINT64:
+          if (arg_in->v_uint64 > G_MAXLONG) {
+              PyErr_Format (PyExc_TypeError,
+                            "Unable to marshal %s to C long",
+                            g_type_tag_to_string(type_tag));
+              return FALSE;
+          }
+          *c_long_out = (glong)arg_in->v_uint64;
+          return TRUE;
+      default:
+          PyErr_Format (PyExc_TypeError,
+                        "Unable to marshal %s to C long",
+                        g_type_tag_to_string (type_tag));
+          return FALSE;
+    }
+}
+
+static gboolean
+_pygi_marshal_from_py_interface_enum (PyGIInvokeState   *state,
+                                      PyGICallableCache *callable_cache,
+                                      PyGIArgCache      *arg_cache,
+                                      PyObject          *py_arg,
+                                      GIArgument        *arg,
+                                      gpointer          *cleanup_data)
+{
+    PyObject *py_long;
+    long c_long;
+    gint is_instance;
+    PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)arg_cache;
+    GIBaseInfo *interface = NULL;
+
+    is_instance = PyObject_IsInstance (py_arg, iface_cache->py_type);
+
+    py_long = PYGLIB_PyNumber_Long (py_arg);
+    if (py_long == NULL) {
+        PyErr_Clear();
+        goto err;
+    }
+
+    c_long = PYGLIB_PyLong_AsLong (py_long);
+    Py_DECREF (py_long);
+
+    /* Write c_long into arg */
+    interface = g_type_info_get_interface (arg_cache->type_info);
+    assert(g_base_info_get_type (interface) == GI_INFO_TYPE_ENUM);
+    if (!gi_argument_from_c_long(arg,
+                                 c_long,
+                                 g_enum_info_get_storage_type ((GIEnumInfo *)interface))) {
+          g_assert_not_reached();
+          g_base_info_unref (interface);
+          return FALSE;
+    }
+
+    /* If this is not an instance of the Enum type that we want
+     * we need to check if the value is equivilant to one of the
+     * Enum's memebers */
+    if (!is_instance) {
+        int i;
+        gboolean is_found = FALSE;
+
+        for (i = 0; i < g_enum_info_get_n_values (iface_cache->interface_info); i++) {
+            GIValueInfo *value_info =
+                g_enum_info_get_value (iface_cache->interface_info, i);
+            gint64 enum_value = g_value_info_get_value (value_info);
+            g_base_info_unref ( (GIBaseInfo *)value_info);
+            if (c_long == enum_value) {
+                is_found = TRUE;
+                break;
+            }
+        }
+
+        if (!is_found)
+            goto err;
+    }
+
+    g_base_info_unref (interface);
+    return TRUE;
+
+err:
+    if (interface)
+        g_base_info_unref (interface);
+    PyErr_Format (PyExc_TypeError, "Expected a %s, but got %s",
+                  iface_cache->type_name, Py_TYPE (py_arg)->tp_name);
+    return FALSE;
+}
+
+static gboolean
+_pygi_marshal_from_py_interface_flags (PyGIInvokeState   *state,
+                                       PyGICallableCache *callable_cache,
+                                       PyGIArgCache      *arg_cache,
+                                       PyObject          *py_arg,
+                                       GIArgument        *arg,
+                                       gpointer          *cleanup_data)
+{
+    PyObject *py_long;
+    unsigned long c_ulong;
+    gint is_instance;
+    PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)arg_cache;
+    GIBaseInfo *interface;
+
+    is_instance = PyObject_IsInstance (py_arg, iface_cache->py_type);
+
+    py_long = PYGLIB_PyNumber_Long (py_arg);
+    if (py_long == NULL) {
+        PyErr_Clear ();
+        goto err;
+    }
+
+    c_ulong = PYGLIB_PyLong_AsUnsignedLong (py_long);
+    Py_DECREF (py_long);
+
+    /* only 0 or argument of type Flag is allowed */
+    if (!is_instance && c_ulong != 0)
+        goto err;
+
+    /* Write c_long into arg */
+    interface = g_type_info_get_interface (arg_cache->type_info);
+    g_assert (g_base_info_get_type (interface) == GI_INFO_TYPE_FLAGS);
+    if (!gi_argument_from_c_long(arg, c_ulong,
+                                 g_enum_info_get_storage_type ((GIEnumInfo *)interface))) {
+        g_base_info_unref (interface);
+        return FALSE;
+    }
+
+    g_base_info_unref (interface);
+    return TRUE;
+
+err:
+    PyErr_Format (PyExc_TypeError, "Expected a %s, but got %s",
+                  iface_cache->type_name, Py_TYPE (py_arg)->tp_name);
+    return FALSE;
+
+}
+
+static PyObject *
+_pygi_marshal_to_py_interface_enum (PyGIInvokeState   *state,
+                                    PyGICallableCache *callable_cache,
+                                    PyGIArgCache      *arg_cache,
+                                    GIArgument        *arg,
+                                    gpointer          *cleanup_data)
+{
+    PyObject *py_obj = NULL;
+    PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)arg_cache;
+    GIBaseInfo *interface;
+    long c_long;
+
+    interface = g_type_info_get_interface (arg_cache->type_info);
+    g_assert (g_base_info_get_type (interface) == GI_INFO_TYPE_ENUM);
+
+    if (!gi_argument_to_c_long(arg, &c_long,
+                               g_enum_info_get_storage_type ((GIEnumInfo *)interface))) {
+        return NULL;
+    }
+
+    if (iface_cache->g_type == G_TYPE_NONE) {
+        py_obj = PyObject_CallFunction (iface_cache->py_type, "l", c_long);
+    } else {
+        py_obj = pyg_enum_from_gtype (iface_cache->g_type, (gint)c_long);
+    }
+    g_base_info_unref (interface);
+    return py_obj;
+}
+
+static PyObject *
+_pygi_marshal_to_py_interface_flags (PyGIInvokeState   *state,
+                                     PyGICallableCache *callable_cache,
+                                     PyGIArgCache      *arg_cache,
+                                     GIArgument        *arg,
+                                     gpointer          *cleanup_data)
+{
+    PyObject *py_obj = NULL;
+    PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)arg_cache;
+    GIBaseInfo *interface;
+    long c_long;
+
+    interface = g_type_info_get_interface (arg_cache->type_info);
+    g_assert (g_base_info_get_type (interface) == GI_INFO_TYPE_FLAGS);
+
+    if (!gi_argument_to_c_long(arg, &c_long,
+                               g_enum_info_get_storage_type ((GIEnumInfo *)interface))) {
+        g_base_info_unref (interface);
+        return NULL;
+    }
+
+    g_base_info_unref (interface);
+    if (iface_cache->g_type == G_TYPE_NONE) {
+        /* An enum with a GType of None is an enum without GType */
+
+        PyObject *py_type = pygi_type_import_by_gi_info (iface_cache->interface_info);
+        PyObject *py_args = NULL;
+
+        if (!py_type)
+            return NULL;
+
+        py_args = PyTuple_New (1);
+        if (PyTuple_SetItem (py_args, 0, PyLong_FromLong (c_long)) != 0) {
+            Py_DECREF (py_args);
+            Py_DECREF (py_type);
+            return NULL;
+        }
+
+        py_obj = PyObject_CallFunction (py_type, "l", c_long);
+
+        Py_DECREF (py_args);
+        Py_DECREF (py_type);
+    } else {
+        py_obj = pyg_flags_from_gtype (iface_cache->g_type, (guint)c_long);
+    }
+
+    return py_obj;
+}
+
+static gboolean
+pygi_arg_enum_setup_from_info (PyGIArgCache  *arg_cache,
+                               GITypeInfo    *type_info,
+                               GIArgInfo     *arg_info,
+                               GITransfer     transfer,
+                               PyGIDirection  direction)
+{
+    if (direction & PYGI_DIRECTION_FROM_PYTHON)
+        arg_cache->from_py_marshaller = _pygi_marshal_from_py_interface_enum;
+
+    if (direction & PYGI_DIRECTION_TO_PYTHON)
+        arg_cache->to_py_marshaller = _pygi_marshal_to_py_interface_enum;
+
+    return TRUE;
+}
+
+
+PyGIArgCache *
+pygi_arg_enum_new_from_info (GITypeInfo      *type_info,
+                             GIArgInfo       *arg_info,
+                             GITransfer       transfer,
+                             PyGIDirection    direction,
+                             GIInterfaceInfo *iface_info)
+{
+    gboolean res = FALSE;
+    PyGIArgCache *cache = NULL;
+
+    cache = pygi_arg_interface_new_from_info (type_info,
+                                              arg_info,
+                                              transfer,
+                                              direction,
+                                              iface_info);
+    if (cache == NULL)
+        return NULL;
+
+    res = pygi_arg_enum_setup_from_info (cache,
+                                         type_info,
+                                         arg_info,
+                                         transfer,
+                                         direction);
+    if (res) {
+        return cache;
+    } else {
+        pygi_arg_cache_free (cache);
+        return NULL;
+    }
+}
+
+static gboolean
+pygi_arg_flags_setup_from_info (PyGIArgCache  *arg_cache,
+                                GITypeInfo    *type_info,
+                                GIArgInfo     *arg_info,
+                                GITransfer     transfer,
+                                PyGIDirection  direction)
+{
+    if (direction & PYGI_DIRECTION_FROM_PYTHON)
+        arg_cache->from_py_marshaller = _pygi_marshal_from_py_interface_flags;
+
+    if (direction & PYGI_DIRECTION_TO_PYTHON)
+        arg_cache->to_py_marshaller = _pygi_marshal_to_py_interface_flags;
+
+    return TRUE;
+}
+
+
+PyGIArgCache *
+pygi_arg_flags_new_from_info (GITypeInfo      *type_info,
+                              GIArgInfo       *arg_info,
+                              GITransfer       transfer,
+                              PyGIDirection    direction,
+                              GIInterfaceInfo *iface_info)
+{
+    gboolean res = FALSE;
+    PyGIArgCache *cache = NULL;
+
+    cache = pygi_arg_interface_new_from_info (type_info,
+                                              arg_info,
+                                              transfer,
+                                              direction,
+                                              iface_info);
+    if (cache == NULL)
+        return NULL;
+
+    res = pygi_arg_flags_setup_from_info (cache,
+                                          type_info,
+                                          arg_info,
+                                          transfer,
+                                          direction);
+    if (res) {
+        return cache;
+    } else {
+        pygi_arg_cache_free (cache);
+        return NULL;
+    }
+}
diff --git a/gi/pygi-enum-marshal.h b/gi/pygi-enum-marshal.h
new file mode 100644 (file)
index 0000000..2fdcbc4
--- /dev/null
@@ -0,0 +1,42 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_ENUM_MARSHAL_H__
+#define __PYGI_ENUM_MARSHAL_H__
+
+#include <girepository.h>
+#include "pygi-cache.h"
+
+G_BEGIN_DECLS
+
+PyGIArgCache *pygi_arg_enum_new_from_info   (GITypeInfo      *type_info,
+                                             GIArgInfo       *arg_info,   /* may be null */
+                                             GITransfer       transfer,
+                                             PyGIDirection    direction,
+                                             GIInterfaceInfo *iface_info);
+
+PyGIArgCache *pygi_arg_flags_new_from_info  (GITypeInfo      *type_info,
+                                             GIArgInfo       *arg_info,   /* may be null */
+                                             GITransfer       transfer,
+                                             PyGIDirection    direction,
+                                             GIInterfaceInfo *iface_info);
+
+G_END_DECLS
+
+#endif /*__PYGI_ENUM_MARSHAL_H__*/
diff --git a/gi/pygi-error.c b/gi/pygi-error.c
new file mode 100644 (file)
index 0000000..8e4a793
--- /dev/null
@@ -0,0 +1,376 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include "pygi-error.h"
+#include "pygi-type.h"
+#include "pygi-python-compat.h"
+#include "pygi-util.h"
+#include "pygi-basictype.h"
+
+
+PyObject *PyGError = NULL;
+
+/**
+ * pygi_error_marshal_to_py:
+ * @error: a pointer to the GError.
+ *
+ * Checks to see if @error has been set.  If @error has been set, then a
+ * GLib.GError Python exception object is returned (but not raised).
+ *
+ * Returns: a GLib.GError Python exception object, or NULL.
+ */
+PyObject *
+pygi_error_marshal_to_py (GError **error)
+{
+    PyGILState_STATE state;
+    PyObject *exc_type;
+    PyObject *exc_instance;
+    const char *domain = NULL;
+
+    g_return_val_if_fail(error != NULL, NULL);
+
+    if (*error == NULL)
+        return NULL;
+
+    state = PyGILState_Ensure();
+
+    exc_type = PyGError;
+
+    if ((*error)->domain) {
+        domain = g_quark_to_string ((*error)->domain);
+    }
+
+    exc_instance = PyObject_CallFunction (exc_type, "ssi",
+                                          (*error)->message,
+                                          domain,
+                                          (*error)->code);
+
+    PyGILState_Release(state);
+
+    return exc_instance;
+}
+
+/**
+ * pygi_error_check:
+ * @error: a pointer to the GError.
+ *
+ * Checks to see if the GError has been set.  If the error has been
+ * set, then the glib.GError Python exception will be raised, and
+ * the GError cleared.
+ *
+ * Returns: True if an error was set.
+ */
+gboolean
+pygi_error_check (GError **error)
+{
+    PyGILState_STATE state;
+    PyObject *exc_instance;
+
+    g_return_val_if_fail(error != NULL, FALSE);
+    if (*error == NULL)
+        return FALSE;
+
+    state = PyGILState_Ensure();
+
+    exc_instance = pygi_error_marshal_to_py (error);
+    PyErr_SetObject(PyGError, exc_instance);
+    Py_DECREF(exc_instance);
+    g_clear_error(error);
+
+    PyGILState_Release(state);
+
+    return TRUE;
+}
+
+/**
+ * pygi_error_marshal_from_py:
+ * @pyerr: A Python exception instance.
+ * @error: a standard GLib GError ** output parameter
+ *
+ * Converts from a Python implemented GError into a GError.
+ *
+ * Returns: TRUE if the conversion was successful, otherwise a Python exception
+ *          is set and FALSE is returned.
+ */
+gboolean
+pygi_error_marshal_from_py (PyObject *pyerr, GError **error)
+{
+    gint code;
+    gchar *message = NULL;
+    gchar *domain = NULL;
+    gboolean res = FALSE;
+    PyObject *py_message = NULL,
+             *py_domain = NULL,
+             *py_code = NULL;
+
+    if (PyObject_IsInstance (pyerr, PyGError) != 1) {
+        PyErr_Format (PyExc_TypeError, "Must be GLib.Error, not %s",
+                      Py_TYPE (pyerr)->tp_name);
+        return FALSE;
+    }
+
+    py_message = PyObject_GetAttrString (pyerr, "message");
+    if (!py_message) {
+        PyErr_SetString (PyExc_ValueError,
+                         "GLib.Error instances must have a 'message' string attribute");
+        goto cleanup;
+    }
+
+    if (!pygi_utf8_from_py (py_message, &message))
+        goto cleanup;
+
+    py_domain = PyObject_GetAttrString (pyerr, "domain");
+    if (!py_domain) {
+        PyErr_SetString (PyExc_ValueError,
+                         "GLib.Error instances must have a 'domain' string attribute");
+        goto cleanup;
+    }
+
+    if (!pygi_utf8_from_py (py_domain, &domain))
+        goto cleanup;
+
+    py_code = PyObject_GetAttrString (pyerr, "code");
+    if (!py_code) {
+        PyErr_SetString (PyExc_ValueError,
+                         "GLib.Error instances must have a 'code' int attribute");
+        goto cleanup;
+    }
+
+    if (!pygi_gint_from_py (py_code, &code))
+        goto cleanup;
+
+    res = TRUE;
+    g_set_error_literal (error,
+                         g_quark_from_string (domain),
+                         code,
+                         message);
+
+cleanup:
+    g_free (message);
+    g_free (domain);
+    Py_XDECREF (py_message);
+    Py_XDECREF (py_code);
+    Py_XDECREF (py_domain);
+    return res;
+}
+
+/**
+ * pygi_gerror_exception_check:
+ * @error: a standard GLib GError ** output parameter
+ *
+ * Checks to see if a GError exception has been raised, and if so
+ * translates the python exception to a standard GLib GError.  If the
+ * raised exception is not a GError then PyErr_Print() is called.
+ *
+ * Returns: 0 if no exception has been raised, -1 if it is a
+ * valid glib.GError, -2 otherwise.
+ */
+gboolean
+pygi_gerror_exception_check (GError **error)
+{
+    int res = -1;
+    PyObject *type, *value, *traceback;
+    PyErr_Fetch(&type, &value, &traceback);
+    if (type == NULL)
+        return 0;
+    PyErr_NormalizeException(&type, &value, &traceback);
+    if (value == NULL) {
+        PyErr_Restore(type, value, traceback);
+        PyErr_Print();
+        return -2;
+    }
+    if (!value ||
+        !PyErr_GivenExceptionMatches(type,
+                                     (PyObject *) PyGError)) {
+        PyErr_Restore(type, value, traceback);
+        PyErr_Print();
+        return -2;
+    }
+    Py_DECREF(type);
+    Py_XDECREF(traceback);
+
+    if (!pygi_error_marshal_from_py (value, error)) {
+        PyErr_Print();
+        res = -2;
+    }
+
+    Py_DECREF(value);
+    return res;
+
+}
+
+static gboolean
+_pygi_marshal_from_py_gerror (PyGIInvokeState   *state,
+                              PyGICallableCache *callable_cache,
+                              PyGIArgCache      *arg_cache,
+                              PyObject          *py_arg,
+                              GIArgument        *arg,
+                              gpointer          *cleanup_data)
+{
+    GError *error = NULL;
+    if (pygi_error_marshal_from_py (py_arg, &error)) {
+        arg->v_pointer = error;
+        *cleanup_data = error;
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+
+
+static void
+_pygi_marshal_from_py_gerror_cleanup  (PyGIInvokeState *state,
+                                       PyGIArgCache    *arg_cache,
+                                       PyObject        *py_arg,
+                                       gpointer         data,
+                                       gboolean         was_processed)
+{
+    if (was_processed) {
+        g_error_free ((GError *)data);
+    }
+}
+
+static PyObject *
+_pygi_marshal_to_py_gerror (PyGIInvokeState   *state,
+                            PyGICallableCache *callable_cache,
+                            PyGIArgCache      *arg_cache,
+                            GIArgument        *arg,
+                            gpointer          *cleanup_data)
+{
+    GError *error = arg->v_pointer;
+    PyObject *py_obj = NULL;
+
+    py_obj = pygi_error_marshal_to_py (&error);
+
+    if (arg_cache->transfer == GI_TRANSFER_EVERYTHING && error != NULL) {
+        g_error_free (error);
+    }
+
+    if (py_obj != NULL) {
+        return py_obj;
+    } else {
+        Py_RETURN_NONE;
+    }
+}
+
+static gboolean
+pygi_arg_gerror_setup_from_info (PyGIArgCache  *arg_cache,
+                                 GITypeInfo    *type_info,
+                                 GIArgInfo     *arg_info,
+                                 GITransfer     transfer,
+                                 PyGIDirection  direction)
+{
+    if (!pygi_arg_base_setup (arg_cache, type_info, arg_info, transfer, direction)) {
+        return FALSE;
+    }
+
+    if (direction & PYGI_DIRECTION_FROM_PYTHON) {
+        arg_cache->from_py_marshaller = _pygi_marshal_from_py_gerror;
+
+        /* Assign cleanup function if we manage memory after call completion. */
+        if (arg_cache->transfer == GI_TRANSFER_NOTHING) {
+            arg_cache->from_py_cleanup = _pygi_marshal_from_py_gerror_cleanup;
+        }
+    }
+
+    if (direction & PYGI_DIRECTION_TO_PYTHON) {
+        arg_cache->to_py_marshaller = _pygi_marshal_to_py_gerror;
+        arg_cache->meta_type = PYGI_META_ARG_TYPE_PARENT;
+    }
+
+    return TRUE;
+}
+
+PyGIArgCache *
+pygi_arg_gerror_new_from_info (GITypeInfo   *type_info,
+                               GIArgInfo    *arg_info,
+                               GITransfer    transfer,
+                               PyGIDirection direction)
+{
+    gboolean res = FALSE;
+    PyGIArgCache *arg_cache;
+
+    arg_cache = pygi_arg_cache_alloc ();
+
+    res = pygi_arg_gerror_setup_from_info (arg_cache,
+                                           type_info,
+                                           arg_info,
+                                           transfer,
+                                           direction);
+    if (res) {
+        return arg_cache;
+    } else {
+        pygi_arg_cache_free (arg_cache);
+        return NULL;
+    }
+}
+
+static PyObject *
+pygerror_from_gvalue (const GValue *value)
+{
+    GError *gerror = (GError *) g_value_get_boxed (value);
+    PyObject *pyerr = pygi_error_marshal_to_py (&gerror);
+    if (pyerr == NULL) {
+        Py_RETURN_NONE;
+    } else {
+        return pyerr;
+    }
+}
+
+static int
+pygerror_to_gvalue (GValue *value, PyObject *pyerror)
+{
+    GError *gerror = NULL;
+
+    if (pygi_error_marshal_from_py (pyerror, &gerror)) {
+        g_value_take_boxed (value, gerror);
+        return 0;
+    }
+
+    return -1;
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_error_register_types (PyObject *module)
+{
+    PyObject *error_module = pygi_import_module ("gi._error");
+    if (!error_module) {
+        return -1;
+    }
+
+    /* Stash a reference to the Python implemented gi._error.GError. */
+    PyGError = PyObject_GetAttrString (error_module, "GError");
+    Py_DECREF (error_module);
+    if (PyGError == NULL)
+        return -1;
+
+    pyg_register_gtype_custom (G_TYPE_ERROR,
+                               pygerror_from_gvalue,
+                               pygerror_to_gvalue);
+
+    return 0;
+}
+
diff --git a/gi/pygi-error.h b/gi/pygi-error.h
new file mode 100644 (file)
index 0000000..3e6c414
--- /dev/null
@@ -0,0 +1,50 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_ERROR_H__
+#define __PYGI_ERROR_H__
+
+#include <girepository.h>
+#include "pygi-cache.h"
+
+G_BEGIN_DECLS
+
+extern PyObject *PyGError;
+
+gboolean      pygi_error_check              (GError **error);
+
+PyObject*     pygi_error_marshal_to_py      (GError **error);
+
+gboolean      pygi_error_marshal_from_py    (PyObject  *pyerr,
+                                             GError   **error);
+
+gboolean      pygi_gerror_exception_check   (GError **error);
+
+PyGIArgCache* pygi_arg_gerror_new_from_info (GITypeInfo    *type_info,
+                                             GIArgInfo     *arg_info,   /* may be null */
+                                             GITransfer     transfer,
+                                             PyGIDirection  direction);
+
+int           pygi_error_register_types     (PyObject *module);
+
+G_END_DECLS
+
+#endif /*__PYGI_ERROR_H__*/
diff --git a/gi/pygi-foreign-api.h b/gi/pygi-foreign-api.h
new file mode 100644 (file)
index 0000000..9367518
--- /dev/null
@@ -0,0 +1,85 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_FOREIGN_API_H__
+#define __PYGI_FOREIGN_API_H__
+
+#include <girepository.h>
+#include <pygobject.h>
+
+typedef PyObject * (*PyGIArgOverrideToGIArgumentFunc)   (PyObject        *value,
+                                                         GIInterfaceInfo *interface_info,
+                                                         GITransfer       transfer,
+                                                         GIArgument      *arg);
+typedef PyObject * (*PyGIArgOverrideFromGIArgumentFunc) (GIInterfaceInfo *interface_info,
+                                                         GITransfer       transfer,
+                                                         gpointer         data);
+typedef PyObject * (*PyGIArgOverrideReleaseFunc)        (GITypeInfo *type_info,
+                                                         gpointer  struct_);
+
+
+struct PyGI_API {
+    void (*register_foreign_struct) (const char* namespace_,
+                                     const char* name,
+                                     PyGIArgOverrideToGIArgumentFunc to_func,
+                                     PyGIArgOverrideFromGIArgumentFunc from_func,
+                                     PyGIArgOverrideReleaseFunc release_func);
+};
+
+
+#ifndef _INSIDE_PYGOBJECT_
+
+static struct PyGI_API *PyGI_API = NULL;
+
+static int
+_pygi_import (void)
+{
+    if (PyGI_API != NULL) {
+        return 1;
+    }
+    PyGI_API = (struct PyGI_API*) PyCapsule_Import("gi._API", FALSE);
+    if (PyGI_API == NULL) {
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static inline PyObject *
+pygi_register_foreign_struct (const char* namespace_,
+                              const char* name,
+                              PyGIArgOverrideToGIArgumentFunc to_func,
+                              PyGIArgOverrideFromGIArgumentFunc from_func,
+                              PyGIArgOverrideReleaseFunc release_func)
+{
+    if (_pygi_import() < 0) {
+        return NULL;
+    }
+    PyGI_API->register_foreign_struct(namespace_,
+                                      name,
+                                      to_func,
+                                      from_func,
+                                      release_func);
+    Py_RETURN_NONE;
+}
+
+#endif /* _INSIDE_PYGOBJECT_ */
+
+#endif /* __PYGI_FOREIGN_API_H__ */
diff --git a/gi/pygi-foreign-cairo.c b/gi/pygi-foreign-cairo.c
new file mode 100644 (file)
index 0000000..c398cb7
--- /dev/null
@@ -0,0 +1,610 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2010  Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <Python.h>
+#include <cairo.h>
+
+#if PY_VERSION_HEX < 0x03000000
+#include <pycairo.h>
+static Pycairo_CAPI_t *Pycairo_CAPI;
+#else
+#include <py3cairo.h>
+#endif
+
+#include <cairo-gobject.h>
+
+/* Limit includes from PyGI to APIs which do not have link dependencies
+ * (pygobject.h and pygi-foreign-api.h) since _gi_cairo is built as a separate
+ * shared library that interacts with PyGI through a PyCapsule API at runtime.
+ */
+#include <pygi-foreign-api.h>
+#include "pygi-python-compat.h"
+
+/*
+ * cairo_t marshaling
+ */
+
+static PyObject *
+cairo_context_to_arg (PyObject        *value,
+                      GIInterfaceInfo *interface_info,
+                      GITransfer       transfer,
+                      GIArgument      *arg)
+{
+    cairo_t *cr;
+
+    if (!PyObject_TypeCheck (value, &PycairoContext_Type)) {
+        PyErr_SetString (PyExc_TypeError, "Expected cairo.Context");
+        return NULL;
+    }
+
+    cr = PycairoContext_GET (value);
+    if (!cr) {
+        return NULL;
+    }
+
+    if (transfer != GI_TRANSFER_NOTHING)
+        cr = cairo_reference (cr);
+
+    arg->v_pointer = cr;
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+cairo_context_from_arg (GIInterfaceInfo *interface_info,
+                        GITransfer       transfer,
+                        gpointer         data)
+{
+    cairo_t *context = (cairo_t*) data;
+
+    if (transfer == GI_TRANSFER_NOTHING)
+        cairo_reference (context);
+
+    return PycairoContext_FromContext (context, &PycairoContext_Type, NULL);
+}
+
+
+static PyObject *
+cairo_context_release (GIBaseInfo *base_info,
+                       gpointer    struct_)
+{
+    cairo_destroy ( (cairo_t*) struct_);
+    Py_RETURN_NONE;
+}
+
+static int
+cairo_context_to_gvalue (GValue *value, PyObject *obj)
+{
+    cairo_t *cr;
+
+    if (!PyObject_TypeCheck (obj, &PycairoContext_Type)) {
+        PyErr_SetString (PyExc_TypeError, "Expected cairo.Context");
+        return -1;
+    }
+
+    cr = PycairoContext_GET (obj);
+    if (!cr) {
+        return -1;
+    }
+
+    /* PycairoContext_GET returns a borrowed reference, use set_boxed
+     * to add new ref to the context which will be managed by the GValue. */
+    g_value_set_boxed (value, cr);
+    return 0;
+}
+
+static PyObject *
+cairo_context_from_gvalue (const GValue *value)
+{
+    /* PycairoContext_FromContext steals a ref, so we dup it out of the GValue. */
+    cairo_t *cr = g_value_dup_boxed (value);
+    if (!cr) {
+        return NULL;
+    }
+
+    return PycairoContext_FromContext (cr, &PycairoContext_Type, NULL);
+}
+
+
+/*
+ * cairo_surface_t marshaling
+ */
+
+static PyObject *
+cairo_surface_to_arg (PyObject        *value,
+                      GIInterfaceInfo *interface_info,
+                      GITransfer       transfer,
+                      GIArgument      *arg)
+{
+    cairo_surface_t *surface;
+
+    if (!PyObject_TypeCheck (value, &PycairoSurface_Type)) {
+        PyErr_SetString (PyExc_TypeError, "Expected cairo.Surface");
+        return NULL;
+    }
+
+    surface = ( (PycairoSurface*) value)->surface;
+    if (!surface) {
+        PyErr_SetString (PyExc_ValueError, "Surface instance wrapping a NULL surface");
+        return NULL;
+    }
+
+    if (transfer != GI_TRANSFER_NOTHING)
+        surface = cairo_surface_reference (surface);
+
+    arg->v_pointer = surface;
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+cairo_surface_from_arg (GIInterfaceInfo *interface_info,
+                        GITransfer       transfer,
+                        gpointer         data)
+{
+    cairo_surface_t *surface = (cairo_surface_t*) data;
+
+    if (transfer == GI_TRANSFER_NOTHING)
+        cairo_surface_reference (surface);
+
+    return PycairoSurface_FromSurface (surface, NULL);
+}
+
+static PyObject *
+cairo_surface_release (GIBaseInfo *base_info,
+                       gpointer    struct_)
+{
+    cairo_surface_destroy ( (cairo_surface_t*) struct_);
+    Py_RETURN_NONE;
+}
+
+static int
+cairo_surface_to_gvalue (GValue *value, PyObject *obj)
+{
+    cairo_surface_t *surface;
+
+    if (!PyObject_TypeCheck (obj, &PycairoSurface_Type)) {
+        PyErr_SetString (PyExc_TypeError, "Expected cairo.Surface");
+        return -1;
+    }
+
+    surface = ((PycairoSurface*) obj)->surface;
+    if (!surface) {
+        return -1;
+    }
+
+    /* surface is a borrowed reference, use set_boxed
+     * to add new ref to the context which will be managed by the GValue. */
+    g_value_set_boxed (value, surface);
+    return 0;
+}
+
+static PyObject *
+cairo_surface_from_gvalue (const GValue *value)
+{
+    /* PycairoSurface_FromSurface steals a ref, so we dup it out of the GValue. */
+    cairo_surface_t *surface = g_value_dup_boxed (value);
+    if (!surface) {
+        return NULL;
+    }
+
+    return PycairoSurface_FromSurface (surface, NULL);
+}
+
+
+/*
+ * cairo_path_t marshaling
+ */
+
+static cairo_path_t *
+_cairo_path_copy (cairo_path_t *path) {
+    cairo_t *cr;
+    cairo_surface_t *surface;
+    cairo_path_t *copy;
+
+    surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0);
+    cr = cairo_create (surface);
+    cairo_append_path (cr, path);
+    copy = cairo_copy_path (cr);
+    cairo_destroy (cr);
+    cairo_surface_destroy (surface);
+
+    return copy;
+}
+
+static PyObject *
+cairo_path_to_arg (PyObject        *value,
+                   GIInterfaceInfo *interface_info,
+                   GITransfer       transfer,
+                   GIArgument      *arg)
+{
+    cairo_path_t *path;
+
+    if (!PyObject_TypeCheck (value, &PycairoPath_Type)) {
+        PyErr_SetString (PyExc_TypeError, "Expected cairo.Path");
+        return NULL;
+    }
+
+    path = ( (PycairoPath*) value)->path;
+    if (!path) {
+        PyErr_SetString (PyExc_ValueError, "Path instance wrapping a NULL path");
+        return NULL;
+    }
+
+    if (transfer != GI_TRANSFER_NOTHING)
+        path = _cairo_path_copy (path);
+
+    arg->v_pointer = path;
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+cairo_path_from_arg (GIInterfaceInfo *interface_info,
+                     GITransfer       transfer,
+                     gpointer         data)
+{
+    cairo_path_t *path = (cairo_path_t*) data;
+
+    if (transfer == GI_TRANSFER_NOTHING) {
+        PyErr_SetString(PyExc_TypeError, "Unsupported annotation (transfer none) for cairo.Path return");
+        return NULL;
+    }
+
+    return PycairoPath_FromPath (path);
+}
+
+static PyObject *
+cairo_path_release (GIBaseInfo *base_info,
+                    gpointer    struct_)
+{
+    cairo_path_destroy ( (cairo_path_t*) struct_);
+    Py_RETURN_NONE;
+}
+
+
+/*
+ * cairo_font_face_t marshaling
+ */
+
+static int
+cairo_font_face_to_gvalue (GValue *value, PyObject *obj)
+{
+    cairo_font_face_t *font_face;
+
+    if (!PyObject_TypeCheck (obj, &PycairoFontFace_Type)) {
+        PyErr_SetString (PyExc_TypeError, "Expected cairo.FontFace");
+        return -1;
+    }
+
+    font_face = ((PycairoFontFace*) obj)->font_face;
+    if (!font_face) {
+        return -1;
+    }
+
+    g_value_set_boxed (value, font_face);
+    return 0;
+}
+
+static PyObject *
+cairo_font_face_from_gvalue (const GValue *value)
+{
+    cairo_font_face_t *font_face = g_value_dup_boxed (value);
+    if (!font_face) {
+        return NULL;
+    }
+
+    return PycairoFontFace_FromFontFace (font_face);
+}
+
+
+/*
+ * cairo_font_options_t marshaling
+ */
+
+static PyObject *
+cairo_font_options_to_arg (PyObject        *value,
+                           GIInterfaceInfo *interface_info,
+                           GITransfer       transfer,
+                           GIArgument      *arg)
+{
+    cairo_font_options_t *font_options;
+
+    if (!PyObject_TypeCheck (value, &PycairoFontOptions_Type)) {
+        PyErr_SetString (PyExc_TypeError, "Expected cairo.FontOptions");
+        return NULL;
+    }
+
+    font_options = ( (PycairoFontOptions*) value)->font_options;
+    if (!font_options) {
+        PyErr_SetString (PyExc_ValueError, "FontOptions instance wrapping a NULL font_options");
+        return NULL;
+    }
+
+    if (transfer != GI_TRANSFER_NOTHING)
+        font_options = cairo_font_options_copy (font_options);
+
+    arg->v_pointer = font_options;
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+cairo_font_options_from_arg (GIInterfaceInfo *interface_info,
+                             GITransfer       transfer,
+                             gpointer         data)
+{
+    cairo_font_options_t *font_options = (cairo_font_options_t*) data;
+
+    if (transfer == GI_TRANSFER_NOTHING)
+        font_options = cairo_font_options_copy (font_options);
+
+    return PycairoFontOptions_FromFontOptions (font_options);
+}
+
+static PyObject *
+cairo_font_options_release (GIBaseInfo *base_info,
+                            gpointer    struct_)
+{
+    cairo_font_options_destroy ( (cairo_font_options_t*) struct_);
+    Py_RETURN_NONE;
+}
+
+
+/*
+ * scaled_font_t marshaling
+ */
+
+static int
+cairo_scaled_font_to_gvalue (GValue *value, PyObject *obj)
+{
+    cairo_scaled_font_t *scaled_font;
+
+    if (!PyObject_TypeCheck (obj, &PycairoScaledFont_Type)) {
+        PyErr_SetString (PyExc_TypeError, "Expected cairo.ScaledFont");
+        return -1;
+    }
+
+    scaled_font = ((PycairoScaledFont*) obj)->scaled_font;
+    if (!scaled_font) {
+        return -1;
+    }
+
+    /* scaled_font is a borrowed reference, use set_boxed
+     * to add new ref to the context which will be managed by the GValue. */
+    g_value_set_boxed (value, scaled_font);
+    return 0;
+}
+
+static PyObject *
+cairo_scaled_font_from_gvalue (const GValue *value)
+{
+    /* PycairoScaledFont_FromScaledFont steals a ref, so we dup it out of the GValue. */
+    cairo_scaled_font_t *scaled_font = g_value_dup_boxed (value);
+    if (!scaled_font) {
+        return NULL;
+    }
+
+    return PycairoScaledFont_FromScaledFont (scaled_font);
+}
+
+
+/*
+ * cairo_pattern_t marshaling
+ */
+
+static int
+cairo_pattern_to_gvalue (GValue *value, PyObject *obj)
+{
+    cairo_pattern_t *pattern;
+
+    if (!PyObject_TypeCheck (obj, &PycairoPattern_Type)) {
+        PyErr_SetString (PyExc_TypeError, "Expected cairo.Pattern");
+        return -1;
+    }
+
+    pattern = ((PycairoPattern*) obj)->pattern;
+    if (!pattern) {
+        return -1;
+    }
+
+    /* pattern is a borrowed reference, use set_boxed
+     * to add new ref to the context which will be managed by the GValue. */
+    g_value_set_boxed (value, pattern);
+    return 0;
+}
+
+static PyObject *
+cairo_pattern_from_gvalue (const GValue *value)
+{
+    /* PycairoPattern_FromPattern steals a ref, so we dup it out of the GValue. */
+    cairo_pattern_t *pattern = g_value_dup_boxed (value);
+    if (!pattern) {
+        return NULL;
+    }
+
+    return PycairoPattern_FromPattern (pattern, NULL);
+}
+
+static PyObject *
+cairo_region_to_arg (PyObject        *value,
+                     GIInterfaceInfo *interface_info,
+                     GITransfer       transfer,
+                     GIArgument      *arg)
+{
+    cairo_region_t *region;
+
+    if (!PyObject_TypeCheck (value, &PycairoRegion_Type)) {
+        PyErr_SetString (PyExc_TypeError, "Expected cairo.Region");
+        return NULL;
+    }
+
+    region = ( (PycairoRegion*) value)->region;
+    if (!region) {
+        PyErr_SetString (PyExc_ValueError, "Region instance wrapping a NULL region");
+        return NULL;
+    }
+
+    if (transfer != GI_TRANSFER_NOTHING)
+        region = cairo_region_copy (region);
+
+    arg->v_pointer = region;
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+cairo_region_from_arg (GIInterfaceInfo *interface_info,
+                       GITransfer transfer,
+                       gpointer data)
+{
+    cairo_region_t *region = (cairo_region_t*) data;
+
+    if (transfer == GI_TRANSFER_NOTHING)
+        cairo_region_reference (region);
+
+    return PycairoRegion_FromRegion (region);
+}
+
+static PyObject *
+cairo_region_release (GIBaseInfo *base_info,
+                      gpointer    struct_)
+{
+    cairo_region_destroy ( (cairo_region_t*) struct_);
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+cairo_matrix_from_arg (GIInterfaceInfo *interface_info,
+                       GITransfer transfer,
+                       gpointer data)
+{
+    cairo_matrix_t *matrix = (cairo_matrix_t*) data;
+
+    if (transfer != GI_TRANSFER_NOTHING) {
+        PyErr_SetString(PyExc_TypeError, "Unsupported annotation (transfer full) for cairo.Matrix");
+        return NULL;
+    }
+
+    if (matrix == NULL) {
+        /* NULL in case of caller-allocates */
+        cairo_matrix_t temp = {0};
+        return PycairoMatrix_FromMatrix (&temp);
+    }
+
+    return PycairoMatrix_FromMatrix (matrix);
+}
+
+static PyObject *
+cairo_matrix_to_arg (PyObject        *value,
+                     GIInterfaceInfo *interface_info,
+                     GITransfer       transfer,
+                     GIArgument      *arg)
+{
+    cairo_matrix_t *matrix;
+
+    if (!PyObject_TypeCheck (value, &PycairoMatrix_Type)) {
+        PyErr_SetString (PyExc_TypeError, "Expected cairo.Matrix");
+        return NULL;
+    }
+
+    matrix = &(( (PycairoMatrix*) value)->matrix);
+
+    arg->v_pointer = matrix;
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+cairo_matrix_release (GIBaseInfo *base_info,
+                      gpointer    struct_)
+{
+    Py_RETURN_NONE;
+}
+
+static PyMethodDef _gi_cairo_functions[] = { {0,} };
+PYGLIB_MODULE_START(_gi_cairo, "_gi_cairo")
+{
+#if PY_VERSION_HEX < 0x03000000
+    Pycairo_IMPORT;
+#else
+    import_cairo();
+#endif
+
+    if (Pycairo_CAPI == NULL)
+        return PYGLIB_MODULE_ERROR_RETURN;
+
+    pygobject_init (3, 13, 2);
+
+    pygi_register_foreign_struct ("cairo",
+                                  "Matrix",
+                                  cairo_matrix_to_arg,
+                                  cairo_matrix_from_arg,
+                                  cairo_matrix_release);
+
+    pygi_register_foreign_struct ("cairo",
+                                  "Context",
+                                  cairo_context_to_arg,
+                                  cairo_context_from_arg,
+                                  cairo_context_release);
+
+    pygi_register_foreign_struct ("cairo",
+                                  "Surface",
+                                  cairo_surface_to_arg,
+                                  cairo_surface_from_arg,
+                                  cairo_surface_release);
+
+    pygi_register_foreign_struct ("cairo",
+                                  "Path",
+                                  cairo_path_to_arg,
+                                  cairo_path_from_arg,
+                                  cairo_path_release);
+
+    pygi_register_foreign_struct ("cairo",
+                                  "FontOptions",
+                                  cairo_font_options_to_arg,
+                                  cairo_font_options_from_arg,
+                                  cairo_font_options_release);
+
+    pygi_register_foreign_struct ("cairo",
+                                  "Region",
+                                  cairo_region_to_arg,
+                                  cairo_region_from_arg,
+                                  cairo_region_release);
+
+    pyg_register_gtype_custom (CAIRO_GOBJECT_TYPE_CONTEXT,
+                               cairo_context_from_gvalue,
+                               cairo_context_to_gvalue);
+
+    pyg_register_gtype_custom (CAIRO_GOBJECT_TYPE_SURFACE,
+                               cairo_surface_from_gvalue,
+                               cairo_surface_to_gvalue);
+
+    pyg_register_gtype_custom (CAIRO_GOBJECT_TYPE_FONT_FACE,
+                               cairo_font_face_from_gvalue,
+                               cairo_font_face_to_gvalue);
+
+    pyg_register_gtype_custom (CAIRO_GOBJECT_TYPE_SCALED_FONT,
+                               cairo_scaled_font_from_gvalue,
+                               cairo_scaled_font_to_gvalue);
+
+    pyg_register_gtype_custom (CAIRO_GOBJECT_TYPE_PATTERN,
+                               cairo_pattern_from_gvalue,
+                               cairo_pattern_to_gvalue);
+
+}
+PYGLIB_MODULE_END;
diff --git a/gi/pygi-foreign.c b/gi/pygi-foreign.c
new file mode 100644 (file)
index 0000000..7db28fd
--- /dev/null
@@ -0,0 +1,219 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2010  litl, LLC
+ * Copyright (c) 2010  Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <config.h>
+
+#include "pygobject-internal.h"
+#include "pygi-foreign.h"
+
+#include <girepository.h>
+
+typedef struct {
+    const char *namespace;
+    const char *name;
+    PyGIArgOverrideToGIArgumentFunc to_func;
+    PyGIArgOverrideFromGIArgumentFunc from_func;
+    PyGIArgOverrideReleaseFunc release_func;
+} PyGIForeignStruct;
+
+static GPtrArray *foreign_structs = NULL;
+
+static void
+init_foreign_structs (void)
+{
+    foreign_structs = g_ptr_array_new ();
+}
+
+static PyGIForeignStruct *
+do_lookup (const gchar *namespace, const gchar *name)
+{
+    guint i;
+    for (i = 0; i < foreign_structs->len; i++) {
+        PyGIForeignStruct *foreign_struct = \
+                g_ptr_array_index (foreign_structs, i);
+
+        if ( (strcmp (namespace, foreign_struct->namespace) == 0) &&
+                (strcmp (name, foreign_struct->name) == 0)) {
+            return foreign_struct;
+        }
+    }
+    return NULL;
+}
+
+static PyObject *
+pygi_struct_foreign_load_module (const char *namespace)
+{
+    gchar *module_name = g_strconcat ("gi._gi_", namespace, NULL);
+    PyObject *module = PyImport_ImportModule (module_name);
+    g_free (module_name);
+    return module;
+}
+
+static PyGIForeignStruct *
+pygi_struct_foreign_lookup_by_name (const char *namespace, const char *name)
+{
+    PyGIForeignStruct *result;
+
+    result = do_lookup (namespace, name);
+
+    if (result == NULL) {
+        PyObject *module = pygi_struct_foreign_load_module (namespace);
+
+        if (module == NULL)
+            PyErr_Clear ();
+        else {
+            Py_DECREF (module);
+            result = do_lookup (namespace, name);
+        }
+    }
+
+    if (result == NULL) {
+        PyErr_Format (PyExc_TypeError,
+                      "Couldn't find foreign struct converter for '%s.%s'",
+                      namespace,
+                      name);
+    }
+
+    return result;
+}
+
+static PyGIForeignStruct *
+pygi_struct_foreign_lookup (GIBaseInfo *base_info)
+{
+    const gchar *namespace = g_base_info_get_namespace (base_info);
+    const gchar *name = g_base_info_get_name (base_info);
+
+    return pygi_struct_foreign_lookup_by_name (namespace, name);
+}
+
+PyObject *
+pygi_struct_foreign_convert_to_g_argument (PyObject        *value,
+                                           GIInterfaceInfo *interface_info,
+                                           GITransfer       transfer,
+                                           GIArgument      *arg)
+{
+    PyObject *result;
+
+    GIBaseInfo *base_info = (GIBaseInfo *) interface_info;
+    PyGIForeignStruct *foreign_struct = pygi_struct_foreign_lookup (base_info);
+
+    if (foreign_struct == NULL) {
+        PyErr_Format(PyExc_KeyError, "could not find foreign type %s",
+                     g_base_info_get_name (base_info));
+        return FALSE;
+    }
+
+    result = foreign_struct->to_func (value, interface_info, transfer, arg);
+    return result;
+}
+
+PyObject *
+pygi_struct_foreign_convert_from_g_argument (GIInterfaceInfo *interface_info,
+                                             GITransfer       transfer,
+                                             GIArgument      *arg)
+{
+    GIBaseInfo *base_info = (GIBaseInfo *) interface_info;
+    PyGIForeignStruct *foreign_struct = pygi_struct_foreign_lookup (base_info);
+
+    if (foreign_struct == NULL)
+        return NULL;
+
+    return foreign_struct->from_func (interface_info, transfer, arg);
+}
+
+PyObject *
+pygi_struct_foreign_release (GIBaseInfo *base_info,
+                             gpointer    struct_)
+{
+    PyGIForeignStruct *foreign_struct = pygi_struct_foreign_lookup (base_info);
+
+    if (foreign_struct == NULL)
+        return NULL;
+
+    if (!foreign_struct->release_func)
+        Py_RETURN_NONE;
+
+    return foreign_struct->release_func (base_info, struct_);
+}
+
+void
+pygi_register_foreign_struct (const char* namespace_,
+                              const char* name,
+                              PyGIArgOverrideToGIArgumentFunc to_func,
+                              PyGIArgOverrideFromGIArgumentFunc from_func,
+                              PyGIArgOverrideReleaseFunc release_func)
+{
+    PyGIForeignStruct *new_struct = g_slice_new (PyGIForeignStruct);
+    new_struct->namespace = namespace_;
+    new_struct->name = name;
+    new_struct->to_func = to_func;
+    new_struct->from_func = from_func;
+    new_struct->release_func = release_func;
+
+    g_ptr_array_add (foreign_structs, new_struct);
+}
+
+PyObject *
+pygi_require_foreign (PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { "namespace", "symbol", NULL };
+    const char *namespace = NULL;
+    const char *symbol = NULL;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "s|z:require_foreign",
+                                      kwlist, &namespace, &symbol)) {
+        return NULL;
+    }
+
+    if (symbol) {
+        PyGIForeignStruct *foreign;
+        foreign = pygi_struct_foreign_lookup_by_name (namespace, symbol);
+        if (foreign == NULL) {
+            return NULL;
+        }
+    } else {
+        PyObject *module = pygi_struct_foreign_load_module (namespace);
+        if (module) {
+            Py_DECREF (module);
+        } else {
+            return NULL;
+        }
+    }
+
+    Py_RETURN_NONE;
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_foreign_init (void)
+{
+    if (foreign_structs == NULL) {
+        init_foreign_structs ();
+    }
+
+    return 0;
+}
diff --git a/gi/pygi-foreign.h b/gi/pygi-foreign.h
new file mode 100644 (file)
index 0000000..c155e4f
--- /dev/null
@@ -0,0 +1,53 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2010  litl, LLC
+ * Copyright (c) 2010  Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef __PYGI_FOREIGN_H__
+#define __PYGI_FOREIGN_H__
+
+#include <Python.h>
+#include "pygi-foreign-api.h"
+
+PyObject *pygi_struct_foreign_convert_to_g_argument (PyObject           *value,
+                                                     GIInterfaceInfo    *interface_info,
+                                                     GITransfer          transfer,
+                                                     GIArgument         *arg);
+PyObject *pygi_struct_foreign_convert_from_g_argument (GIInterfaceInfo *interface_info,
+                                                       GITransfer       transfer,
+                                                       GIArgument      *arg);
+PyObject *pygi_struct_foreign_release (GITypeInfo *type_info,
+                                       gpointer struct_);
+
+void pygi_register_foreign_struct (const char* namespace_,
+                                   const char* name,
+                                   PyGIArgOverrideToGIArgumentFunc to_func,
+                                   PyGIArgOverrideFromGIArgumentFunc from_func,
+                                   PyGIArgOverrideReleaseFunc release_func);
+
+PyObject *pygi_require_foreign    (PyObject *self,
+                                   PyObject *args,
+                                   PyObject *kwargs);
+
+int pygi_foreign_init (void);
+
+#endif /* __PYGI_FOREIGN_H__ */
diff --git a/gi/pygi-hashtable.c b/gi/pygi-hashtable.c
new file mode 100644 (file)
index 0000000..285eca1
--- /dev/null
@@ -0,0 +1,422 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include "pygi-hashtable.h"
+#include "pygi-argument.h"
+#include "pygi-util.h"
+
+typedef struct _PyGIHashCache
+{
+    PyGIArgCache arg_cache;
+    PyGIArgCache *key_cache;
+    PyGIArgCache *value_cache;
+} PyGIHashCache;
+
+
+static void
+_hash_cache_free_func (PyGIHashCache *cache)
+{
+    if (cache != NULL) {
+        pygi_arg_cache_free (cache->key_cache);
+        pygi_arg_cache_free (cache->value_cache);
+        g_slice_free (PyGIHashCache, cache);
+    }
+}
+
+static gboolean
+_pygi_marshal_from_py_ghash (PyGIInvokeState   *state,
+                             PyGICallableCache *callable_cache,
+                             PyGIArgCache      *arg_cache,
+                             PyObject          *py_arg,
+                             GIArgument        *arg,
+                             gpointer          *cleanup_data)
+{
+    PyGIMarshalFromPyFunc key_from_py_marshaller;
+    PyGIMarshalFromPyFunc value_from_py_marshaller;
+
+    int i;
+    Py_ssize_t length;
+    PyObject *py_keys, *py_values;
+
+    GHashFunc hash_func;
+    GEqualFunc equal_func;
+
+    GHashTable *hash_ = NULL;
+    PyGIHashCache *hash_cache = (PyGIHashCache *)arg_cache;
+
+    if (py_arg == Py_None) {
+        arg->v_pointer = NULL;
+        return TRUE;
+    }
+
+    py_keys = PyMapping_Keys (py_arg);
+    if (py_keys == NULL) {
+        PyErr_Format (PyExc_TypeError, "Must be mapping, not %s",
+                      Py_TYPE (py_arg)->tp_name);
+        return FALSE;
+    }
+
+    length = PyMapping_Length (py_arg);
+    if (length < 0) {
+        Py_DECREF (py_keys);
+        return FALSE;
+    }
+
+    py_values = PyMapping_Values (py_arg);
+    if (py_values == NULL) {
+        Py_DECREF (py_keys);
+        return FALSE;
+    }
+
+    key_from_py_marshaller = hash_cache->key_cache->from_py_marshaller;
+    value_from_py_marshaller = hash_cache->value_cache->from_py_marshaller;
+
+    switch (hash_cache->key_cache->type_tag) {
+        case GI_TYPE_TAG_UTF8:
+        case GI_TYPE_TAG_FILENAME:
+            hash_func = g_str_hash;
+            equal_func = g_str_equal;
+            break;
+        default:
+            hash_func = NULL;
+            equal_func = NULL;
+    }
+
+    hash_ = g_hash_table_new (hash_func, equal_func);
+    if (hash_ == NULL) {
+        PyErr_NoMemory ();
+        Py_DECREF (py_keys);
+        Py_DECREF (py_values);
+        return FALSE;
+    }
+
+    for (i = 0; i < length; i++) {
+        GIArgument key, value;
+        gpointer key_cleanup_data = NULL;
+        gpointer value_cleanup_data = NULL;
+        PyObject *py_key = PyList_GET_ITEM (py_keys, i);
+        PyObject *py_value = PyList_GET_ITEM (py_values, i);
+        if (py_key == NULL || py_value == NULL)
+            goto err;
+
+        if (!key_from_py_marshaller ( state,
+                                      callable_cache,
+                                      hash_cache->key_cache,
+                                      py_key,
+                                     &key,
+                                     &key_cleanup_data))
+            goto err;
+
+        if (!value_from_py_marshaller ( state,
+                                        callable_cache,
+                                        hash_cache->value_cache,
+                                        py_value,
+                                       &value,
+                                       &value_cleanup_data))
+            goto err;
+
+        g_hash_table_insert (hash_,
+                             _pygi_arg_to_hash_pointer (&key, hash_cache->key_cache->type_info),
+                             _pygi_arg_to_hash_pointer (&value, hash_cache->value_cache->type_info));
+        continue;
+err:
+        /* FIXME: cleanup hash keys and values */
+        Py_XDECREF (py_key);
+        Py_XDECREF (py_value);
+        Py_DECREF (py_keys);
+        Py_DECREF (py_values);
+        g_hash_table_unref (hash_);
+        _PyGI_ERROR_PREFIX ("Item %i: ", i);
+        return FALSE;
+    }
+
+    arg->v_pointer = hash_;
+
+    if (arg_cache->transfer == GI_TRANSFER_NOTHING) {
+        /* Free everything in cleanup. */
+        *cleanup_data = arg->v_pointer;
+    } else if (arg_cache->transfer == GI_TRANSFER_CONTAINER) {
+        /* Make a shallow copy so we can free the elements later in cleanup
+         * because it is possible invoke will free the list before our cleanup. */
+        *cleanup_data = g_hash_table_ref (arg->v_pointer);
+    } else { /* GI_TRANSFER_EVERYTHING */
+        /* No cleanup, everything is given to the callee.
+         * Note that the keys and values will leak for transfer everything because
+         * we do not use g_hash_table_new_full and set key/value_destroy_func. */
+        *cleanup_data = NULL;
+    }
+
+    return TRUE;
+}
+
+static void
+_pygi_marshal_cleanup_from_py_ghash  (PyGIInvokeState *state,
+                                      PyGIArgCache    *arg_cache,
+                                      PyObject        *py_arg,
+                                      gpointer         data,
+                                      gboolean         was_processed)
+{
+    if (data == NULL)
+        return;
+
+    if (was_processed) {
+        GHashTable *hash_;
+        PyGIHashCache *hash_cache = (PyGIHashCache *)arg_cache;
+
+        hash_ = (GHashTable *)data;
+
+        /* clean up keys and values first */
+        if (hash_cache->key_cache->from_py_cleanup != NULL ||
+                hash_cache->value_cache->from_py_cleanup != NULL) {
+            GHashTableIter hiter;
+            gpointer key;
+            gpointer value;
+
+            PyGIMarshalCleanupFunc key_cleanup_func =
+                hash_cache->key_cache->from_py_cleanup;
+            PyGIMarshalCleanupFunc value_cleanup_func =
+                hash_cache->value_cache->from_py_cleanup;
+
+            g_hash_table_iter_init (&hiter, hash_);
+            while (g_hash_table_iter_next (&hiter, &key, &value)) {
+                if (key != NULL && key_cleanup_func != NULL)
+                    key_cleanup_func (state,
+                                      hash_cache->key_cache,
+                                      NULL,
+                                      key,
+                                      TRUE);
+                if (value != NULL && value_cleanup_func != NULL)
+                    value_cleanup_func (state,
+                                        hash_cache->value_cache,
+                                        NULL,
+                                        value,
+                                        TRUE);
+            }
+        }
+
+        g_hash_table_unref (hash_);
+    }
+}
+
+static PyObject *
+_pygi_marshal_to_py_ghash (PyGIInvokeState   *state,
+                           PyGICallableCache *callable_cache,
+                           PyGIArgCache      *arg_cache,
+                           GIArgument        *arg,
+                           gpointer          *cleanup_data)
+{
+    GHashTable *hash_;
+    GHashTableIter hash_table_iter;
+
+    PyGIMarshalToPyFunc key_to_py_marshaller;
+    PyGIMarshalToPyFunc value_to_py_marshaller;
+
+    PyGIArgCache *key_arg_cache;
+    PyGIArgCache *value_arg_cache;
+    PyGIHashCache *hash_cache = (PyGIHashCache *)arg_cache;
+
+    GIArgument key_arg;
+    GIArgument value_arg;
+
+    PyObject *py_obj = NULL;
+
+    hash_ = arg->v_pointer;
+
+    if (hash_ == NULL) {
+        py_obj = Py_None;
+        Py_INCREF (py_obj);
+        return py_obj;
+    }
+
+    py_obj = PyDict_New ();
+    if (py_obj == NULL)
+        return NULL;
+
+    key_arg_cache = hash_cache->key_cache;
+    key_to_py_marshaller = key_arg_cache->to_py_marshaller;
+
+    value_arg_cache = hash_cache->value_cache;
+    value_to_py_marshaller = value_arg_cache->to_py_marshaller;
+
+    g_hash_table_iter_init (&hash_table_iter, hash_);
+    while (g_hash_table_iter_next (&hash_table_iter,
+                                   &key_arg.v_pointer,
+                                   &value_arg.v_pointer)) {
+        gpointer key_cleanup_data = NULL;
+        gpointer value_cleanup_data = NULL;
+        PyObject *py_key;
+        PyObject *py_value;
+        int retval;
+
+
+        _pygi_hash_pointer_to_arg (&key_arg, hash_cache->key_cache->type_info);
+        py_key = key_to_py_marshaller ( state,
+                                      callable_cache,
+                                      key_arg_cache,
+                                     &key_arg,
+                                     &key_cleanup_data);
+
+        if (py_key == NULL) {
+            Py_CLEAR (py_obj);
+            return NULL;
+        }
+
+        _pygi_hash_pointer_to_arg (&value_arg, hash_cache->value_cache->type_info);
+        py_value = value_to_py_marshaller ( state,
+                                          callable_cache,
+                                          value_arg_cache,
+                                         &value_arg,
+                                         &value_cleanup_data);
+
+        if (py_value == NULL) {
+            Py_CLEAR (py_obj);
+            Py_DECREF(py_key);
+            return NULL;
+        }
+
+        retval = PyDict_SetItem (py_obj, py_key, py_value);
+
+        Py_DECREF (py_key);
+        Py_DECREF (py_value);
+
+        if (retval < 0) {
+            Py_CLEAR (py_obj);
+            return NULL;
+        }
+    }
+
+    return py_obj;
+}
+
+static void
+_pygi_marshal_cleanup_to_py_ghash (PyGIInvokeState *state,
+                                   PyGIArgCache    *arg_cache,
+                                   gpointer         cleanup_data,
+                                   gpointer         data,
+                                   gboolean         was_processed)
+{
+    if (data == NULL)
+        return;
+
+    /* assume hashtable has boxed key and value */
+    if (arg_cache->transfer == GI_TRANSFER_EVERYTHING || arg_cache->transfer == GI_TRANSFER_CONTAINER)
+        g_hash_table_unref ( (GHashTable *)data);
+}
+
+static void
+_arg_cache_from_py_ghash_setup (PyGIArgCache *arg_cache)
+{
+    arg_cache->from_py_marshaller = _pygi_marshal_from_py_ghash;
+    arg_cache->from_py_cleanup = _pygi_marshal_cleanup_from_py_ghash;
+}
+
+static void
+_arg_cache_to_py_ghash_setup (PyGIArgCache *arg_cache)
+{
+    arg_cache->to_py_marshaller = _pygi_marshal_to_py_ghash;
+    arg_cache->to_py_cleanup = _pygi_marshal_cleanup_to_py_ghash;
+}
+
+static gboolean
+pygi_arg_hash_table_setup_from_info (PyGIHashCache      *hc,
+                                     GITypeInfo         *type_info,
+                                     GIArgInfo          *arg_info,
+                                     GITransfer          transfer,
+                                     PyGIDirection       direction,
+                                     PyGICallableCache  *callable_cache)
+{
+    GITypeInfo *key_type_info;
+    GITypeInfo *value_type_info;
+    GITransfer item_transfer;
+
+    if (!pygi_arg_base_setup ((PyGIArgCache *)hc, type_info, arg_info, transfer, direction))
+        return FALSE;
+
+    ( (PyGIArgCache *)hc)->destroy_notify = (GDestroyNotify)_hash_cache_free_func;
+    key_type_info = g_type_info_get_param_type (type_info, 0);
+    value_type_info = g_type_info_get_param_type (type_info, 1);
+
+    item_transfer =
+        transfer == GI_TRANSFER_CONTAINER ? GI_TRANSFER_NOTHING : transfer;
+
+    hc->key_cache = pygi_arg_cache_new (key_type_info,
+                                        NULL,
+                                        item_transfer,
+                                        direction,
+                                        callable_cache,
+                                        0, 0);
+
+    if (hc->key_cache == NULL) {
+        return FALSE;
+    }
+
+    hc->value_cache = pygi_arg_cache_new (value_type_info,
+                                          NULL,
+                                          item_transfer,
+                                          direction,
+                                          callable_cache,
+                                          0, 0);
+
+    if (hc->value_cache == NULL) {
+        return FALSE;
+    }
+
+    g_base_info_unref( (GIBaseInfo *)key_type_info);
+    g_base_info_unref( (GIBaseInfo *)value_type_info);
+
+    if (direction & PYGI_DIRECTION_FROM_PYTHON) {
+        _arg_cache_from_py_ghash_setup ((PyGIArgCache *)hc);
+    }
+
+    if (direction & PYGI_DIRECTION_TO_PYTHON) {
+        _arg_cache_to_py_ghash_setup ((PyGIArgCache *)hc);
+    }
+
+    return TRUE;
+}
+
+PyGIArgCache *
+pygi_arg_hash_table_new_from_info (GITypeInfo         *type_info,
+                                   GIArgInfo          *arg_info,
+                                   GITransfer          transfer,
+                                   PyGIDirection       direction,
+                                   PyGICallableCache  *callable_cache)
+{
+    gboolean res = FALSE;
+    PyGIHashCache *hc = NULL;
+
+    hc = g_slice_new0 (PyGIHashCache);
+    if (hc == NULL)
+        return NULL;
+
+    res = pygi_arg_hash_table_setup_from_info (hc,
+                                               type_info,
+                                               arg_info,
+                                               transfer,
+                                               direction,
+                                               callable_cache);
+    if (res) {
+        return (PyGIArgCache *)hc;
+    } else {
+        pygi_arg_cache_free ((PyGIArgCache *)hc);
+        return NULL;
+    }
+}
diff --git a/gi/pygi-hashtable.h b/gi/pygi-hashtable.h
new file mode 100644 (file)
index 0000000..74cd04f
--- /dev/null
@@ -0,0 +1,36 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2013 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_HASHTABLE_H__
+#define __PYGI_HASHTABLE_H__
+
+#include "pygi-cache.h"
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+PyGIArgCache *pygi_arg_hash_table_new_from_info (GITypeInfo         *type_info,
+                                                 GIArgInfo          *arg_info,   /* may be null */
+                                                 GITransfer          transfer,
+                                                 PyGIDirection       direction,
+                                                 PyGICallableCache  *callable_cache);
+
+G_END_DECLS
+
+#endif /*__PYGI_HASHTABLE_H__*/
diff --git a/gi/pygi-info.c b/gi/pygi-info.c
new file mode 100644 (file)
index 0000000..4fca825
--- /dev/null
@@ -0,0 +1,2506 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+ * Copyright (C) 2013 Simon Feltman <sfeltman@gnome.org>
+ *
+ *   pygi-info.c: GI.*Info wrappers.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pygi-python-compat.h"
+#include "pygi-info.h"
+#include "pygi-cache.h"
+#include "pygi-invoke.h"
+#include "pygi-type.h"
+#include "pygi-argument.h"
+#include "pygi-util.h"
+#include "pygi-basictype.h"
+#include "pygi-type.h"
+
+/* _generate_doc_string
+ *
+ * C wrapper to call Python implemented "gi.docstring.generate_doc_string"
+ */
+static PyObject *
+_generate_doc_string(PyGIBaseInfo *self)
+{
+    static PyObject *_py_generate_doc_string = NULL;
+
+    if (_py_generate_doc_string == NULL) {
+        PyObject *mod = pygi_import_module ("gi.docstring");
+        if (!mod)
+            return NULL;
+
+        _py_generate_doc_string = PyObject_GetAttrString (mod, "generate_doc_string");
+        if (_py_generate_doc_string == NULL) {
+            Py_DECREF (mod);
+            return NULL;
+        }
+        Py_DECREF (mod);
+    }
+
+    return PyObject_CallFunctionObjArgs (_py_generate_doc_string, self, NULL);
+}
+
+static PyObject *
+_get_info_string (PyGIBaseInfo *self,
+                  const gchar* (*get_info_string)(GIBaseInfo*))
+{
+    const gchar *value = get_info_string ((GIBaseInfo*)self->info);
+    if (value == NULL) {
+        Py_RETURN_NONE;
+    }
+    return pygi_utf8_to_py (value);
+}
+
+static PyObject *
+_get_child_info (PyGIBaseInfo *self,
+                 GIBaseInfo* (*get_child_info)(GIBaseInfo*))
+{
+    GIBaseInfo *info;
+    PyObject *py_info;
+
+    info = get_child_info ((GIBaseInfo*)self->info);
+    if (info == NULL) {
+        Py_RETURN_NONE;
+    }
+
+    py_info = _pygi_info_new (info);
+    g_base_info_unref (info);
+    return py_info;
+}
+
+
+static PyObject *
+_get_child_info_by_name (PyGIBaseInfo *self, PyObject *py_name,
+                         GIBaseInfo* (*get_child_info_by_name)(GIBaseInfo*, const gchar*))
+{
+    GIBaseInfo *info;
+    PyObject *py_info;
+    char *name;
+
+    if (!pygi_utf8_from_py (py_name, &name))
+        return NULL;
+
+    info = get_child_info_by_name ((GIObjectInfo*)self->info, name);
+    g_free (name);
+    if (info == NULL) {
+        Py_RETURN_NONE;
+    }
+
+    py_info = _pygi_info_new (info);
+    g_base_info_unref (info);
+    return py_info;
+}
+
+
+/* _make_infos_tuple
+ *
+ * Build a tuple from the common API pattern in GI of having a
+ * function which returns a count and an indexed GIBaseInfo
+ * in the range of 0 to count;
+ */
+static PyObject *
+_make_infos_tuple (PyGIBaseInfo *self,
+                   gint (*get_n_infos)(GIBaseInfo*),
+                   GIBaseInfo* (*get_info)(GIBaseInfo*, gint))
+{
+    gint n_infos;
+    PyObject *infos;
+    gint i;
+
+    n_infos = get_n_infos ( (GIBaseInfo *) self->info);
+
+    infos = PyTuple_New (n_infos);
+    if (infos == NULL) {
+        return NULL;
+    }
+
+    for (i = 0; i < n_infos; i++) {
+        GIBaseInfo *info;
+        PyObject *py_info;
+
+        info = (GIBaseInfo *) get_info (self->info, i);
+        g_assert (info != NULL);
+
+        py_info = _pygi_info_new (info);
+
+        g_base_info_unref (info);
+
+        if (py_info == NULL) {
+            Py_CLEAR (infos);
+            break;
+        }
+
+        PyTuple_SET_ITEM (infos, i, py_info);
+    }
+
+    return infos;
+}
+
+
+/* BaseInfo */
+
+/* We need to be careful about calling g_base_info_get_name because
+ * calling it with a GI_INFO_TYPE_TYPE will crash.
+ * See: https://bugzilla.gnome.org/show_bug.cgi?id=709456
+ */
+static const char *
+_safe_base_info_get_name (GIBaseInfo *info)
+{
+    if (g_base_info_get_type (info) == GI_INFO_TYPE_TYPE) {
+        return "type_type_instance";
+    } else {
+        return g_base_info_get_name (info);
+    }
+}
+
+static void
+_base_info_dealloc (PyGIBaseInfo *self)
+{
+    if (self->inst_weakreflist != NULL)
+        PyObject_ClearWeakRefs ( (PyObject *) self);
+
+    g_base_info_unref (self->info);
+
+    if (self->cache != NULL)
+        pygi_callable_cache_free ( (PyGICallableCache *) self->cache);
+
+    Py_TYPE (self)->tp_free ((PyObject *)self);
+}
+
+static PyObject *
+_base_info_repr (PyGIBaseInfo *self)
+{
+
+    return PYGLIB_PyUnicode_FromFormat ("%s(%s)",
+                                        Py_TYPE( (PyObject *) self)->tp_name,
+                                        _safe_base_info_get_name (self->info));
+}
+
+static PyObject *
+_wrap_g_base_info_equal (PyGIBaseInfo *self, PyObject *other)
+{
+    GIBaseInfo *other_info;
+
+    if (!PyObject_TypeCheck (other, &PyGIBaseInfo_Type)) {
+        Py_INCREF (Py_NotImplemented);
+        return Py_NotImplemented;
+    }
+
+    other_info = ((PyGIBaseInfo *)other)->info;
+    if (g_base_info_equal (self->info, other_info)) {
+        Py_RETURN_TRUE;
+    } else {
+        Py_RETURN_FALSE;
+    }
+}
+
+static PyObject *
+_base_info_richcompare (PyGIBaseInfo *self, PyObject *other, int op)
+{
+    PyObject *res;
+
+    switch (op) {
+        case Py_EQ:
+            return _wrap_g_base_info_equal (self, other);
+        case Py_NE:
+            res = _wrap_g_base_info_equal (self, other);
+            if (res == Py_True) {
+                Py_DECREF (res);
+                Py_RETURN_FALSE;
+            } else {
+                Py_DECREF (res);
+                Py_RETURN_TRUE;
+            }
+        default:
+            res = Py_NotImplemented;
+            break;
+    }
+    Py_INCREF(res);
+    return res;
+}
+
+PYGLIB_DEFINE_TYPE("gi.BaseInfo", PyGIBaseInfo_Type, PyGIBaseInfo);
+
+gboolean
+_pygi_is_python_keyword (const gchar *name)
+{
+    /* It may be better to use keyword.iskeyword(); keep in sync with
+     * python -c 'import keyword; print(keyword.kwlist)' */
+#if PY_VERSION_HEX < 0x03000000
+    /* Python 2.x */
+    static const gchar* keywords[] = {"and", "as", "assert", "break", "class",
+        "continue", "def", "del", "elif", "else", "except", "exec", "finally",
+        "for", "from", "global", "if", "import", "in", "is", "lambda", "not",
+        "or", "pass", "print", "raise", "return", "try", "while", "with",
+        "yield", NULL};
+#elif PY_VERSION_HEX < 0x04000000
+    /* Python 3.x; note that we explicitly keep "print"; it is not a keyword
+     * any more, but we do not want to break API between Python versions */
+    static const gchar* keywords[] = {"False", "None", "True", "and", "as",
+        "assert", "break", "class", "continue", "def", "del", "elif", "else",
+        "except", "finally", "for", "from", "global", "if", "import", "in",
+        "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return",
+        "try", "while", "with", "yield",
+        "print", NULL};
+#else
+    #error Need keyword list for this major Python version
+#endif
+
+    const gchar **i;
+
+    for (i = keywords; *i != NULL; ++i) {
+        if (strcmp (name, *i) == 0) {
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+static PyObject *
+_wrap_g_base_info_get_type (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (g_base_info_get_type (self->info));
+}
+
+static PyObject *
+_wrap_g_base_info_get_name (PyGIBaseInfo *self)
+{
+    const gchar *name;
+
+    name = _safe_base_info_get_name (self->info);
+
+    /* escape keywords */
+    if (_pygi_is_python_keyword (name)) {
+        gchar *escaped = g_strconcat (name, "_", NULL);
+        PyObject *obj = pygi_utf8_to_py (escaped);
+        g_free (escaped);
+        return obj;
+    }
+
+    return pygi_utf8_to_py (name);
+}
+
+static PyObject *
+_wrap_g_base_info_get_name_unescaped (PyGIBaseInfo *self)
+{
+    return _get_info_string (self, _safe_base_info_get_name);
+}
+
+static PyObject *
+_wrap_g_base_info_get_namespace (PyGIBaseInfo *self)
+{
+    return _get_info_string (self, g_base_info_get_namespace);
+}
+
+static PyObject *
+_wrap_g_base_info_is_deprecated (PyGIBaseInfo *self)
+{
+    if (g_base_info_is_deprecated (self->info))
+        Py_RETURN_TRUE;
+    else
+        Py_RETURN_FALSE;
+}
+
+static PyObject *
+_wrap_g_base_info_get_attribute (PyGIBaseInfo *self, PyObject *arg)
+{
+    char *name;
+    const char *value;
+
+    if (!pygi_utf8_from_py (arg, &name))
+        return NULL;
+
+    value = g_base_info_get_attribute (self->info, name);
+    g_free (name);
+    if (value == NULL) {
+        Py_RETURN_NONE;
+    }
+    return pygi_utf8_to_py (value);
+}
+
+static PyObject *
+_wrap_g_base_info_get_container (PyGIBaseInfo *self)
+{
+    /* Note: don't use _get_child_info because g_base_info_get_container
+     * is marked as [transfer none] and therefore returns a borrowed ref.
+     */
+    GIBaseInfo *info;
+
+    info = g_base_info_get_container (self->info);
+
+    if (info == NULL) {
+        Py_RETURN_NONE;
+    }
+
+    return _pygi_info_new (info);
+}
+
+
+static PyMethodDef _PyGIBaseInfo_methods[] = {
+    { "get_type", (PyCFunction) _wrap_g_base_info_get_type, METH_NOARGS },
+    { "get_name", (PyCFunction) _wrap_g_base_info_get_name, METH_NOARGS },
+    { "get_name_unescaped", (PyCFunction) _wrap_g_base_info_get_name_unescaped, METH_NOARGS },
+    { "get_namespace", (PyCFunction) _wrap_g_base_info_get_namespace, METH_NOARGS },
+    { "is_deprecated", (PyCFunction) _wrap_g_base_info_is_deprecated, METH_NOARGS },
+    { "get_attribute", (PyCFunction) _wrap_g_base_info_get_attribute, METH_O },
+    { "get_container", (PyCFunction) _wrap_g_base_info_get_container, METH_NOARGS },
+    { "equal", (PyCFunction) _wrap_g_base_info_equal, METH_O },
+    { NULL, NULL, 0 }
+};
+
+/* _base_info_getattro:
+ *
+ * The usage of __getattr__ is needed because the get/set method table
+ * does not work for __doc__.
+ */
+static PyObject *
+_base_info_getattro(PyGIBaseInfo *self, PyObject *name)
+{
+    PyObject *result;
+
+    static PyObject *docstr;
+    if (docstr == NULL) {
+        docstr= PYGLIB_PyUnicode_InternFromString("__doc__");
+        if (docstr == NULL)
+            return NULL;
+    }
+
+    Py_INCREF (name);
+    PYGLIB_PyUnicode_InternInPlace (&name);
+
+    if (name == docstr) {
+        result = _generate_doc_string (self);
+    } else {
+        result = PyObject_GenericGetAttr ((PyObject *)self, name);
+    }
+
+    Py_DECREF (name);
+    return result;
+}
+
+static PyObject *
+_base_info_attr_name(PyGIBaseInfo *self, void *closure)
+{
+    return _wrap_g_base_info_get_name (self);
+}
+
+static PyObject *
+_base_info_attr_module(PyGIBaseInfo *self, void *closure)
+{
+    return PYGLIB_PyUnicode_FromFormat ("gi.repository.%s",
+                                        g_base_info_get_namespace (self->info));
+}
+
+static PyGetSetDef _base_info_getsets[] = {
+        { "__name__", (getter)_base_info_attr_name, (setter)0, "Name", NULL},
+        { "__module__", (getter)_base_info_attr_module, (setter)0, "Module name", NULL},
+    { NULL, 0, 0 }
+};
+
+PyObject *
+_pygi_info_new (GIBaseInfo *info)
+{
+    GIInfoType info_type;
+    PyTypeObject *type = NULL;
+    PyGIBaseInfo *self;
+
+    info_type = g_base_info_get_type (info);
+
+    switch (info_type)
+    {
+        case GI_INFO_TYPE_INVALID:
+            PyErr_SetString (PyExc_RuntimeError, "Invalid info type");
+            return NULL;
+        case GI_INFO_TYPE_FUNCTION:
+            type = &PyGIFunctionInfo_Type;
+            break;
+        case GI_INFO_TYPE_CALLBACK:
+            type = &PyGICallbackInfo_Type;
+            break;
+        case GI_INFO_TYPE_STRUCT:
+        case GI_INFO_TYPE_BOXED:
+            type = &PyGIStructInfo_Type;
+            break;
+        case GI_INFO_TYPE_ENUM:
+        case GI_INFO_TYPE_FLAGS:
+            type = &PyGIEnumInfo_Type;
+            break;
+        case GI_INFO_TYPE_OBJECT:
+            type = &PyGIObjectInfo_Type;
+            break;
+        case GI_INFO_TYPE_INTERFACE:
+            type = &PyGIInterfaceInfo_Type;
+            break;
+        case GI_INFO_TYPE_CONSTANT:
+            type = &PyGIConstantInfo_Type;
+            break;
+        case GI_INFO_TYPE_UNION:
+            type = &PyGIUnionInfo_Type;
+            break;
+        case GI_INFO_TYPE_VALUE:
+            type = &PyGIValueInfo_Type;
+            break;
+        case GI_INFO_TYPE_SIGNAL:
+            type = &PyGISignalInfo_Type;
+            break;
+        case GI_INFO_TYPE_VFUNC:
+            type = &PyGIVFuncInfo_Type;
+            break;
+        case GI_INFO_TYPE_PROPERTY:
+            type = &PyGIPropertyInfo_Type;
+            break;
+        case GI_INFO_TYPE_FIELD:
+            type = &PyGIFieldInfo_Type;
+            break;
+        case GI_INFO_TYPE_ARG:
+            type = &PyGIArgInfo_Type;
+            break;
+        case GI_INFO_TYPE_TYPE:
+            type = &PyGITypeInfo_Type;
+            break;
+        case GI_INFO_TYPE_UNRESOLVED:
+            type = &PyGIUnresolvedInfo_Type;
+            break;
+        default:
+            g_assert_not_reached();
+            break;
+    }
+
+    self = (PyGIBaseInfo *) type->tp_alloc (type, 0);
+    if (self == NULL) {
+        return NULL;
+    }
+
+    self->info = g_base_info_ref (info);
+    self->inst_weakreflist = NULL;
+    self->cache = NULL;
+
+    return (PyObject *) self;
+}
+
+GIBaseInfo *
+_pygi_object_get_gi_info (PyObject     *object,
+                          PyTypeObject *type)
+{
+    PyObject *py_info;
+    GIBaseInfo *info = NULL;
+
+    py_info = PyObject_GetAttrString (object, "__info__");
+    if (py_info == NULL) {
+        return NULL;
+    }
+    if (!PyObject_TypeCheck (py_info, type)) {
+        PyErr_Format (PyExc_TypeError, "attribute '__info__' must be %s, not %s",
+                      type->tp_name, Py_TYPE(py_info)->tp_name);
+        goto out;
+    }
+
+    info = ( (PyGIBaseInfo *) py_info)->info;
+    g_base_info_ref (info);
+
+out:
+    Py_DECREF (py_info);
+
+    return info;
+}
+
+
+/* CallableInfo */
+PYGLIB_DEFINE_TYPE ("gi.CallableInfo", PyGICallableInfo_Type, PyGICallableInfo);
+
+/* _callable_info_call:
+ *
+ * Shared wrapper for invoke which can be bound (instance method or class constructor)
+ * or unbound (function or static method).
+ */
+static PyObject *
+_callable_info_call (PyGICallableInfo *self, PyObject *args, PyObject *kwargs)
+{
+    /* Insert the bound arg at the beginning of the invoke method args. */
+    if (self->py_bound_arg) {
+        int i;
+        PyObject *result;
+        Py_ssize_t argcount = PyTuple_Size (args);
+        PyObject *newargs = PyTuple_New (argcount + 1);
+        if (newargs == NULL)
+            return NULL;
+
+        Py_INCREF (self->py_bound_arg);
+        PyTuple_SET_ITEM (newargs, 0, self->py_bound_arg);
+
+        for (i = 0; i < argcount; i++) {
+            PyObject *v = PyTuple_GET_ITEM (args, i);
+            Py_XINCREF (v);
+            PyTuple_SET_ITEM (newargs, i+1, v);
+        }
+
+        /* Invoke with the original GI info struct this wrapper was based upon.
+         * This is necessary to maintain the same cache for all bound versions.
+         */
+        result = _wrap_g_callable_info_invoke ((PyGIBaseInfo *)self->py_unbound_info,
+                                               newargs, kwargs);
+        Py_DECREF (newargs);
+        return result;
+
+    } else {
+        /* We should never have an unbound info when calling when calling invoke
+         * at this point because the descriptor implementation on sub-classes
+         * should return "self" not a copy when there is no bound arg.
+         */
+        g_assert (self->py_unbound_info == NULL);
+        return _wrap_g_callable_info_invoke ((PyGIBaseInfo *)self, args, kwargs);
+    }
+}
+
+
+/* _function_info_call:
+ *
+ * Specialization of _callable_info_call for GIFunctionInfo which
+ * handles constructor error conditions.
+ */
+static PyObject *
+_function_info_call (PyGICallableInfo *self, PyObject *args, PyObject *kwargs)
+{
+    if (self->py_bound_arg) {
+        GIFunctionInfoFlags flags;
+
+        /* Ensure constructors are only called as class methods on the class
+         * implementing the constructor and not on sub-classes.
+         */
+        flags = g_function_info_get_flags ( (GIFunctionInfo*) self->base.info);
+        if (flags & GI_FUNCTION_IS_CONSTRUCTOR) {
+            PyObject *py_str_name;
+            const gchar *str_name;
+            GIBaseInfo *container_info = g_base_info_get_container (self->base.info);
+            g_assert (container_info != NULL);
+
+            py_str_name = PyObject_GetAttrString (self->py_bound_arg, "__name__");
+            if (py_str_name == NULL)
+                return NULL;
+
+            if (PyUnicode_Check (py_str_name) ) {
+                PyObject *tmp = PyUnicode_AsUTF8String (py_str_name);
+                Py_DECREF (py_str_name);
+                py_str_name = tmp;
+            }
+
+            str_name = PYGLIB_PyBytes_AsString (py_str_name);
+            if (strcmp (str_name, _safe_base_info_get_name (container_info))) {
+                PyErr_Format (PyExc_TypeError,
+                              "%s constructor cannot be used to create instances of "
+                              "a subclass %s",
+                              _safe_base_info_get_name (container_info),
+                              str_name);
+                Py_DECREF (py_str_name);
+                return NULL;
+            }
+            Py_DECREF (py_str_name);
+        }
+    }
+
+    return _callable_info_call (self, args, kwargs);
+}
+
+/* _new_bound_callable_info
+ *
+ * Utility function for sub-classes to create a bound version of themself.
+ */
+static PyGICallableInfo *
+_new_bound_callable_info (PyGICallableInfo *self, PyObject *bound_arg)
+{
+    PyGICallableInfo *new_self;
+
+    /* Return self if this is already bound or there is nothing passed to bind.  */
+    if (self->py_bound_arg != NULL || bound_arg == NULL || bound_arg == Py_None) {
+        Py_INCREF ((PyObject *)self);
+        return self;
+    }
+
+    new_self = (PyGICallableInfo *)_pygi_info_new (self->base.info);
+    if (new_self == NULL)
+        return NULL;
+
+    Py_INCREF ((PyObject *)self);
+    new_self->py_unbound_info = (struct PyGICallableInfo *)self;
+
+    Py_INCREF (bound_arg);
+    new_self->py_bound_arg = bound_arg;
+
+    return new_self;
+}
+
+/* _function_info_descr_get
+ *
+ * Descriptor protocol implementation for functions, methods, and constructors.
+ */
+static PyObject *
+_function_info_descr_get (PyGICallableInfo *self, PyObject *obj, PyObject *type) {
+    GIFunctionInfoFlags flags;
+    PyObject *bound_arg = NULL;
+
+    flags = g_function_info_get_flags ( (GIFunctionInfo*) self->base.info);
+    if (flags & GI_FUNCTION_IS_CONSTRUCTOR) {
+        if (type == NULL)
+            bound_arg = (PyObject *)(Py_TYPE(obj));
+        else
+            bound_arg = type;
+    } else if (flags & GI_FUNCTION_IS_METHOD) {
+        bound_arg = obj;
+    }
+
+    return (PyObject *)_new_bound_callable_info (self, bound_arg);
+}
+
+/* _vfunc_info_descr_get
+ *
+ * Descriptor protocol implementation for virtual functions.
+ */
+static PyObject *
+_vfunc_info_descr_get (PyGICallableInfo *self, PyObject *obj, PyObject *type) {
+    PyObject *result;
+    PyObject *bound_arg = NULL;
+
+    bound_arg = PyObject_GetAttrString (type, "__gtype__");
+    if (bound_arg == NULL)
+        return NULL;
+
+    /* _new_bound_callable_info adds its own ref so free the one from GetAttrString */
+    result = (PyObject *)_new_bound_callable_info (self, bound_arg);
+    Py_DECREF (bound_arg);
+    return result;
+}
+
+static void
+_callable_info_dealloc (PyGICallableInfo *self)
+{
+    Py_CLEAR (self->py_unbound_info);
+    Py_CLEAR (self->py_bound_arg);
+
+    PyGIBaseInfo_Type.tp_dealloc ((PyObject *) self);
+}
+
+static PyObject *
+_wrap_g_callable_info_get_arguments (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_callable_info_get_n_args, g_callable_info_get_arg);
+}
+
+static PyObject *
+_wrap_g_callable_info_get_return_type (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_callable_info_get_return_type);
+}
+
+static PyObject *
+_wrap_g_callable_info_get_caller_owns (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (
+            g_callable_info_get_caller_owns (self->info) );
+}
+
+static PyObject *
+_wrap_g_callable_info_may_return_null (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (
+            g_callable_info_may_return_null (self->info) );
+}
+
+static PyObject *
+_wrap_g_callable_info_skip_return (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (g_callable_info_skip_return (self->info));
+}
+
+static PyObject *
+_wrap_g_callable_info_get_return_attribute (PyGIBaseInfo *self, PyObject *py_name)
+{
+    gchar *name;
+    const gchar *attr;
+
+    if (!pygi_utf8_from_py (py_name, &name))
+        return NULL;
+
+    attr = g_callable_info_get_return_attribute (self->info, name);
+    if (attr) {
+        g_free (name);
+        return pygi_utf8_to_py (attr);
+    } else {
+        PyErr_Format(PyExc_AttributeError, "return attribute %s not found", name);
+        g_free (name);
+        return NULL;
+    }
+}
+
+static PyObject *
+_wrap_g_callable_info_can_throw_gerror (PyGIBaseInfo *self)
+{
+    if (g_callable_info_can_throw_gerror (self->info))
+        Py_RETURN_TRUE;
+    else
+        Py_RETURN_FALSE;
+}
+
+static PyMethodDef _PyGICallableInfo_methods[] = {
+    { "invoke", (PyCFunction) _wrap_g_callable_info_invoke, METH_VARARGS | METH_KEYWORDS },
+    { "get_arguments", (PyCFunction) _wrap_g_callable_info_get_arguments, METH_NOARGS },
+    { "get_return_type", (PyCFunction) _wrap_g_callable_info_get_return_type, METH_NOARGS },
+    { "get_caller_owns", (PyCFunction) _wrap_g_callable_info_get_caller_owns, METH_NOARGS },
+    { "may_return_null", (PyCFunction) _wrap_g_callable_info_may_return_null, METH_NOARGS },
+    { "skip_return", (PyCFunction) _wrap_g_callable_info_skip_return, METH_NOARGS },
+    { "get_return_attribute", (PyCFunction) _wrap_g_callable_info_get_return_attribute, METH_O },
+    { "can_throw_gerror", (PyCFunction) _wrap_g_callable_info_can_throw_gerror, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+/* CallbackInfo */
+PYGLIB_DEFINE_TYPE ("gi.CallbackInfo", PyGICallbackInfo_Type, PyGICallableInfo);
+
+static PyMethodDef _PyGICallbackInfo_methods[] = {
+    { NULL, NULL, 0 }
+};
+
+/* ErrorDomainInfo */
+PYGLIB_DEFINE_TYPE ("gi.ErrorDomainInfo", PyGIErrorDomainInfo_Type, PyGIBaseInfo);
+
+static PyMethodDef _PyGIErrorDomainInfo_methods[] = {
+    { NULL, NULL, 0 }
+};
+
+/* SignalInfo */
+PYGLIB_DEFINE_TYPE ("gi.SignalInfo", PyGISignalInfo_Type, PyGICallableInfo);
+
+static PyObject *
+_wrap_g_signal_info_get_flags (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (
+            g_signal_info_get_flags ((GISignalInfo *)self->info) );
+}
+
+static PyObject *
+_wrap_g_signal_info_get_class_closure (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_signal_info_get_class_closure);
+}
+
+static PyObject *
+_wrap_g_signal_info_true_stops_emit (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (
+            g_signal_info_true_stops_emit ((GISignalInfo *)self->info) );
+}
+
+static PyMethodDef _PyGISignalInfo_methods[] = {
+    { "get_flags", (PyCFunction) _wrap_g_signal_info_get_flags, METH_NOARGS },
+    { "get_class_closure", (PyCFunction) _wrap_g_signal_info_get_class_closure, METH_NOARGS },
+    { "true_stops_emit", (PyCFunction) _wrap_g_signal_info_true_stops_emit, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+/* PropertyInfo */
+PYGLIB_DEFINE_TYPE ("gi.PropertyInfo", PyGIPropertyInfo_Type, PyGIBaseInfo);
+
+static PyObject *
+_wrap_g_property_info_get_flags (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (
+            g_property_info_get_flags ((GIPropertyInfo *)self->info) );
+}
+
+static PyObject *
+_wrap_g_property_info_get_type (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_property_info_get_type);
+}
+
+static PyObject *
+_wrap_g_property_info_get_ownership_transfer (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (
+            g_property_info_get_ownership_transfer ((GIPropertyInfo *)self->info) );
+}
+
+static PyMethodDef _PyGIPropertyInfo_methods[] = {
+    { "get_flags", (PyCFunction) _wrap_g_property_info_get_flags, METH_NOARGS },
+    { "get_type", (PyCFunction) _wrap_g_property_info_get_type, METH_NOARGS },
+    { "get_ownership_transfer", (PyCFunction) _wrap_g_property_info_get_ownership_transfer, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+
+/* ArgInfo */
+PYGLIB_DEFINE_TYPE ("gi.ArgInfo", PyGIArgInfo_Type, PyGIBaseInfo);
+
+static PyObject *
+_wrap_g_arg_info_get_direction (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (
+           g_arg_info_get_direction ((GIArgInfo*)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_is_caller_allocates (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (
+           g_arg_info_is_caller_allocates ((GIArgInfo*)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_is_return_value (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (
+           g_arg_info_is_return_value ((GIArgInfo*)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_is_optional (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (
+           g_arg_info_is_optional ((GIArgInfo*)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_may_be_null (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (
+           g_arg_info_may_be_null ((GIArgInfo*)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_get_ownership_transfer (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (
+            g_arg_info_get_ownership_transfer ((GIArgInfo *)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_get_scope (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (
+            g_arg_info_get_scope ((GIArgInfo *)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_get_closure (PyGIBaseInfo *self)
+{
+    return pygi_gint_to_py (
+            g_arg_info_get_closure ((GIArgInfo *)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_get_destroy (PyGIBaseInfo *self)
+{
+    return pygi_gint_to_py (
+            g_arg_info_get_destroy ((GIArgInfo *)self->info) );
+}
+
+static PyObject *
+_wrap_g_arg_info_get_type (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_arg_info_get_type);
+}
+
+static PyMethodDef _PyGIArgInfo_methods[] = {
+    { "get_direction", (PyCFunction) _wrap_g_arg_info_get_direction, METH_NOARGS },
+    { "is_caller_allocates", (PyCFunction) _wrap_g_arg_info_is_caller_allocates, METH_NOARGS },
+    { "is_return_value", (PyCFunction) _wrap_g_arg_info_is_return_value, METH_NOARGS },
+    { "is_optional", (PyCFunction) _wrap_g_arg_info_is_optional, METH_NOARGS },
+    { "may_be_null", (PyCFunction) _wrap_g_arg_info_may_be_null, METH_NOARGS },
+    { "get_ownership_transfer", (PyCFunction) _wrap_g_arg_info_get_ownership_transfer, METH_NOARGS },
+    { "get_scope", (PyCFunction) _wrap_g_arg_info_get_scope, METH_NOARGS },
+    { "get_closure", (PyCFunction) _wrap_g_arg_info_get_closure, METH_NOARGS },
+    { "get_destroy", (PyCFunction) _wrap_g_arg_info_get_destroy, METH_NOARGS },
+    { "get_type", (PyCFunction) _wrap_g_arg_info_get_type, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+
+/* TypeInfo */
+PYGLIB_DEFINE_TYPE ("gi.TypeInfo", PyGITypeInfo_Type, PyGIBaseInfo);
+
+static PyObject *
+_wrap_g_type_info_is_pointer (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (g_type_info_is_pointer (self->info));
+}
+
+static PyObject *
+_wrap_g_type_info_get_tag (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (g_type_info_get_tag (self->info));
+}
+
+static PyObject *
+_wrap_g_type_info_get_tag_as_string (PyGIBaseInfo *self)
+{
+    GITypeTag tag = g_type_info_get_tag (self->info);
+    return pygi_utf8_to_py (g_type_tag_to_string(tag));
+}
+
+static PyObject *
+_wrap_g_type_info_get_param_type (PyGIBaseInfo *self, PyObject *py_n)
+{
+    GIBaseInfo *info;
+    PyObject *py_info;
+    gint n;
+
+    if (!pygi_gint_from_py (py_n, &n))
+        return NULL;
+
+    info = (GIBaseInfo *) g_type_info_get_param_type ( (GITypeInfo *) self->info, n);
+    if (info == NULL) {
+        Py_RETURN_NONE;
+    }
+
+    py_info = _pygi_info_new (info);
+    g_base_info_unref (info);
+    return py_info;
+}
+
+static PyObject *
+_wrap_g_type_info_get_interface (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_type_info_get_interface);
+}
+
+static PyObject *
+_wrap_g_type_info_get_array_length (PyGIBaseInfo *self)
+{
+    return pygi_gint_to_py (g_type_info_get_array_length (self->info));
+}
+
+static PyObject *
+_wrap_g_type_info_get_array_fixed_size (PyGIBaseInfo *self)
+{
+    return pygi_gint_to_py (g_type_info_get_array_fixed_size (self->info));
+}
+
+static PyObject *
+_wrap_g_type_info_is_zero_terminated (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (g_type_info_is_zero_terminated (self->info));
+}
+
+static PyObject *
+_wrap_g_type_info_get_array_type (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (g_type_info_get_array_type (self->info));
+}
+
+static PyMethodDef _PyGITypeInfo_methods[] = {
+    { "is_pointer", (PyCFunction) _wrap_g_type_info_is_pointer, METH_NOARGS },
+    { "get_tag", (PyCFunction) _wrap_g_type_info_get_tag, METH_NOARGS },
+    { "get_tag_as_string", (PyCFunction) _wrap_g_type_info_get_tag_as_string, METH_NOARGS },
+    { "get_param_type", (PyCFunction) _wrap_g_type_info_get_param_type, METH_O },
+    { "get_interface", (PyCFunction) _wrap_g_type_info_get_interface, METH_NOARGS },
+    { "get_array_length", (PyCFunction) _wrap_g_type_info_get_array_length, METH_NOARGS },
+    { "get_array_fixed_size", (PyCFunction) _wrap_g_type_info_get_array_fixed_size, METH_NOARGS },
+    { "is_zero_terminated", (PyCFunction) _wrap_g_type_info_is_zero_terminated, METH_NOARGS },
+    { "get_array_type", (PyCFunction) _wrap_g_type_info_get_array_type, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+
+/* FunctionInfo */
+PYGLIB_DEFINE_TYPE ("gi.FunctionInfo", PyGIFunctionInfo_Type, PyGICallableInfo);
+
+static PyObject *
+_wrap_g_function_info_is_constructor (PyGIBaseInfo *self)
+{
+    GIFunctionInfoFlags flags;
+    gboolean is_constructor;
+
+    flags = g_function_info_get_flags ( (GIFunctionInfo*) self->info);
+    is_constructor = flags & GI_FUNCTION_IS_CONSTRUCTOR;
+
+    return pygi_gboolean_to_py (is_constructor);
+}
+
+static PyObject *
+_wrap_g_function_info_is_method (PyGIBaseInfo *self)
+{
+    GIFunctionInfoFlags flags;
+    gboolean is_method;
+
+    flags = g_function_info_get_flags ( (GIFunctionInfo*) self->info);
+    is_method = flags & GI_FUNCTION_IS_METHOD;
+
+    return pygi_gboolean_to_py (is_method);
+}
+
+gsize
+_pygi_g_type_tag_size (GITypeTag type_tag)
+{
+    gsize size = 0;
+
+    switch (type_tag) {
+        case GI_TYPE_TAG_BOOLEAN:
+            size = sizeof (gboolean);
+            break;
+        case GI_TYPE_TAG_INT8:
+        case GI_TYPE_TAG_UINT8:
+            size = sizeof (gint8);
+            break;
+        case GI_TYPE_TAG_INT16:
+        case GI_TYPE_TAG_UINT16:
+            size = sizeof (gint16);
+            break;
+        case GI_TYPE_TAG_INT32:
+        case GI_TYPE_TAG_UINT32:
+            size = sizeof (gint32);
+            break;
+        case GI_TYPE_TAG_INT64:
+        case GI_TYPE_TAG_UINT64:
+            size = sizeof (gint64);
+            break;
+        case GI_TYPE_TAG_FLOAT:
+            size = sizeof (gfloat);
+            break;
+        case GI_TYPE_TAG_DOUBLE:
+            size = sizeof (gdouble);
+            break;
+        case GI_TYPE_TAG_GTYPE:
+            size = sizeof (GType);
+            break;
+        case GI_TYPE_TAG_UNICHAR:
+            size = sizeof (gunichar);
+            break;
+        case GI_TYPE_TAG_VOID:
+        case GI_TYPE_TAG_UTF8:
+        case GI_TYPE_TAG_FILENAME:
+        case GI_TYPE_TAG_ARRAY:
+        case GI_TYPE_TAG_INTERFACE:
+        case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
+        case GI_TYPE_TAG_GHASH:
+        case GI_TYPE_TAG_ERROR:
+            PyErr_Format (PyExc_TypeError,
+                          "Unable to know the size (assuming %s is not a pointer)",
+                          g_type_tag_to_string (type_tag));
+            break;
+        default:
+            break;
+    }
+
+    return size;
+}
+
+gsize
+_pygi_g_type_info_size (GITypeInfo *type_info)
+{
+    gsize size = 0;
+
+    GITypeTag type_tag;
+
+    type_tag = g_type_info_get_tag (type_info);
+    switch (type_tag) {
+        case GI_TYPE_TAG_BOOLEAN:
+        case GI_TYPE_TAG_INT8:
+        case GI_TYPE_TAG_UINT8:
+        case GI_TYPE_TAG_INT16:
+        case GI_TYPE_TAG_UINT16:
+        case GI_TYPE_TAG_INT32:
+        case GI_TYPE_TAG_UINT32:
+        case GI_TYPE_TAG_INT64:
+        case GI_TYPE_TAG_UINT64:
+        case GI_TYPE_TAG_FLOAT:
+        case GI_TYPE_TAG_DOUBLE:
+        case GI_TYPE_TAG_GTYPE:
+        case GI_TYPE_TAG_UNICHAR:
+            size = _pygi_g_type_tag_size (type_tag);
+            g_assert (size > 0);
+            break;
+        case GI_TYPE_TAG_INTERFACE:
+        {
+            GIBaseInfo *info;
+            GIInfoType info_type;
+
+            info = g_type_info_get_interface (type_info);
+            info_type = g_base_info_get_type (info);
+
+            switch (info_type) {
+                case GI_INFO_TYPE_STRUCT:
+                    if (g_type_info_is_pointer (type_info)) {
+                        size = sizeof (gpointer);
+                    } else {
+                        size = g_struct_info_get_size ( (GIStructInfo *) info);
+                    }
+                    break;
+                case GI_INFO_TYPE_UNION:
+                    if (g_type_info_is_pointer (type_info)) {
+                        size = sizeof (gpointer);
+                    } else {
+                        size = g_union_info_get_size ( (GIUnionInfo *) info);
+                    }
+                    break;
+                case GI_INFO_TYPE_ENUM:
+                case GI_INFO_TYPE_FLAGS:
+                    if (g_type_info_is_pointer (type_info)) {
+                        size = sizeof (gpointer);
+                    } else {
+                        GITypeTag enum_type_tag;
+
+                        enum_type_tag = g_enum_info_get_storage_type ( (GIEnumInfo *) info);
+                        size = _pygi_g_type_tag_size (enum_type_tag);
+                    }
+                    break;
+                case GI_INFO_TYPE_BOXED:
+                case GI_INFO_TYPE_OBJECT:
+                case GI_INFO_TYPE_INTERFACE:
+                case GI_INFO_TYPE_CALLBACK:
+                    size = sizeof (gpointer);
+                    break;
+                case GI_INFO_TYPE_VFUNC:
+                case GI_INFO_TYPE_INVALID:
+                case GI_INFO_TYPE_FUNCTION:
+                case GI_INFO_TYPE_CONSTANT:
+                case GI_INFO_TYPE_VALUE:
+                case GI_INFO_TYPE_SIGNAL:
+                case GI_INFO_TYPE_PROPERTY:
+                case GI_INFO_TYPE_FIELD:
+                case GI_INFO_TYPE_ARG:
+                case GI_INFO_TYPE_TYPE:
+                case GI_INFO_TYPE_UNRESOLVED:
+                default:
+                    g_assert_not_reached();
+                    break;
+            }
+
+            g_base_info_unref (info);
+            break;
+        }
+        case GI_TYPE_TAG_ARRAY:
+        case GI_TYPE_TAG_VOID:
+        case GI_TYPE_TAG_UTF8:
+        case GI_TYPE_TAG_FILENAME:
+        case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
+        case GI_TYPE_TAG_GHASH:
+        case GI_TYPE_TAG_ERROR:
+            size = sizeof (gpointer);
+            break;
+        default:
+            break;
+    }
+
+    return size;
+}
+
+static PyObject *
+_wrap_g_function_info_get_symbol (PyGIBaseInfo *self)
+{
+    return _get_info_string (self, g_function_info_get_symbol);
+}
+
+static PyObject *
+_wrap_g_function_info_get_flags (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (g_function_info_get_flags (self->info));
+}
+
+static PyObject *
+_wrap_g_function_info_get_property (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_function_info_get_property);
+}
+
+static PyObject *
+_wrap_g_function_info_get_vfunc (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_function_info_get_vfunc);
+}
+
+static PyMethodDef _PyGIFunctionInfo_methods[] = {
+    { "is_constructor", (PyCFunction) _wrap_g_function_info_is_constructor, METH_NOARGS },
+    { "is_method", (PyCFunction) _wrap_g_function_info_is_method, METH_NOARGS },
+    { "get_symbol", (PyCFunction) _wrap_g_function_info_get_symbol, METH_NOARGS },
+    { "get_flags", (PyCFunction) _wrap_g_function_info_get_flags, METH_NOARGS },
+    { "get_property", (PyCFunction) _wrap_g_function_info_get_property, METH_NOARGS },
+    { "get_vfunc", (PyCFunction) _wrap_g_function_info_get_vfunc, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+/* RegisteredTypeInfo */
+PYGLIB_DEFINE_TYPE ("gi.RegisteredTypeInfo", PyGIRegisteredTypeInfo_Type, PyGIBaseInfo);
+
+static PyObject *
+_wrap_g_registered_type_info_get_type_name (PyGIBaseInfo *self)
+{
+    return _get_info_string (self, g_registered_type_info_get_type_name);
+}
+
+static PyObject *
+_wrap_g_registered_type_info_get_type_init (PyGIBaseInfo *self)
+{
+    return _get_info_string (self, g_registered_type_info_get_type_init);
+}
+
+static PyObject *
+_wrap_g_registered_type_info_get_g_type (PyGIBaseInfo *self)
+{
+    GType type;
+
+    type = g_registered_type_info_get_g_type ( (GIRegisteredTypeInfo *) self->info);
+
+    return pyg_type_wrapper_new (type);
+}
+
+static PyMethodDef _PyGIRegisteredTypeInfo_methods[] = {
+    { "get_type_name", (PyCFunction) _wrap_g_registered_type_info_get_type_name, METH_NOARGS },
+    { "get_type_init", (PyCFunction) _wrap_g_registered_type_info_get_type_init, METH_NOARGS },
+    { "get_g_type", (PyCFunction) _wrap_g_registered_type_info_get_g_type, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+
+/* GIStructInfo */
+PYGLIB_DEFINE_TYPE ("StructInfo", PyGIStructInfo_Type, PyGIBaseInfo);
+
+static PyObject *
+_wrap_g_struct_info_get_fields (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_struct_info_get_n_fields, g_struct_info_get_field);
+}
+
+static PyObject *
+_wrap_g_struct_info_get_methods (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_struct_info_get_n_methods, g_struct_info_get_method);
+}
+
+static PyObject *
+_wrap_g_struct_info_get_size (PyGIBaseInfo *self)
+{
+    return pygi_gsize_to_py (g_struct_info_get_size (self->info));
+}
+
+static PyObject *
+_wrap_g_struct_info_get_alignment (PyGIBaseInfo *self)
+{
+    return pygi_gsize_to_py (g_struct_info_get_alignment (self->info));
+}
+
+static PyObject *
+_wrap_g_struct_info_is_gtype_struct (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (g_struct_info_is_gtype_struct (self->info));
+}
+
+static PyObject *
+_wrap_g_struct_info_is_foreign (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (g_struct_info_is_foreign (self->info));
+}
+
+static PyMethodDef _PyGIStructInfo_methods[] = {
+    { "get_fields", (PyCFunction) _wrap_g_struct_info_get_fields, METH_NOARGS },
+    { "get_methods", (PyCFunction) _wrap_g_struct_info_get_methods, METH_NOARGS },
+    { "get_size", (PyCFunction) _wrap_g_struct_info_get_size, METH_NOARGS },
+    { "get_alignment", (PyCFunction) _wrap_g_struct_info_get_alignment, METH_NOARGS },
+    { "is_gtype_struct", (PyCFunction) _wrap_g_struct_info_is_gtype_struct, METH_NOARGS },
+    { "is_foreign", (PyCFunction) _wrap_g_struct_info_is_foreign, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+gboolean
+pygi_g_struct_info_is_simple (GIStructInfo *struct_info)
+{
+    gboolean is_simple;
+    gint n_field_infos;
+    gint i;
+
+    is_simple = TRUE;
+
+    n_field_infos = g_struct_info_get_n_fields (struct_info);
+
+    for (i = 0; i < n_field_infos && is_simple; i++) {
+        GIFieldInfo *field_info;
+        GITypeInfo *field_type_info;
+        GITypeTag field_type_tag;
+
+        field_info = g_struct_info_get_field (struct_info, i);
+        field_type_info = g_field_info_get_type (field_info);
+
+
+        field_type_tag = g_type_info_get_tag (field_type_info);
+
+        switch (field_type_tag) {
+            case GI_TYPE_TAG_BOOLEAN:
+            case GI_TYPE_TAG_INT8:
+            case GI_TYPE_TAG_UINT8:
+            case GI_TYPE_TAG_INT16:
+            case GI_TYPE_TAG_UINT16:
+            case GI_TYPE_TAG_INT32:
+            case GI_TYPE_TAG_UINT32:
+            case GI_TYPE_TAG_INT64:
+            case GI_TYPE_TAG_UINT64:
+            case GI_TYPE_TAG_FLOAT:
+            case GI_TYPE_TAG_DOUBLE:
+            case GI_TYPE_TAG_UNICHAR:
+                if (g_type_info_is_pointer (field_type_info)) {
+                    is_simple = FALSE;
+                }
+                break;
+            case GI_TYPE_TAG_VOID:
+            case GI_TYPE_TAG_GTYPE:
+            case GI_TYPE_TAG_ERROR:
+            case GI_TYPE_TAG_UTF8:
+            case GI_TYPE_TAG_FILENAME:
+            case GI_TYPE_TAG_ARRAY:
+            case GI_TYPE_TAG_GLIST:
+            case GI_TYPE_TAG_GSLIST:
+            case GI_TYPE_TAG_GHASH:
+                is_simple = FALSE;
+                break;
+            case GI_TYPE_TAG_INTERFACE:
+            {
+                GIBaseInfo *info;
+                GIInfoType info_type;
+
+                info = g_type_info_get_interface (field_type_info);
+                info_type = g_base_info_get_type (info);
+
+                switch (info_type) {
+                    case GI_INFO_TYPE_STRUCT:
+                        if (g_type_info_is_pointer (field_type_info)) {
+                            is_simple = FALSE;
+                        } else {
+                            is_simple = pygi_g_struct_info_is_simple ( (GIStructInfo *) info);
+                        }
+                        break;
+                    case GI_INFO_TYPE_UNION:
+                        /* TODO */
+                        is_simple = FALSE;
+                        break;
+                    case GI_INFO_TYPE_ENUM:
+                    case GI_INFO_TYPE_FLAGS:
+                        if (g_type_info_is_pointer (field_type_info)) {
+                            is_simple = FALSE;
+                        }
+                        break;
+                    case GI_INFO_TYPE_BOXED:
+                    case GI_INFO_TYPE_OBJECT:
+                    case GI_INFO_TYPE_CALLBACK:
+                    case GI_INFO_TYPE_INTERFACE:
+                        is_simple = FALSE;
+                        break;
+                    case GI_INFO_TYPE_VFUNC:
+                    case GI_INFO_TYPE_INVALID:
+                    case GI_INFO_TYPE_FUNCTION:
+                    case GI_INFO_TYPE_CONSTANT:
+                    case GI_INFO_TYPE_VALUE:
+                    case GI_INFO_TYPE_SIGNAL:
+                    case GI_INFO_TYPE_PROPERTY:
+                    case GI_INFO_TYPE_FIELD:
+                    case GI_INFO_TYPE_ARG:
+                    case GI_INFO_TYPE_TYPE:
+                    case GI_INFO_TYPE_UNRESOLVED:
+                    default:
+                        g_assert_not_reached();
+                        break;
+                }
+
+                g_base_info_unref (info);
+                break;
+            }
+            default:
+                g_assert_not_reached();
+                break;
+        }
+
+        g_base_info_unref ( (GIBaseInfo *) field_type_info);
+        g_base_info_unref ( (GIBaseInfo *) field_info);
+    }
+
+    return is_simple;
+}
+
+
+/* EnumInfo */
+PYGLIB_DEFINE_TYPE ("gi.EnumInfo", PyGIEnumInfo_Type, PyGIBaseInfo);
+
+static PyObject *
+_wrap_g_enum_info_get_values (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_enum_info_get_n_values, g_enum_info_get_value);
+}
+
+static PyObject *
+_wrap_g_enum_info_is_flags (PyGIBaseInfo *self)
+{
+    GIInfoType info_type = g_base_info_get_type ((GIBaseInfo *) self->info);
+
+    if (info_type == GI_INFO_TYPE_ENUM) {
+        Py_RETURN_FALSE;
+    } else if (info_type == GI_INFO_TYPE_FLAGS) {
+        Py_RETURN_TRUE;
+    } else {
+        g_assert_not_reached();
+    }
+}
+
+static PyObject *
+_wrap_g_enum_info_get_methods (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_enum_info_get_n_methods, g_enum_info_get_method);
+}
+
+static PyObject *
+_wrap_g_enum_info_get_storage_type (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (g_enum_info_get_storage_type ((GIBaseInfo *) self->info));
+}
+
+static PyMethodDef _PyGIEnumInfo_methods[] = {
+    { "get_values", (PyCFunction) _wrap_g_enum_info_get_values, METH_NOARGS },
+    { "is_flags", (PyCFunction) _wrap_g_enum_info_is_flags, METH_NOARGS },
+    { "get_methods", (PyCFunction) _wrap_g_enum_info_get_methods, METH_NOARGS },
+    { "get_storage_type", (PyCFunction) _wrap_g_enum_info_get_storage_type, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+
+/* ObjectInfo */
+PYGLIB_DEFINE_TYPE ("ObjectInfo", PyGIObjectInfo_Type, PyGIBaseInfo);
+
+static PyObject *
+_wrap_g_object_info_get_parent (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_object_info_get_parent);
+}
+
+static PyObject *
+_wrap_g_object_info_get_methods (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_object_info_get_n_methods, g_object_info_get_method);
+}
+
+static PyObject *
+_wrap_g_object_info_find_method (PyGIBaseInfo *self, PyObject *py_name)
+{
+    return _get_child_info_by_name (self, py_name, g_object_info_find_method);
+}
+
+static PyObject *
+_wrap_g_object_info_get_fields (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_object_info_get_n_fields, g_object_info_get_field);
+}
+
+static PyObject *
+_wrap_g_object_info_get_properties (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_object_info_get_n_properties, g_object_info_get_property);
+}
+
+static PyObject *
+_wrap_g_object_info_get_signals (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_object_info_get_n_signals, g_object_info_get_signal);
+}
+
+static PyObject *
+_wrap_g_object_info_get_interfaces (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_object_info_get_n_interfaces, g_object_info_get_interface);
+}
+
+static PyObject *
+_wrap_g_object_info_get_constants (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_object_info_get_n_constants, g_object_info_get_constant);
+}
+
+static PyObject *
+_wrap_g_object_info_get_vfuncs (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_object_info_get_n_vfuncs, g_object_info_get_vfunc);
+}
+
+static PyObject *
+_wrap_g_object_info_get_abstract (PyGIBaseInfo *self)
+{
+    gboolean is_abstract  = g_object_info_get_abstract ( (GIObjectInfo*) self->info);
+    return pygi_gboolean_to_py (is_abstract);
+}
+
+static PyObject *
+_wrap_g_object_info_get_type_name (PyGIBaseInfo *self)
+{
+    return _get_info_string (self, g_object_info_get_type_name);
+}
+
+static PyObject *
+_wrap_g_object_info_get_type_init (PyGIBaseInfo *self)
+{
+    return _get_info_string (self, g_object_info_get_type_init);
+}
+
+static PyObject *
+_wrap_g_object_info_get_fundamental (PyGIBaseInfo *self)
+{
+    return pygi_gboolean_to_py (g_object_info_get_fundamental ( (GIObjectInfo*) self->info));
+}
+
+static PyObject *
+_wrap_g_object_info_get_class_struct (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_object_info_get_class_struct);
+}
+
+static PyObject *
+_wrap_g_object_info_find_vfunc (PyGIBaseInfo *self, PyObject *py_name)
+{
+    return _get_child_info_by_name (self, py_name, g_object_info_find_vfunc);
+}
+
+static PyObject *
+_wrap_g_object_info_get_unref_function (PyGIBaseInfo *self)
+{
+    return _get_info_string (self, g_object_info_get_unref_function);
+}
+
+static PyObject *
+_wrap_g_object_info_get_ref_function (PyGIBaseInfo *self)
+{
+    return _get_info_string (self, g_object_info_get_ref_function);
+}
+
+static PyObject *
+_wrap_g_object_info_get_set_value_function (PyGIBaseInfo *self)
+{
+    return _get_info_string (self, g_object_info_get_set_value_function);
+}
+
+static PyObject *
+_wrap_g_object_info_get_get_value_function (PyGIBaseInfo *self)
+{
+    return _get_info_string (self, g_object_info_get_get_value_function);
+}
+
+static PyMethodDef _PyGIObjectInfo_methods[] = {
+    { "get_parent", (PyCFunction) _wrap_g_object_info_get_parent, METH_NOARGS },
+    { "get_methods", (PyCFunction) _wrap_g_object_info_get_methods, METH_NOARGS },
+    { "find_method", (PyCFunction) _wrap_g_object_info_find_method, METH_O },
+    { "get_fields", (PyCFunction) _wrap_g_object_info_get_fields, METH_NOARGS },
+    { "get_properties", (PyCFunction) _wrap_g_object_info_get_properties, METH_NOARGS },
+    { "get_signals", (PyCFunction) _wrap_g_object_info_get_signals, METH_NOARGS },
+    { "get_interfaces", (PyCFunction) _wrap_g_object_info_get_interfaces, METH_NOARGS },
+    { "get_constants", (PyCFunction) _wrap_g_object_info_get_constants, METH_NOARGS },
+    { "get_vfuncs", (PyCFunction) _wrap_g_object_info_get_vfuncs, METH_NOARGS },
+    { "find_vfunc", (PyCFunction) _wrap_g_object_info_find_vfunc, METH_O },
+    { "get_abstract", (PyCFunction) _wrap_g_object_info_get_abstract, METH_NOARGS },
+    { "get_type_name", (PyCFunction) _wrap_g_object_info_get_type_name, METH_NOARGS },
+    { "get_type_init", (PyCFunction) _wrap_g_object_info_get_type_init, METH_NOARGS },
+    { "get_fundamental", (PyCFunction) _wrap_g_object_info_get_fundamental, METH_NOARGS },
+    { "get_class_struct", (PyCFunction) _wrap_g_object_info_get_class_struct, METH_NOARGS },
+    { "get_unref_function", (PyCFunction) _wrap_g_object_info_get_unref_function, METH_NOARGS },
+    { "get_ref_function", (PyCFunction) _wrap_g_object_info_get_ref_function, METH_NOARGS },
+    { "get_set_value_function", (PyCFunction) _wrap_g_object_info_get_set_value_function, METH_NOARGS },
+    { "get_get_value_function", (PyCFunction) _wrap_g_object_info_get_get_value_function, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+
+/* GIInterfaceInfo */
+PYGLIB_DEFINE_TYPE ("InterfaceInfo", PyGIInterfaceInfo_Type, PyGIBaseInfo);
+
+static PyObject *
+_wrap_g_interface_info_get_methods (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_interface_info_get_n_methods, g_interface_info_get_method);
+}
+
+static PyObject *
+_wrap_g_interface_info_find_method (PyGIBaseInfo *self, PyObject *py_name)
+{
+    return _get_child_info_by_name (self, py_name, g_interface_info_find_method);
+}
+
+static PyObject *
+_wrap_g_interface_info_get_constants (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_interface_info_get_n_constants, g_interface_info_get_constant);
+}
+
+static PyObject *
+_wrap_g_interface_info_get_vfuncs (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_interface_info_get_n_vfuncs, g_interface_info_get_vfunc);
+}
+
+static PyObject *
+_wrap_g_interface_info_find_vfunc (PyGIBaseInfo *self, PyObject *py_name)
+{
+    return _get_child_info_by_name (self, py_name, g_interface_info_find_vfunc);
+}
+
+static PyObject *
+_wrap_g_interface_info_get_prerequisites (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_interface_info_get_n_prerequisites, g_interface_info_get_prerequisite);
+}
+
+static PyObject *
+_wrap_g_interface_info_get_properties (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_interface_info_get_n_properties, g_interface_info_get_property);
+}
+
+static PyObject *
+_wrap_g_interface_info_get_iface_struct (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_interface_info_get_iface_struct);
+}
+
+static PyObject *
+_wrap_g_interface_info_get_signals (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_interface_info_get_n_signals, g_interface_info_get_signal);
+}
+
+static PyObject *
+_wrap_g_interface_info_find_signal (PyGIBaseInfo *self, PyObject *py_name)
+{
+    return _get_child_info_by_name (self, py_name, g_interface_info_find_signal);
+}
+
+static PyMethodDef _PyGIInterfaceInfo_methods[] = {
+    { "get_prerequisites", (PyCFunction) _wrap_g_interface_info_get_prerequisites, METH_NOARGS },
+    { "get_properties", (PyCFunction) _wrap_g_interface_info_get_properties, METH_NOARGS },
+    { "get_methods", (PyCFunction) _wrap_g_interface_info_get_methods, METH_NOARGS },
+    { "find_method", (PyCFunction) _wrap_g_interface_info_find_method, METH_O },
+    { "get_signals", (PyCFunction) _wrap_g_interface_info_get_signals, METH_NOARGS },
+    { "find_signal", (PyCFunction) _wrap_g_interface_info_find_signal, METH_O },
+    { "get_vfuncs", (PyCFunction) _wrap_g_interface_info_get_vfuncs, METH_NOARGS },
+    { "get_constants", (PyCFunction) _wrap_g_interface_info_get_constants, METH_NOARGS },
+    { "get_iface_struct", (PyCFunction) _wrap_g_interface_info_get_iface_struct, METH_NOARGS },
+    { "find_vfunc", (PyCFunction) _wrap_g_interface_info_find_vfunc, METH_O },
+    { NULL, NULL, 0 }
+};
+
+/* GIConstantInfo */
+PYGLIB_DEFINE_TYPE ("gi.ConstantInfo", PyGIConstantInfo_Type, PyGIBaseInfo);
+
+static PyObject *
+_wrap_g_constant_info_get_value (PyGIBaseInfo *self)
+{
+    GITypeInfo *type_info;
+    GIArgument value = {0};
+    PyObject *py_value;
+    gboolean free_array = FALSE;
+
+    if (g_constant_info_get_value ( (GIConstantInfo *) self->info, &value) < 0) {
+        PyErr_SetString (PyExc_RuntimeError, "unable to get value");
+        return NULL;
+    }
+
+    type_info = g_constant_info_get_type ( (GIConstantInfo *) self->info);
+
+    if (g_type_info_get_tag (type_info) == GI_TYPE_TAG_ARRAY) {
+        value.v_pointer = _pygi_argument_to_array (&value, NULL, NULL, NULL,
+                                                   type_info, &free_array);
+    }
+
+    py_value = _pygi_argument_to_object (&value, type_info, GI_TRANSFER_NOTHING);
+    
+    if (free_array) {
+        g_array_free (value.v_pointer, FALSE);
+    }
+
+    g_constant_info_free_value (self->info, &value);
+    g_base_info_unref ( (GIBaseInfo *) type_info);
+
+    return py_value;
+}
+
+static PyMethodDef _PyGIConstantInfo_methods[] = {
+    { "get_value", (PyCFunction) _wrap_g_constant_info_get_value, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+/* GIValueInfo */
+PYGLIB_DEFINE_TYPE ("gi.ValueInfo", PyGIValueInfo_Type, PyGIBaseInfo);
+
+static PyObject *
+_wrap_g_value_info_get_value (PyGIBaseInfo *self)
+{
+    gint64 value;
+
+    value = g_value_info_get_value ( (GIValueInfo *) self->info);
+
+    return pygi_gint64_to_py (value);
+}
+
+
+static PyMethodDef _PyGIValueInfo_methods[] = {
+    { "get_value", (PyCFunction) _wrap_g_value_info_get_value, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+
+/* GIFieldInfo */
+PYGLIB_DEFINE_TYPE ("gi.FieldInfo", PyGIFieldInfo_Type, PyGIBaseInfo);
+
+static gssize
+_struct_field_array_length_marshal (gsize length_index,
+                                    void *container_ptr,
+                                    void *struct_data_ptr)
+{
+    gssize array_len = -1;
+    GIFieldInfo *array_len_field = NULL;
+    GIArgument arg = {0};
+    GIBaseInfo *container_info = (GIBaseInfo *)container_ptr;
+
+    switch (g_base_info_get_type (container_info)) {
+        case GI_INFO_TYPE_UNION:
+            array_len_field = g_union_info_get_field ((GIUnionInfo *)container_info, (gint)length_index);
+            break;
+        case GI_INFO_TYPE_STRUCT:
+            array_len_field = g_struct_info_get_field ((GIStructInfo *)container_info, (gint)length_index);
+            break;
+        case GI_INFO_TYPE_OBJECT:
+            array_len_field = g_object_info_get_field ((GIObjectInfo *)container_info, (gint)length_index);
+            break;
+        default:
+            /* Other types don't have fields. */
+            g_assert_not_reached();
+    }
+
+    if (array_len_field == NULL) {
+        return -1;
+    }
+
+    if (g_field_info_get_field (array_len_field, struct_data_ptr, &arg)) {
+        GITypeInfo *array_len_type_info;
+
+        array_len_type_info = g_field_info_get_type (array_len_field);
+        if (array_len_type_info == NULL) {
+            goto out;
+        }
+
+        if (!pygi_argument_to_gssize (&arg,
+                                      g_type_info_get_tag (array_len_type_info),
+                                      &array_len)) {
+            array_len = -1;
+        }
+
+        g_base_info_unref (array_len_type_info);
+    }
+
+out:
+    g_base_info_unref (array_len_field);
+    return array_len;
+}
+
+static gint
+_pygi_g_registered_type_info_check_object (GIRegisteredTypeInfo *info,
+                                           gboolean              is_instance,
+                                           PyObject             *object)
+{
+    gint retval;
+
+    GType g_type;
+    PyObject *py_type;
+    gchar *type_name_expected = NULL;
+    GIInfoType interface_type;
+
+    interface_type = g_base_info_get_type (info);
+    if ( (interface_type == GI_INFO_TYPE_STRUCT) &&
+            (g_struct_info_is_foreign ( (GIStructInfo*) info))) {
+        /* TODO: Could we check is the correct foreign type? */
+        return 1;
+    }
+
+    g_type = g_registered_type_info_get_g_type (info);
+    if (g_type != G_TYPE_NONE) {
+        py_type = pygi_type_get_from_g_type (g_type);
+    } else {
+        py_type = pygi_type_import_by_gi_info ( (GIBaseInfo *) info);
+    }
+
+    if (py_type == NULL) {
+        return 0;
+    }
+
+    g_assert (PyType_Check (py_type));
+
+    if (is_instance) {
+        retval = PyObject_IsInstance (object, py_type);
+        if (!retval) {
+            type_name_expected = _pygi_g_base_info_get_fullname (
+                                     (GIBaseInfo *) info);
+        }
+    } else {
+        if (!PyObject_Type (py_type)) {
+            type_name_expected = "type";
+            retval = 0;
+        } else if (!PyType_IsSubtype ( (PyTypeObject *) object,
+                                       (PyTypeObject *) py_type)) {
+            type_name_expected = _pygi_g_base_info_get_fullname (
+                                     (GIBaseInfo *) info);
+            retval = 0;
+        } else {
+            retval = 1;
+        }
+    }
+
+    Py_DECREF (py_type);
+
+    if (!retval) {
+        PyTypeObject *object_type;
+
+        if (type_name_expected == NULL) {
+            return -1;
+        }
+
+        object_type = (PyTypeObject *) PyObject_Type (object);
+        if (object_type == NULL) {
+            return -1;
+        }
+
+        PyErr_Format (PyExc_TypeError, "Must be %s, not %s",
+                      type_name_expected, object_type->tp_name);
+
+        g_free (type_name_expected);
+    }
+
+    return retval;
+}
+
+static PyObject *
+_wrap_g_field_info_get_value (PyGIBaseInfo *self,
+                              PyObject     *args)
+{
+    PyObject *instance;
+    GIBaseInfo *container_info;
+    GIInfoType container_info_type;
+    gpointer pointer;
+    GITypeInfo *field_type_info;
+    GIArgument value;
+    PyObject *py_value = NULL;
+    gboolean free_array = FALSE;
+
+    memset(&value, 0, sizeof(GIArgument));
+
+    if (!PyArg_ParseTuple (args, "O:FieldInfo.get_value", &instance)) {
+        return NULL;
+    }
+
+    container_info = g_base_info_get_container (self->info);
+    g_assert (container_info != NULL);
+
+    /* Check the instance. */
+    if (!_pygi_g_registered_type_info_check_object ( (GIRegisteredTypeInfo *) container_info, TRUE, instance)) {
+        _PyGI_ERROR_PREFIX ("argument 1: ");
+        return NULL;
+    }
+
+    /* Get the pointer to the container. */
+    container_info_type = g_base_info_get_type (container_info);
+    switch (container_info_type) {
+        case GI_INFO_TYPE_UNION:
+        case GI_INFO_TYPE_STRUCT:
+            pointer = pyg_boxed_get (instance, void);
+            break;
+        case GI_INFO_TYPE_OBJECT:
+            pointer = pygobject_get (instance);
+            break;
+        default:
+            /* Other types don't have fields. */
+            g_assert_not_reached();
+    }
+
+    /* Get the field's value. */
+    field_type_info = g_field_info_get_type ( (GIFieldInfo *) self->info);
+
+    /* A few types are not handled by g_field_info_get_field, so do it here. */
+    if (!g_type_info_is_pointer (field_type_info)
+            && g_type_info_get_tag (field_type_info) == GI_TYPE_TAG_INTERFACE) {
+        GIBaseInfo *info;
+        GIInfoType info_type;
+
+        if (! (g_field_info_get_flags ( (GIFieldInfo *) self->info) & GI_FIELD_IS_READABLE)) {
+            PyErr_SetString (PyExc_RuntimeError, "field is not readable");
+            goto out;
+        }
+
+        info = g_type_info_get_interface (field_type_info);
+
+        info_type = g_base_info_get_type (info);
+
+        g_base_info_unref (info);
+
+        switch (info_type) {
+            case GI_INFO_TYPE_UNION:
+                PyErr_SetString (PyExc_NotImplementedError, "getting an union is not supported yet");
+                goto out;
+            case GI_INFO_TYPE_STRUCT:
+            {
+                gsize offset;
+
+                offset = g_field_info_get_offset ( (GIFieldInfo *) self->info);
+
+                value.v_pointer = (char*) pointer + offset;
+
+                goto argument_to_object;
+            }
+            default:
+                /* Fallback. */
+                break;
+        }
+    }
+
+    if (!g_field_info_get_field ( (GIFieldInfo *) self->info, pointer, &value)) {
+        PyErr_SetString (PyExc_RuntimeError, "unable to get the value");
+        goto out;
+    }
+
+    if (g_type_info_get_tag (field_type_info) == GI_TYPE_TAG_ARRAY) {
+        value.v_pointer = _pygi_argument_to_array (&value,
+                                                   _struct_field_array_length_marshal,
+                                                   container_info,
+                                                   pointer,
+                                                   field_type_info,
+                                                   &free_array);
+    }
+
+argument_to_object:
+    py_value = _pygi_argument_to_object (&value, field_type_info, GI_TRANSFER_NOTHING);
+
+    if (free_array) {
+        g_array_free (value.v_pointer, FALSE);
+    }
+
+out:
+    g_base_info_unref ( (GIBaseInfo *) field_type_info);
+
+    return py_value;
+}
+
+static PyObject *
+_wrap_g_field_info_set_value (PyGIBaseInfo *self,
+                              PyObject     *args)
+{
+    PyObject *instance;
+    PyObject *py_value;
+    GIBaseInfo *container_info;
+    GIInfoType container_info_type;
+    gpointer pointer;
+    GITypeInfo *field_type_info;
+    GIArgument value;
+    PyObject *retval = NULL;
+
+    if (!PyArg_ParseTuple (args, "OO:FieldInfo.set_value", &instance, &py_value)) {
+        return NULL;
+    }
+
+    container_info = g_base_info_get_container (self->info);
+    g_assert (container_info != NULL);
+
+    /* Check the instance. */
+    if (!_pygi_g_registered_type_info_check_object ( (GIRegisteredTypeInfo *) container_info, TRUE, instance)) {
+        _PyGI_ERROR_PREFIX ("argument 1: ");
+        return NULL;
+    }
+
+    /* Get the pointer to the container. */
+    container_info_type = g_base_info_get_type (container_info);
+    switch (container_info_type) {
+        case GI_INFO_TYPE_UNION:
+        case GI_INFO_TYPE_STRUCT:
+            pointer = pyg_boxed_get (instance, void);
+            break;
+        case GI_INFO_TYPE_OBJECT:
+            pointer = pygobject_get (instance);
+            break;
+        default:
+            /* Other types don't have fields. */
+            g_assert_not_reached();
+    }
+
+    field_type_info = g_field_info_get_type ( (GIFieldInfo *) self->info);
+
+    /* Set the field's value. */
+    /* A few types are not handled by g_field_info_set_field, so do it here. */
+    if (!g_type_info_is_pointer (field_type_info)
+            && g_type_info_get_tag (field_type_info) == GI_TYPE_TAG_INTERFACE) {
+        GIBaseInfo *info;
+        GIInfoType info_type;
+
+        if (! (g_field_info_get_flags ( (GIFieldInfo *) self->info) & GI_FIELD_IS_WRITABLE)) {
+            PyErr_SetString (PyExc_RuntimeError, "field is not writable");
+            goto out;
+        }
+
+        info = g_type_info_get_interface (field_type_info);
+
+        info_type = g_base_info_get_type (info);
+
+        switch (info_type) {
+            case GI_INFO_TYPE_UNION:
+                PyErr_SetString (PyExc_NotImplementedError, "setting an union is not supported yet");
+                goto out;
+            case GI_INFO_TYPE_STRUCT:
+            {
+                gboolean is_simple;
+                gsize offset;
+                gssize size;
+
+                is_simple = pygi_g_struct_info_is_simple ( (GIStructInfo *) info);
+
+                if (!is_simple) {
+                    PyErr_SetString (PyExc_TypeError,
+                                     "cannot set a structure which has no well-defined ownership transfer rules");
+                    g_base_info_unref (info);
+                    goto out;
+                }
+
+                value = _pygi_argument_from_object (py_value, field_type_info, GI_TRANSFER_NOTHING);
+                if (PyErr_Occurred()) {
+                    g_base_info_unref (info);
+                    goto out;
+                }
+
+                offset = g_field_info_get_offset ( (GIFieldInfo *) self->info);
+                size = g_struct_info_get_size ( (GIStructInfo *) info);
+                g_assert (size > 0);
+
+                g_memmove ((char*) pointer + offset, value.v_pointer, size);
+
+                g_base_info_unref (info);
+
+                retval = Py_None;
+                goto out;
+            }
+            default:
+                /* Fallback. */
+                break;
+        }
+
+        g_base_info_unref (info);
+    } else if (g_type_info_is_pointer (field_type_info)
+            && (g_type_info_get_tag (field_type_info) == GI_TYPE_TAG_VOID
+                || g_type_info_get_tag (field_type_info) == GI_TYPE_TAG_UTF8)) {
+        int offset;
+        value = _pygi_argument_from_object (py_value, field_type_info, GI_TRANSFER_NOTHING);
+        if (PyErr_Occurred()) {
+            goto out;
+        }
+
+        offset = g_field_info_get_offset ((GIFieldInfo *) self->info);
+        G_STRUCT_MEMBER (gpointer, pointer, offset) = (gpointer)value.v_pointer;
+
+        retval = Py_None;
+        goto out;
+    }
+
+    value = _pygi_argument_from_object (py_value, field_type_info, GI_TRANSFER_EVERYTHING);
+    if (PyErr_Occurred()) {
+        goto out;
+    }
+
+    if (!g_field_info_set_field ( (GIFieldInfo *) self->info, pointer, &value)) {
+        _pygi_argument_release (&value, field_type_info, GI_TRANSFER_NOTHING, GI_DIRECTION_IN);
+        PyErr_SetString (PyExc_RuntimeError, "unable to set value for field");
+        goto out;
+    }
+
+    retval = Py_None;
+
+out:
+    g_base_info_unref ( (GIBaseInfo *) field_type_info);
+
+    Py_XINCREF (retval);
+    return retval;
+}
+
+static PyObject *
+_wrap_g_field_info_get_flags (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (g_field_info_get_flags (self->info));
+}
+
+static PyObject *
+_wrap_g_field_info_get_size (PyGIBaseInfo *self)
+{
+    return pygi_gint_to_py (g_field_info_get_size (self->info));
+}
+
+static PyObject *
+_wrap_g_field_info_get_offset (PyGIBaseInfo *self)
+{
+    return pygi_gint_to_py (g_field_info_get_offset (self->info));
+}
+
+static PyObject *
+_wrap_g_field_info_get_type (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_field_info_get_type);
+}
+
+static PyMethodDef _PyGIFieldInfo_methods[] = {
+    { "get_value", (PyCFunction) _wrap_g_field_info_get_value, METH_VARARGS },
+    { "set_value", (PyCFunction) _wrap_g_field_info_set_value, METH_VARARGS },
+    { "get_flags", (PyCFunction) _wrap_g_field_info_get_flags, METH_VARARGS },
+    { "get_size", (PyCFunction) _wrap_g_field_info_get_size, METH_VARARGS },
+    { "get_offset", (PyCFunction) _wrap_g_field_info_get_offset, METH_VARARGS },
+    { "get_type", (PyCFunction) _wrap_g_field_info_get_type, METH_VARARGS },
+    { NULL, NULL, 0 }
+};
+
+
+/* GIUnresolvedInfo */
+PYGLIB_DEFINE_TYPE ("gi.UnresolvedInfo", PyGIUnresolvedInfo_Type, PyGIBaseInfo);
+
+static PyMethodDef _PyGIUnresolvedInfo_methods[] = {
+    { NULL, NULL, 0 }
+};
+
+/* GIVFuncInfo */
+PYGLIB_DEFINE_TYPE ("gi.VFuncInfo", PyGIVFuncInfo_Type, PyGICallableInfo);
+
+static PyObject *
+_wrap_g_vfunc_info_get_flags (PyGIBaseInfo *self)
+{
+    return pygi_guint_to_py (g_vfunc_info_get_flags ((GIVFuncInfo *) self->info));
+}
+
+static PyObject *
+_wrap_g_vfunc_info_get_offset (PyGIBaseInfo *self)
+{
+    return pygi_gint_to_py (g_vfunc_info_get_offset ((GIVFuncInfo *) self->info));
+}
+
+static PyObject *
+_wrap_g_vfunc_info_get_signal (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_vfunc_info_get_signal);
+}
+
+static PyObject *
+_wrap_g_vfunc_info_get_invoker (PyGIBaseInfo *self)
+{
+    return _get_child_info (self, g_vfunc_info_get_invoker);
+}
+
+static PyMethodDef _PyGIVFuncInfo_methods[] = {
+    { "get_flags", (PyCFunction) _wrap_g_vfunc_info_get_flags, METH_NOARGS },
+    { "get_offset", (PyCFunction) _wrap_g_vfunc_info_get_offset, METH_NOARGS },
+    { "get_signal", (PyCFunction) _wrap_g_vfunc_info_get_signal, METH_NOARGS },
+    { "get_invoker", (PyCFunction) _wrap_g_vfunc_info_get_invoker, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+
+/* GIUnionInfo */
+PYGLIB_DEFINE_TYPE ("gi.UnionInfo", PyGIUnionInfo_Type, PyGIBaseInfo);
+
+static PyObject *
+_wrap_g_union_info_get_fields (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_union_info_get_n_fields, g_union_info_get_field);
+}
+
+static PyObject *
+_wrap_g_union_info_get_methods (PyGIBaseInfo *self)
+{
+    return _make_infos_tuple (self, g_union_info_get_n_methods, g_union_info_get_method);
+}
+
+static PyObject *
+_wrap_g_union_info_get_size (PyGIBaseInfo *self)
+{
+    return pygi_gsize_to_py (g_union_info_get_size (self->info));
+}
+
+static PyMethodDef _PyGIUnionInfo_methods[] = {
+    { "get_fields", (PyCFunction) _wrap_g_union_info_get_fields, METH_NOARGS },
+    { "get_methods", (PyCFunction) _wrap_g_union_info_get_methods, METH_NOARGS },
+    { "get_size", (PyCFunction) _wrap_g_union_info_get_size, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+/* Private */
+
+gchar *
+_pygi_g_base_info_get_fullname (GIBaseInfo *info)
+{
+    GIBaseInfo *container_info;
+    gchar *fullname;
+
+    container_info = g_base_info_get_container (info);
+    if (container_info != NULL) {
+        fullname = g_strdup_printf ("%s.%s.%s",
+                                    g_base_info_get_namespace (container_info),
+                                    _safe_base_info_get_name (container_info),
+                                    _safe_base_info_get_name (info));
+    } else {
+        fullname = g_strdup_printf ("%s.%s",
+                                    g_base_info_get_namespace (info),
+                                    _safe_base_info_get_name (info));
+    }
+
+    if (fullname == NULL) {
+        PyErr_NoMemory();
+    }
+
+    return fullname;
+}
+
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_info_register_types (PyObject *m)
+{
+#define _PyGI_REGISTER_TYPE(m, type, cname, base) \
+    Py_TYPE(&type) = &PyType_Type; \
+    type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE); \
+    type.tp_weaklistoffset = offsetof(PyGIBaseInfo, inst_weakreflist); \
+    type.tp_methods = _PyGI##cname##_methods; \
+    type.tp_base = &base; \
+    if (PyType_Ready(&type) < 0) \
+        return -1; \
+    Py_INCREF ((PyObject *)&type); \
+    if (PyModule_AddObject(m, #cname, (PyObject *)&type) < 0) { \
+        Py_DECREF ((PyObject *)&type); \
+        return -1; \
+    };
+
+    Py_TYPE(&PyGIBaseInfo_Type) = &PyType_Type;
+
+    PyGIBaseInfo_Type.tp_dealloc = (destructor) _base_info_dealloc;
+    PyGIBaseInfo_Type.tp_repr = (reprfunc) _base_info_repr;
+    PyGIBaseInfo_Type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE);
+    PyGIBaseInfo_Type.tp_weaklistoffset = offsetof(PyGIBaseInfo, inst_weakreflist);
+    PyGIBaseInfo_Type.tp_methods = _PyGIBaseInfo_methods;
+    PyGIBaseInfo_Type.tp_richcompare = (richcmpfunc)_base_info_richcompare;
+    PyGIBaseInfo_Type.tp_getset = _base_info_getsets;
+    PyGIBaseInfo_Type.tp_getattro = (getattrofunc) _base_info_getattro;
+
+    if (PyType_Ready(&PyGIBaseInfo_Type) < 0)
+        return -1;
+    Py_INCREF ((PyObject *)&PyGIBaseInfo_Type);
+    if (PyModule_AddObject(m, "BaseInfo", (PyObject *)&PyGIBaseInfo_Type) < 0) {
+        Py_DECREF ((PyObject *)&PyGIBaseInfo_Type);
+        return -1;
+    }
+
+    PyGICallableInfo_Type.tp_call = (ternaryfunc) _callable_info_call;
+    PyGICallableInfo_Type.tp_dealloc = (destructor) _callable_info_dealloc;
+    _PyGI_REGISTER_TYPE (m, PyGICallableInfo_Type, CallableInfo,
+                         PyGIBaseInfo_Type);
+
+    // FIXME: this is to work around a pylint issue
+    // https://gitlab.gnome.org/GNOME/pygobject/issues/217
+#ifndef PYPY_VERSION
+    _PyGI_REGISTER_TYPE (m, PyGIFunctionInfo_Type, FunctionInfo,
+                         PyGICallableInfo_Type);
+#endif
+    PyGIFunctionInfo_Type.tp_call = (ternaryfunc) _function_info_call;
+    PyGIFunctionInfo_Type.tp_descr_get = (descrgetfunc) _function_info_descr_get;
+#ifdef PYPY_VERSION
+    _PyGI_REGISTER_TYPE (m, PyGIFunctionInfo_Type, FunctionInfo,
+                         PyGICallableInfo_Type);
+#endif
+
+    PyGIVFuncInfo_Type.tp_descr_get = (descrgetfunc) _vfunc_info_descr_get;
+    _PyGI_REGISTER_TYPE (m, PyGIVFuncInfo_Type, VFuncInfo,
+                         PyGICallableInfo_Type);
+
+    _PyGI_REGISTER_TYPE (m, PyGISignalInfo_Type, SignalInfo,
+                         PyGICallableInfo_Type);
+
+    _PyGI_REGISTER_TYPE (m, PyGIUnresolvedInfo_Type, UnresolvedInfo,
+                         PyGIBaseInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGICallbackInfo_Type, CallbackInfo,
+                         PyGICallableInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIRegisteredTypeInfo_Type, RegisteredTypeInfo,
+                         PyGIBaseInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIStructInfo_Type, StructInfo,
+                         PyGIRegisteredTypeInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIEnumInfo_Type, EnumInfo,
+                         PyGIRegisteredTypeInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIObjectInfo_Type, ObjectInfo,
+                         PyGIRegisteredTypeInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIInterfaceInfo_Type, InterfaceInfo,
+                         PyGIRegisteredTypeInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIConstantInfo_Type, ConstantInfo,
+                         PyGIBaseInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIValueInfo_Type, ValueInfo,
+                         PyGIBaseInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIFieldInfo_Type, FieldInfo,
+                         PyGIBaseInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIUnionInfo_Type, UnionInfo,
+                         PyGIRegisteredTypeInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIErrorDomainInfo_Type, ErrorDomainInfo,
+                         PyGIBaseInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIPropertyInfo_Type, PropertyInfo,
+                         PyGIBaseInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGIArgInfo_Type, ArgInfo,
+                         PyGIBaseInfo_Type);
+    _PyGI_REGISTER_TYPE (m, PyGITypeInfo_Type, TypeInfo,
+                         PyGIBaseInfo_Type);
+
+#undef _PyGI_REGISTER_TYPE
+
+#define _PyGI_ENUM_BEGIN(name) \
+        { \
+            const char *__enum_name = #name; \
+            PyObject *__enum_value = NULL; \
+            PyObject *__new_enum_cls = NULL; \
+            PyObject *__enum_instance_dict = PyDict_New(); \
+            PyObject *__module_name = PyObject_GetAttrString (m, "__name__"); \
+            PyDict_SetItemString (__enum_instance_dict, "__module__", __module_name); \
+            Py_DECREF (__module_name);
+
+#define _PyGI_ENUM_ADD_VALUE(prefix, name) \
+            __enum_value = pygi_guint_to_py (prefix##_##name); \
+            if (PyDict_SetItemString(__enum_instance_dict, #name, __enum_value) < 0) { \
+                Py_DECREF (__enum_instance_dict); \
+                Py_DECREF (__enum_value); \
+                return -1; \
+            } \
+            Py_DECREF (__enum_value);
+
+#define _PyGI_ENUM_END \
+            __new_enum_cls = PyObject_CallFunction ((PyObject *)&PyType_Type, "s(O)O", \
+                                                    __enum_name, (PyObject *)&PyType_Type, \
+                                                    __enum_instance_dict); \
+            Py_DECREF (__enum_instance_dict); \
+            PyModule_AddObject (m, __enum_name, __new_enum_cls); /* steals ref */ \
+        }
+
+
+    /* GIDirection */
+    _PyGI_ENUM_BEGIN (Direction)
+        _PyGI_ENUM_ADD_VALUE (GI_DIRECTION, IN)
+        _PyGI_ENUM_ADD_VALUE (GI_DIRECTION, OUT)
+        _PyGI_ENUM_ADD_VALUE (GI_DIRECTION, INOUT)
+    _PyGI_ENUM_END
+
+
+    /* GITransfer */
+    _PyGI_ENUM_BEGIN (Transfer)
+        _PyGI_ENUM_ADD_VALUE (GI_TRANSFER, NOTHING)
+        _PyGI_ENUM_ADD_VALUE (GI_TRANSFER, CONTAINER)
+        _PyGI_ENUM_ADD_VALUE (GI_TRANSFER, EVERYTHING)
+    _PyGI_ENUM_END
+
+    /* GIArrayType */
+    _PyGI_ENUM_BEGIN (ArrayType)
+        _PyGI_ENUM_ADD_VALUE (GI_ARRAY_TYPE, C)
+        _PyGI_ENUM_ADD_VALUE (GI_ARRAY_TYPE, ARRAY)
+        _PyGI_ENUM_ADD_VALUE (GI_ARRAY_TYPE, PTR_ARRAY)
+        _PyGI_ENUM_ADD_VALUE (GI_ARRAY_TYPE, BYTE_ARRAY)
+    _PyGI_ENUM_END
+
+    /* GIScopeType */
+    _PyGI_ENUM_BEGIN (ScopeType)
+        _PyGI_ENUM_ADD_VALUE (GI_SCOPE_TYPE, INVALID)
+        _PyGI_ENUM_ADD_VALUE (GI_SCOPE_TYPE, CALL)
+        _PyGI_ENUM_ADD_VALUE (GI_SCOPE_TYPE, ASYNC)
+        _PyGI_ENUM_ADD_VALUE (GI_SCOPE_TYPE, NOTIFIED)
+    _PyGI_ENUM_END
+
+    /* GIVFuncInfoFlags */
+    _PyGI_ENUM_BEGIN (VFuncInfoFlags)
+        _PyGI_ENUM_ADD_VALUE (GI_VFUNC_MUST, CHAIN_UP)
+        _PyGI_ENUM_ADD_VALUE (GI_VFUNC_MUST, OVERRIDE)
+        _PyGI_ENUM_ADD_VALUE (GI_VFUNC_MUST, NOT_OVERRIDE)
+    _PyGI_ENUM_END
+
+    /* GIFieldInfoFlags */
+    _PyGI_ENUM_BEGIN (FieldInfoFlags)
+        _PyGI_ENUM_ADD_VALUE (GI_FIELD, IS_READABLE)
+        _PyGI_ENUM_ADD_VALUE (GI_FIELD, IS_WRITABLE)
+    _PyGI_ENUM_END
+
+    /* GIFunctionInfoFlags */
+    _PyGI_ENUM_BEGIN (FunctionInfoFlags)
+        _PyGI_ENUM_ADD_VALUE (GI_FUNCTION, IS_METHOD)
+        _PyGI_ENUM_ADD_VALUE (GI_FUNCTION, IS_CONSTRUCTOR)
+        _PyGI_ENUM_ADD_VALUE (GI_FUNCTION, IS_GETTER)
+        _PyGI_ENUM_ADD_VALUE (GI_FUNCTION, IS_SETTER)
+        _PyGI_ENUM_ADD_VALUE (GI_FUNCTION, WRAPS_VFUNC)
+        _PyGI_ENUM_ADD_VALUE (GI_FUNCTION, THROWS)
+    _PyGI_ENUM_END
+
+    /* GITypeTag */
+    _PyGI_ENUM_BEGIN (TypeTag)
+        /* Basic types */
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, VOID)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, BOOLEAN)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, INT8)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, UINT8)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, INT16)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, UINT16)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, INT32)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, UINT32)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, INT64)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, UINT64)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, FLOAT)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, DOUBLE)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, GTYPE)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, UTF8)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, FILENAME)
+
+        /* Non-basic types; compare with G_TYPE_TAG_IS_BASIC */
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, ARRAY)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, INTERFACE)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, GLIST)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, GSLIST)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, GHASH)
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, ERROR)
+
+        /* Another basic type */
+        _PyGI_ENUM_ADD_VALUE (GI_TYPE_TAG, UNICHAR)
+    _PyGI_ENUM_END
+
+    /* GIInfoType */
+    _PyGI_ENUM_BEGIN (InfoType)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, INVALID)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, FUNCTION)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, CALLBACK)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, STRUCT)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, BOXED)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, ENUM)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, FLAGS)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, OBJECT)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, INTERFACE)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, CONSTANT)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, INVALID_0)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, UNION)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, VALUE)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, SIGNAL)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, VFUNC)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, PROPERTY)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, FIELD)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, ARG)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, TYPE)
+        _PyGI_ENUM_ADD_VALUE (GI_INFO_TYPE, UNRESOLVED)
+    _PyGI_ENUM_END
+
+#undef _PyGI_ENUM_BEGIN
+#undef _PyGI_ENUM_ADD_VALUE
+#undef _PyGI_ENUM_END
+
+    return 0;
+}
diff --git a/gi/pygi-info.h b/gi/pygi-info.h
new file mode 100644 (file)
index 0000000..12ef491
--- /dev/null
@@ -0,0 +1,95 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_INFO_H__
+#define __PYGI_INFO_H__
+
+#include <Python.h>
+
+#include <girepository.h>
+#include "pygi-cache.h"
+
+G_BEGIN_DECLS
+
+typedef struct {
+    PyObject_HEAD
+    GIBaseInfo *info;
+    PyObject *inst_weakreflist;
+    PyGICallableCache *cache;
+} PyGIBaseInfo;
+
+typedef struct {
+    PyGIBaseInfo base;
+
+    /* Reference the unbound version of this struct.
+     * We use this for the actual call to invoke because it manages the cache.
+     */
+    struct PyGICallableInfo *py_unbound_info;
+
+    /* Holds bound argument for instance, class, and vfunc methods. */
+    PyObject *py_bound_arg;
+
+} PyGICallableInfo;
+
+
+gboolean pygi_g_struct_info_is_simple (GIStructInfo *struct_info);
+
+
+/* Private */
+
+extern PyTypeObject PyGIBaseInfo_Type;
+extern PyTypeObject PyGICallableInfo_Type;
+extern PyTypeObject PyGICallbackInfo_Type;
+extern PyTypeObject PyGIFunctionInfo_Type;
+extern PyTypeObject PyGIRegisteredTypeInfo_Type;
+extern PyTypeObject PyGIStructInfo_Type;
+extern PyTypeObject PyGIEnumInfo_Type;
+extern PyTypeObject PyGIObjectInfo_Type;
+extern PyTypeObject PyGIInterfaceInfo_Type;
+extern PyTypeObject PyGIConstantInfo_Type;
+extern PyTypeObject PyGIValueInfo_Type;
+extern PyTypeObject PyGIFieldInfo_Type;
+extern PyTypeObject PyGIUnresolvedInfo_Type;
+extern PyTypeObject PyGIVFuncInfo_Type;
+extern PyTypeObject PyGIUnionInfo_Type;
+extern PyTypeObject PyGIBoxedInfo_Type;
+extern PyTypeObject PyGIErrorDomainInfo_Type;
+extern PyTypeObject PyGISignalInfo_Type;
+extern PyTypeObject PyGIPropertyInfo_Type;
+extern PyTypeObject PyGIArgInfo_Type;
+extern PyTypeObject PyGITypeInfo_Type;
+
+#define PyGIBaseInfo_GET_GI_INFO(object) g_base_info_ref(((PyGIBaseInfo *)object)->info)
+
+PyObject* _pygi_info_new (GIBaseInfo *info);
+GIBaseInfo* _pygi_object_get_gi_info (PyObject     *object,
+                                      PyTypeObject *type);
+
+gchar* _pygi_g_base_info_get_fullname (GIBaseInfo *info);
+
+gsize _pygi_g_type_tag_size (GITypeTag type_tag);
+gsize _pygi_g_type_info_size (GITypeInfo *type_info);
+
+int pygi_info_register_types (PyObject *m);
+
+gboolean _pygi_is_python_keyword (const gchar *name);
+
+G_END_DECLS
+
+#endif /* __PYGI_INFO_H__ */
diff --git a/gi/pygi-invoke-state-struct.h b/gi/pygi-invoke-state-struct.h
new file mode 100644 (file)
index 0000000..dbf4e66
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef __PYGI_INVOKE_STATE_STRUCT_H__
+#define __PYGI_INVOKE_STATE_STRUCT_H__
+
+#include <Python.h>
+
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+typedef struct _PyGIInvokeArgState
+{
+    /* Holds memory for the C value of arguments marshaled "to" or "from" Python. */
+    GIArgument arg_value;
+
+    /* Holds pointers to values in arg_values or a caller allocated chunk of
+     * memory via arg_pointer.v_pointer.
+     */
+    GIArgument arg_pointer;
+
+    /* Holds from_py marshaler cleanup data. */
+    gpointer arg_cleanup_data;
+
+    /* Holds to_py marshaler cleanup data. */
+    gpointer to_py_arg_cleanup_data;
+} PyGIInvokeArgState;
+
+
+typedef struct _PyGIInvokeState
+{
+    PyObject *py_in_args;
+    gssize n_py_in_args;
+
+    /* Number of arguments the ffi wrapped C function takes. Used as the exact
+     * count for argument related arrays held in this struct.
+     */
+    gssize n_args;
+
+    /* List of arguments passed to ffi. Elements can point directly to values held in
+     * arg_values for "in/from Python" or indirectly via arg_pointers for
+     * "out/inout/to Python". In the latter case, the args[x].arg_pointer.v_pointer
+     * member points to memory for the value storage.
+     */
+    GIArgument **ffi_args;
+
+    /* Array of size n_args containing per argument state */
+    PyGIInvokeArgState *args;
+
+    /* Memory to receive the result of the C ffi function call. */
+    GIArgument return_arg;
+    gpointer to_py_return_arg_cleanup_data;
+
+    /* A GError exception which is indirectly bound into the last position of
+     * the "args" array if the callable caches "throws" member is set.
+     */
+    GError *error;
+
+    gboolean failed;
+
+    gpointer user_data;
+
+    /* Function pointer to call with ffi. */
+    gpointer function_ptr;
+
+} PyGIInvokeState;
+
+G_END_DECLS
+
+#endif
diff --git a/gi/pygi-invoke.c b/gi/pygi-invoke.c
new file mode 100644 (file)
index 0000000..d5956c6
--- /dev/null
@@ -0,0 +1,771 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+ * Copyright (C) 2011 John (J5) Palimier <johnp@redhat.com>
+ *
+ *   pygi-invoke.c: main invocation function
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pygi-invoke.h"
+#include "pygi-marshal-cleanup.h"
+#include "pygi-error.h"
+#include "pygi-resulttuple.h"
+#include "pygi-foreign.h"
+#include "pygi-boxed.h"
+
+extern PyObject *_PyGIDefaultArgPlaceholder;
+
+static gboolean
+_check_for_unexpected_kwargs (PyGICallableCache *cache,
+                              GHashTable  *arg_name_hash,
+                              PyObject    *py_kwargs)
+{
+    PyObject *dict_key, *dict_value;
+    Py_ssize_t dict_iter_pos = 0;
+
+    while (PyDict_Next (py_kwargs, &dict_iter_pos, &dict_key, &dict_value)) {
+        PyObject *key;
+
+#if PY_VERSION_HEX < 0x03000000
+        if (PyString_Check (dict_key)) {
+            Py_INCREF (dict_key);
+            key = dict_key;
+        } else
+#endif
+        {
+            key = PyUnicode_AsUTF8String (dict_key);
+            if (key == NULL) {
+                return FALSE;
+            }
+        }
+
+        /* Use extended lookup because it returns whether or not the key actually
+         * exists in the hash table. g_hash_table_lookup returns NULL for keys not
+         * found which maps to index 0 for our hash lookup.
+         */
+        if (!g_hash_table_lookup_extended (arg_name_hash, PyBytes_AsString(key), NULL, NULL)) {
+            char *full_name = pygi_callable_cache_get_full_name (cache);
+            PyErr_Format (PyExc_TypeError,
+                          "%.200s() got an unexpected keyword argument '%.400s'",
+                          full_name,
+                          PyBytes_AsString (key));
+            Py_DECREF (key);
+            g_free (full_name);
+            return FALSE;
+        }
+
+        Py_DECREF (key);
+    }
+    return TRUE;
+}
+
+/**
+ * _py_args_combine_and_check_length:
+ * @cache: PyGICallableCache
+ * @py_args: the tuple of positional arguments.
+ * @py_kwargs: the dict of keyword arguments to be merged with py_args.
+ *
+ * Returns: New value reference to the combined py_args and py_kwargs.
+ */
+static PyObject *
+_py_args_combine_and_check_length (PyGICallableCache *cache,
+                                   PyObject    *py_args,
+                                   PyObject    *py_kwargs)
+{
+    PyObject *combined_py_args = NULL;
+    Py_ssize_t n_py_args, n_py_kwargs, i;
+    gssize n_expected_args = cache->n_py_args;
+    GSList *l;
+
+    n_py_args = PyTuple_GET_SIZE (py_args);
+    if (py_kwargs == NULL)
+        n_py_kwargs = 0;
+    else
+        n_py_kwargs = PyDict_Size (py_kwargs);
+
+    /* Fast path, we already have the exact number of args and not kwargs. */
+    if (n_py_kwargs == 0 && n_py_args == n_expected_args && cache->user_data_varargs_index < 0) {
+        Py_INCREF (py_args);
+        return py_args;
+    }
+
+    if (cache->user_data_varargs_index < 0 && n_expected_args < n_py_args) {
+        char *full_name = pygi_callable_cache_get_full_name (cache);
+        PyErr_Format (PyExc_TypeError,
+                      "%.200s() takes exactly %zd %sargument%s (%zd given)",
+                      full_name,
+                      n_expected_args,
+                      n_py_kwargs > 0 ? "non-keyword " : "",
+                      n_expected_args == 1 ? "" : "s",
+                      n_py_args);
+        g_free (full_name);
+        return NULL;
+    }
+
+    if (cache->user_data_varargs_index >= 0 && n_py_kwargs > 0 && n_expected_args < n_py_args) {
+        char *full_name = pygi_callable_cache_get_full_name (cache);
+        PyErr_Format (PyExc_TypeError,
+                      "%.200s() cannot use variable user data arguments with keyword arguments",
+                      full_name);
+        g_free (full_name);
+        return NULL;
+    }
+
+    if (n_py_kwargs > 0 && !_check_for_unexpected_kwargs (cache,
+                                                          cache->arg_name_hash,
+                                                          py_kwargs)) {
+        return NULL;
+    }
+
+    /* will hold arguments from both py_args and py_kwargs
+     * when they are combined into a single tuple */
+    combined_py_args = PyTuple_New (n_expected_args);
+
+    for (i = 0, l = cache->arg_name_list; i < n_expected_args && l; i++, l = l->next) {
+        PyObject *py_arg_item = NULL;
+        PyObject *kw_arg_item = NULL;
+        const gchar *arg_name = l->data;
+        int arg_cache_index = -1;
+        gboolean is_varargs_user_data = FALSE;
+
+        if (arg_name != NULL)
+            arg_cache_index = GPOINTER_TO_INT (g_hash_table_lookup (cache->arg_name_hash, arg_name));
+
+        is_varargs_user_data = cache->user_data_varargs_index >= 0 &&
+                                arg_cache_index == cache->user_data_varargs_index;
+
+        if (n_py_kwargs > 0 && arg_name != NULL) {
+            /* NULL means this argument has no keyword name */
+            /* ex. the first argument to a method or constructor */
+            kw_arg_item = PyDict_GetItemString (py_kwargs, arg_name);
+        }
+
+        /* use a bounded retrieval of the original input */
+        if (i < n_py_args)
+            py_arg_item = PyTuple_GET_ITEM (py_args, i);
+
+        if (kw_arg_item == NULL && py_arg_item != NULL) {
+            if (is_varargs_user_data) {
+                /* For tail end user_data varargs, pull a slice off and we are done. */
+                PyObject *user_data = PyTuple_GetSlice (py_args, i, PY_SSIZE_T_MAX);
+                PyTuple_SET_ITEM (combined_py_args, i, user_data);
+                return combined_py_args;
+            } else {
+                Py_INCREF (py_arg_item);
+                PyTuple_SET_ITEM (combined_py_args, i, py_arg_item);
+            }
+        } else if (kw_arg_item != NULL && py_arg_item == NULL) {
+            if (is_varargs_user_data) {
+                /* Special case where user_data is passed as a keyword argument (user_data=foo)
+                 * Wrap the value in a tuple to represent variable args for marshaling later on.
+                 */
+                PyObject *user_data = Py_BuildValue("(O)", kw_arg_item, NULL);
+                PyTuple_SET_ITEM (combined_py_args, i, user_data);
+            } else {
+                Py_INCREF (kw_arg_item);
+                PyTuple_SET_ITEM (combined_py_args, i, kw_arg_item);
+            }
+
+        } else if (kw_arg_item == NULL && py_arg_item == NULL) {
+            if (is_varargs_user_data) {
+                /* For varargs user_data, pass an empty tuple when nothing is given. */
+                PyTuple_SET_ITEM (combined_py_args, i, PyTuple_New (0));
+            } else if (arg_cache_index >= 0 && _pygi_callable_cache_get_arg (cache, arg_cache_index)->has_default) {
+                /* If the argument supports a default, use a place holder in the
+                 * argument tuple, this will be checked later during marshaling.
+                 */
+                Py_INCREF (_PyGIDefaultArgPlaceholder);
+                PyTuple_SET_ITEM (combined_py_args, i, _PyGIDefaultArgPlaceholder);
+            } else {
+                char *full_name = pygi_callable_cache_get_full_name (cache);
+                PyErr_Format (PyExc_TypeError,
+                              "%.200s() takes exactly %zd %sargument%s (%zd given)",
+                              full_name,
+                              n_expected_args,
+                              n_py_kwargs > 0 ? "non-keyword " : "",
+                              n_expected_args == 1 ? "" : "s",
+                              n_py_args);
+                g_free (full_name);
+
+                Py_DECREF (combined_py_args);
+                return NULL;
+            }
+        } else if (kw_arg_item != NULL && py_arg_item != NULL) {
+            char *full_name = pygi_callable_cache_get_full_name (cache);
+            PyErr_Format (PyExc_TypeError,
+                          "%.200s() got multiple values for keyword argument '%.200s'",
+                          full_name,
+                          arg_name);
+
+            Py_DECREF (combined_py_args);
+            g_free (full_name);
+            return NULL;
+        }
+    }
+
+    return combined_py_args;
+}
+
+/* To reduce calls to g_slice_*() we (1) allocate all the memory depended on
+ * the argument count in one go and (2) keep one version per argument count
+ * around for faster reuse.
+ */
+
+#define PyGI_INVOKE_ARG_STATE_SIZE(n)   (n * (sizeof (PyGIInvokeArgState) + sizeof (GIArgument *)))
+#define PyGI_INVOKE_ARG_STATE_N_MAX     10
+static gpointer free_arg_state[PyGI_INVOKE_ARG_STATE_N_MAX];
+
+/**
+ * _pygi_invoke_arg_state_init:
+ * Sets PyGIInvokeState.args and PyGIInvokeState.ffi_args.
+ * On error returns FALSE and sets an exception.
+ */
+gboolean
+_pygi_invoke_arg_state_init (PyGIInvokeState *state) {
+
+    gpointer mem;
+
+    if (state->n_args < PyGI_INVOKE_ARG_STATE_N_MAX && (mem = free_arg_state[state->n_args]) != NULL) {
+        free_arg_state[state->n_args] = NULL;
+        memset (mem, 0, PyGI_INVOKE_ARG_STATE_SIZE (state->n_args));
+    } else {
+        mem = g_slice_alloc0 (PyGI_INVOKE_ARG_STATE_SIZE (state->n_args));
+    }
+
+    if (mem == NULL && state->n_args != 0) {
+        PyErr_NoMemory();
+        return FALSE;
+    }
+
+    if (mem != NULL) {
+        state->args = mem;
+        state->ffi_args = (gpointer)((gchar *)mem + state->n_args * sizeof (PyGIInvokeArgState));
+    }
+
+    return TRUE;
+}
+
+/**
+ * _pygi_invoke_arg_state_free:
+ * Frees PyGIInvokeState.args and PyGIInvokeState.ffi_args
+ */
+void
+_pygi_invoke_arg_state_free(PyGIInvokeState *state) {
+    if (state->n_args < PyGI_INVOKE_ARG_STATE_N_MAX && free_arg_state[state->n_args] == NULL) {
+        free_arg_state[state->n_args] = state->args;
+        return;
+    }
+
+    g_slice_free1 (PyGI_INVOKE_ARG_STATE_SIZE (state->n_args), state->args);
+}
+
+static gboolean
+_invoke_state_init_from_cache (PyGIInvokeState *state,
+                               PyGIFunctionCache *function_cache,
+                               PyObject *py_args,
+                               PyObject *kwargs)
+{
+    PyGICallableCache *cache = (PyGICallableCache *) function_cache;
+
+    state->n_args = _pygi_callable_cache_args_len (cache);
+
+    if (cache->throws) {
+        state->n_args++;
+    }
+
+    /* Copy the function pointer to the state for the normal case. For vfuncs,
+     * this has already been filled out based on the implementor's GType.
+     */
+    if (state->function_ptr == NULL)
+        state->function_ptr = function_cache->invoker.native_address;
+
+    state->py_in_args = _py_args_combine_and_check_length (cache,
+                                                           py_args,
+                                                           kwargs);
+
+    if (state->py_in_args == NULL) {
+        return FALSE;
+    }
+    state->n_py_in_args = PyTuple_Size (state->py_in_args);
+
+    if (!_pygi_invoke_arg_state_init (state)) {
+        return FALSE;
+    }
+
+    state->error = NULL;
+
+    if (cache->throws) {
+        gssize error_index = state->n_args - 1;
+        /* The ffi argument for GError needs to be a triple pointer. */
+        state->args[error_index].arg_pointer.v_pointer = &state->error;
+        state->ffi_args[error_index] = &(state->args[error_index].arg_pointer);
+    }
+
+    return TRUE;
+}
+
+static void
+_invoke_state_clear (PyGIInvokeState *state, PyGIFunctionCache *function_cache)
+{
+    _pygi_invoke_arg_state_free (state);
+    Py_XDECREF (state->py_in_args);
+}
+
+static gboolean
+_caller_alloc (PyGIArgCache *arg_cache, GIArgument *arg)
+{
+    if (arg_cache->type_tag == GI_TYPE_TAG_INTERFACE) {
+        PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)arg_cache;
+
+        arg->v_pointer = NULL;
+        if (g_type_is_a (iface_cache->g_type, G_TYPE_BOXED)) {
+            arg->v_pointer =
+                pygi_boxed_alloc (iface_cache->interface_info, NULL);
+        } else if (iface_cache->g_type == G_TYPE_VALUE) {
+            arg->v_pointer = g_slice_new0 (GValue);
+        } else if (iface_cache->is_foreign) {
+            PyObject *foreign_struct =
+                pygi_struct_foreign_convert_from_g_argument (
+                    iface_cache->interface_info,
+                    GI_TRANSFER_NOTHING,
+                    NULL);
+
+                pygi_struct_foreign_convert_to_g_argument (foreign_struct,
+                                                           iface_cache->interface_info,
+                                                           GI_TRANSFER_EVERYTHING,
+                                                           arg);
+        } else {
+                gssize size = g_struct_info_get_size(
+                    (GIStructInfo *)iface_cache->interface_info);
+                arg->v_pointer = g_malloc0 (size);
+        }
+    } else if (arg_cache->type_tag == GI_TYPE_TAG_ARRAY) {
+        PyGIArgGArray *array_cache = (PyGIArgGArray *)arg_cache;
+
+        arg->v_pointer = g_array_new (TRUE, TRUE, (guint)array_cache->item_size);
+    } else {
+        return FALSE;
+    }
+
+    if (arg->v_pointer == NULL)
+        return FALSE;
+
+
+    return TRUE;
+}
+
+/* pygi_invoke_marshal_in_args:
+ *
+ * Fills out the state struct argument lists. arg_values will always hold
+ * actual values marshaled either to or from Python and C. arg_pointers will
+ * hold pointers (via v_pointer) to auxilary value storage. This will normally
+ * point to values stored in arg_values. In the case of caller allocated
+ * out args, arg_pointers[x].v_pointer will point to newly allocated memory.
+ * arg_pointers inserts a level of pointer indirection between arg_values
+ * and the argument list ffi receives when dealing with non-caller allocated
+ * out arguments.
+ *
+ * For example:
+ * [[
+ *  void callee (int *i, int j) { *i = 50 - j; }
+ *  void caller () {
+ *    int i = 0;
+ *    callee (&i, 8);
+ *  }
+ *
+ *  args[0] == &arg_pointers[0];
+ *  arg_pointers[0].v_pointer == &arg_values[0];
+ *  arg_values[0].v_int == 42;
+ *
+ *  args[1] == &arg_values[1];
+ *  arg_values[1].v_int == 8;
+ * ]]
+ *
+ */
+static gboolean
+_invoke_marshal_in_args (PyGIInvokeState *state, PyGIFunctionCache *function_cache)
+{
+    PyGICallableCache *cache = (PyGICallableCache *) function_cache;
+    gssize i;
+
+    if (state->n_py_in_args > cache->n_py_args) {
+        char *full_name = pygi_callable_cache_get_full_name (cache);
+        PyErr_Format (PyExc_TypeError,
+                      "%s() takes exactly %zd argument(s) (%zd given)",
+                      full_name,
+                      cache->n_py_args,
+                      state->n_py_in_args);
+        g_free (full_name);
+        return FALSE;
+    }
+
+    for (i = 0; (gsize)i < _pygi_callable_cache_args_len (cache); i++) {
+        GIArgument *c_arg = &state->args[i].arg_value;
+        PyGIArgCache *arg_cache = g_ptr_array_index (cache->args_cache, i);
+        PyObject *py_arg = NULL;
+
+        switch (arg_cache->direction) {
+            case PYGI_DIRECTION_FROM_PYTHON:
+                /* The ffi argument points directly at memory in arg_values. */
+                state->ffi_args[i] = c_arg;
+
+                if (arg_cache->meta_type == PYGI_META_ARG_TYPE_CLOSURE) {
+                    state->ffi_args[i]->v_pointer = state->user_data;
+                    continue;
+                } else if (arg_cache->meta_type != PYGI_META_ARG_TYPE_PARENT)
+                    continue;
+
+                if (arg_cache->py_arg_index >= state->n_py_in_args) {
+                    char *full_name = pygi_callable_cache_get_full_name (cache);
+                    PyErr_Format (PyExc_TypeError,
+                                  "%s() takes exactly %zd argument(s) (%zd given)",
+                                   full_name,
+                                   cache->n_py_args,
+                                   state->n_py_in_args);
+                    g_free (full_name);
+
+                    /* clean up all of the args we have already marshalled,
+                     * since invoke will not be called
+                     */
+                    pygi_marshal_cleanup_args_from_py_parameter_fail (state,
+                                                                      cache,
+                                                                      i);
+                    return FALSE;
+                }
+
+                py_arg =
+                    PyTuple_GET_ITEM (state->py_in_args,
+                                      arg_cache->py_arg_index);
+
+                break;
+            case PYGI_DIRECTION_BIDIRECTIONAL:
+                if (arg_cache->meta_type != PYGI_META_ARG_TYPE_CHILD) {
+                    if (arg_cache->py_arg_index >= state->n_py_in_args) {
+                        char *full_name = pygi_callable_cache_get_full_name (cache);
+                        PyErr_Format (PyExc_TypeError,
+                                      "%s() takes exactly %zd argument(s) (%zd given)",
+                                       full_name,
+                                       cache->n_py_args,
+                                       state->n_py_in_args);
+                        g_free (full_name);
+                        pygi_marshal_cleanup_args_from_py_parameter_fail (state,
+                                                                          cache,
+                                                                          i);
+                        return FALSE;
+                    }
+
+                    py_arg =
+                        PyTuple_GET_ITEM (state->py_in_args,
+                                          arg_cache->py_arg_index);
+                }
+                /* Fall through */
+
+            case PYGI_DIRECTION_TO_PYTHON:
+                /* arg_pointers always stores a pointer to the data to be marshaled "to python"
+                 * even in cases where arg_pointers is not being used as indirection between
+                 * ffi and arg_values. This gives a guarantee that out argument marshaling
+                 * (_invoke_marshal_out_args) can always rely on arg_pointers pointing to
+                 * the correct chunk of memory to marshal.
+                 */
+                state->args[i].arg_pointer.v_pointer = c_arg;
+
+                if (arg_cache->is_caller_allocates) {
+                    /* In the case of caller allocated out args, we don't use
+                     * an extra level of indirection and state->args will point
+                     * directly at the data to be marshaled. However, as noted
+                     * above, arg_pointers will also point to this caller allocated
+                     * chunk of memory used by out argument marshaling.
+                     */
+                    state->ffi_args[i] = c_arg;
+
+                    if (!_caller_alloc (arg_cache, c_arg)) {
+                        char *full_name = pygi_callable_cache_get_full_name (cache);
+                        PyErr_Format (PyExc_TypeError,
+                                      "Could not caller allocate argument %zd of callable %s",
+                                      i, full_name);
+                        g_free (full_name);
+                        pygi_marshal_cleanup_args_from_py_parameter_fail (state,
+                                                                          cache,
+                                                                          i);
+                        return FALSE;
+                    }
+                } else {
+                    /* Non-caller allocated out args will use arg_pointers as an
+                     * extra level of indirection */
+                    state->ffi_args[i] = &state->args[i].arg_pointer;
+                }
+
+                break;
+            default:
+                g_assert_not_reached();
+                break;
+        }
+
+        if (py_arg == _PyGIDefaultArgPlaceholder) {
+            *c_arg = arg_cache->default_value;
+        } else if (arg_cache->from_py_marshaller != NULL &&
+                   arg_cache->meta_type != PYGI_META_ARG_TYPE_CHILD) {
+            gboolean success;
+            gpointer cleanup_data = NULL;
+
+            if (!arg_cache->allow_none && py_arg == Py_None) {
+                PyErr_Format (PyExc_TypeError,
+                              "Argument %zd does not allow None as a value",
+                              i);
+
+                 pygi_marshal_cleanup_args_from_py_parameter_fail (state,
+                                                                   cache,
+                                                                   i);
+                 return FALSE;
+            }
+            success = arg_cache->from_py_marshaller (state,
+                                                     cache,
+                                                     arg_cache,
+                                                     py_arg,
+                                                     c_arg,
+                                                     &cleanup_data);
+            state->args[i].arg_cleanup_data = cleanup_data;
+
+            if (!success) {
+                pygi_marshal_cleanup_args_from_py_parameter_fail (state,
+                                                                  cache,
+                                                                  i);
+                return FALSE;
+            }
+
+        }
+
+    }
+
+    return TRUE;
+}
+
+static PyObject *
+_invoke_marshal_out_args (PyGIInvokeState *state, PyGIFunctionCache *function_cache)
+{
+    PyGICallableCache *cache = (PyGICallableCache *) function_cache;
+    PyObject *py_out = NULL;
+    PyObject *py_return = NULL;
+    gssize n_out_args = cache->n_to_py_args - cache->n_to_py_child_args;
+
+    if (cache->return_cache) {
+        if (!cache->return_cache->is_skipped) {
+            gpointer cleanup_data = NULL;
+            py_return = cache->return_cache->to_py_marshaller ( state,
+                                                                cache,
+                                                                cache->return_cache,
+                                                               &state->return_arg,
+                                                               &cleanup_data);
+            state->to_py_return_arg_cleanup_data = cleanup_data;
+            if (py_return == NULL) {
+                pygi_marshal_cleanup_args_return_fail (state,
+                                                       cache);
+                return NULL;
+            }
+        } else {
+            if (cache->return_cache->transfer == GI_TRANSFER_EVERYTHING) {
+                PyGIMarshalToPyCleanupFunc to_py_cleanup =
+                    cache->return_cache->to_py_cleanup;
+
+                if (to_py_cleanup != NULL)
+                    to_py_cleanup ( state,
+                                    cache->return_cache,
+                                    NULL,
+                                   &state->return_arg,
+                                    FALSE);
+            }
+        }
+    }
+
+    if (n_out_args == 0) {
+        if (cache->return_cache->is_skipped && state->error == NULL) {
+            /* we skip the return value and have no (out) arguments to return,
+             * so py_return should be NULL. But we must not return NULL,
+             * otherwise Python will expect an exception.
+             */
+            g_assert (py_return == NULL);
+            Py_INCREF(Py_None);
+            py_return = Py_None;
+        }
+
+        py_out = py_return;
+    } else if (!cache->has_return && n_out_args == 1) {
+        /* if we get here there is one out arg an no return */
+        PyGIArgCache *arg_cache = (PyGIArgCache *)cache->to_py_args->data;
+        gpointer cleanup_data = NULL;
+        py_out = arg_cache->to_py_marshaller (state,
+                                              cache,
+                                              arg_cache,
+                                              state->args[arg_cache->c_arg_index].arg_pointer.v_pointer,
+                                              &cleanup_data);
+        state->args[arg_cache->c_arg_index].to_py_arg_cleanup_data = cleanup_data;
+        if (py_out == NULL) {
+            pygi_marshal_cleanup_args_to_py_parameter_fail (state,
+                                                            cache,
+                                                            0);
+            return NULL;
+        }
+
+    } else {
+        /* return a tuple */
+        gssize py_arg_index = 0;
+        GSList *cache_item = cache->to_py_args;
+        gssize tuple_len = cache->has_return + n_out_args;
+
+        py_out = pygi_resulttuple_new (cache->resulttuple_type, tuple_len);
+
+        if (py_out == NULL) {
+            pygi_marshal_cleanup_args_to_py_parameter_fail (state,
+                                                            cache,
+                                                            py_arg_index);
+            return NULL;
+        }
+
+        if (cache->has_return) {
+            PyTuple_SET_ITEM (py_out, py_arg_index, py_return);
+            py_arg_index++;
+        }
+
+        for (; py_arg_index < tuple_len; py_arg_index++) {
+            PyGIArgCache *arg_cache = (PyGIArgCache *)cache_item->data;
+            gpointer cleanup_data = NULL;
+            PyObject *py_obj = arg_cache->to_py_marshaller (state,
+                                                            cache,
+                                                            arg_cache,
+                                                            state->args[arg_cache->c_arg_index].arg_pointer.v_pointer,
+                                                            &cleanup_data);
+            state->args[arg_cache->c_arg_index].to_py_arg_cleanup_data = cleanup_data;
+
+            if (py_obj == NULL) {
+                if (cache->has_return)
+                    py_arg_index--;
+
+                pygi_marshal_cleanup_args_to_py_parameter_fail (state,
+                                                                cache,
+                                                                py_arg_index);
+                Py_DECREF (py_out);
+                return NULL;
+            }
+
+            PyTuple_SET_ITEM (py_out, py_arg_index, py_obj);
+            cache_item = cache_item->next;
+        }
+    }
+    return py_out;
+}
+
+PyObject *
+pygi_invoke_c_callable (PyGIFunctionCache *function_cache,
+                        PyGIInvokeState *state,
+                        PyObject *py_args,
+                        PyObject *py_kwargs)
+{
+    PyGICallableCache *cache = (PyGICallableCache *) function_cache;
+    GIFFIReturnValue ffi_return_value = {0};
+    PyObject *ret = NULL;
+
+    if (!_invoke_state_init_from_cache (state, function_cache,
+                                        py_args, py_kwargs))
+         goto err;
+
+    if (!_invoke_marshal_in_args (state, function_cache))
+         goto err;
+
+    Py_BEGIN_ALLOW_THREADS;
+
+        ffi_call (&function_cache->invoker.cif,
+                  state->function_ptr,
+                  (void *) &ffi_return_value,
+                  (void **) state->ffi_args);
+
+    Py_END_ALLOW_THREADS;
+
+    /* If the callable throws, the address of state->error will be bound into
+     * the state->args as the last value. When the callee sets an error using
+     * the state->args passed, it will have the side effect of setting
+     * state->error allowing for easy checking here.
+     */
+    if (state->error != NULL) {
+        if (pygi_error_check (&state->error)) {
+            /* even though we errored out, the call itself was successful,
+               so we assume the call processed all of the parameters */
+            pygi_marshal_cleanup_args_from_py_marshal_success (state, cache);
+            goto err;
+        }
+    }
+
+    if (cache->return_cache) {
+        gi_type_info_extract_ffi_return_value (cache->return_cache->type_info,
+                                               &ffi_return_value,
+                                               &state->return_arg);
+    }
+
+    ret = _invoke_marshal_out_args (state, function_cache);
+    pygi_marshal_cleanup_args_from_py_marshal_success (state, cache);
+
+    if (ret != NULL)
+        pygi_marshal_cleanup_args_to_py_marshal_success (state, cache);
+
+err:
+    _invoke_state_clear (state, function_cache);
+    return ret;
+}
+
+PyObject *
+pygi_callable_info_invoke (GIBaseInfo *info, PyObject *py_args,
+                           PyObject *kwargs, PyGICallableCache *cache,
+                           gpointer user_data)
+{
+    return pygi_function_cache_invoke ((PyGIFunctionCache *) cache,
+                                       py_args, kwargs);
+}
+
+PyObject *
+_wrap_g_callable_info_invoke (PyGIBaseInfo *self, PyObject *py_args,
+                              PyObject *kwargs)
+{
+    if (self->cache == NULL) {
+        PyGIFunctionCache *function_cache;
+        GIInfoType type = g_base_info_get_type (self->info);
+
+        if (type == GI_INFO_TYPE_FUNCTION) {
+            GIFunctionInfoFlags flags;
+
+            flags = g_function_info_get_flags ( (GIFunctionInfo *)self->info);
+
+            if (flags & GI_FUNCTION_IS_CONSTRUCTOR) {
+                function_cache = pygi_constructor_cache_new (self->info);
+            } else if (flags & GI_FUNCTION_IS_METHOD) {
+                function_cache = pygi_method_cache_new (self->info);
+            } else {
+                function_cache = pygi_function_cache_new (self->info);
+            }
+        } else if (type == GI_INFO_TYPE_VFUNC) {
+            function_cache = pygi_vfunc_cache_new (self->info);
+        } else if (type == GI_INFO_TYPE_CALLBACK) {
+            g_error ("Cannot invoke callback types");
+        } else {
+            function_cache = pygi_method_cache_new (self->info);
+        }
+
+        self->cache = (PyGICallableCache *)function_cache;
+        if (self->cache == NULL)
+            return NULL;
+    }
+
+    return pygi_callable_info_invoke (self->info, py_args, kwargs, self->cache, NULL);
+}
diff --git a/gi/pygi-invoke.h b/gi/pygi-invoke.h
new file mode 100644 (file)
index 0000000..aa51f3f
--- /dev/null
@@ -0,0 +1,47 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_INVOKE_H__
+#define __PYGI_INVOKE_H__
+
+#include <Python.h>
+
+#include <girepository.h>
+
+#include "pygi-info.h"
+#include "pygi-invoke-state-struct.h"
+
+G_BEGIN_DECLS
+
+PyObject *pygi_invoke_c_callable    (PyGIFunctionCache *function_cache,
+                                     PyGIInvokeState *state,
+                                     PyObject *py_args, PyObject *py_kwargs);
+PyObject *pygi_callable_info_invoke (GIBaseInfo *info, PyObject *py_args,
+                                     PyObject *kwargs, PyGICallableCache *cache,
+                                     gpointer user_data);
+PyObject *_wrap_g_callable_info_invoke (PyGIBaseInfo *self, PyObject *py_args,
+                                        PyObject *kwargs);
+
+gboolean _pygi_invoke_arg_state_init (PyGIInvokeState *state);
+
+void _pygi_invoke_arg_state_free     (PyGIInvokeState *state);
+
+G_END_DECLS
+
+#endif /* __PYGI_INVOKE_H__ */
diff --git a/gi/pygi-list.c b/gi/pygi-list.c
new file mode 100644 (file)
index 0000000..712c372
--- /dev/null
@@ -0,0 +1,500 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include "pygi-list.h"
+#include "pygi-argument.h"
+#include "pygi-util.h"
+
+typedef PyGISequenceCache PyGIArgGList;
+
+/*
+ * GList and GSList from Python
+ */
+static gboolean
+_pygi_marshal_from_py_glist (PyGIInvokeState   *state,
+                             PyGICallableCache *callable_cache,
+                             PyGIArgCache      *arg_cache,
+                             PyObject          *py_arg,
+                             GIArgument        *arg,
+                             gpointer          *cleanup_data)
+{
+    PyGIMarshalFromPyFunc from_py_marshaller;
+    int i;
+    Py_ssize_t length;
+    GList *list_ = NULL;
+    PyGISequenceCache *sequence_cache = (PyGISequenceCache *)arg_cache;
+
+
+    if (py_arg == Py_None) {
+        arg->v_pointer = NULL;
+        return TRUE;
+    }
+
+    if (!PySequence_Check (py_arg)) {
+        PyErr_Format (PyExc_TypeError, "Must be sequence, not %s",
+                      Py_TYPE (py_arg)->tp_name);
+        return FALSE;
+    }
+
+    length = PySequence_Length (py_arg);
+    if (length < 0)
+        return FALSE;
+
+    from_py_marshaller = sequence_cache->item_cache->from_py_marshaller;
+    for (i = 0; i < length; i++) {
+        GIArgument item = {0};
+        gpointer item_cleanup_data = NULL;
+        PyObject *py_item = PySequence_GetItem (py_arg, i);
+        if (py_item == NULL)
+            goto err;
+
+        if (!from_py_marshaller ( state,
+                                  callable_cache,
+                                  sequence_cache->item_cache,
+                                  py_item,
+                                 &item,
+                                 &item_cleanup_data))
+            goto err;
+
+        Py_DECREF (py_item);
+        list_ = g_list_prepend (list_, _pygi_arg_to_hash_pointer (&item, sequence_cache->item_cache->type_info));
+        continue;
+err:
+        /* FIXME: clean up list
+        if (sequence_cache->item_cache->from_py_cleanup != NULL) {
+            PyGIMarshalCleanupFunc cleanup = sequence_cache->item_cache->from_py_cleanup;
+        }
+        */
+        Py_XDECREF (py_item);
+        g_list_free (list_);
+        _PyGI_ERROR_PREFIX ("Item %i: ", i);
+        return FALSE;
+    }
+
+    arg->v_pointer = g_list_reverse (list_);
+
+    if (arg_cache->transfer == GI_TRANSFER_NOTHING) {
+        /* Free everything in cleanup. */
+        *cleanup_data = arg->v_pointer;
+    } else if (arg_cache->transfer == GI_TRANSFER_CONTAINER) {
+        /* Make a shallow copy so we can free the elements later in cleanup
+         * because it is possible invoke will free the list before our cleanup. */
+        *cleanup_data = g_list_copy (arg->v_pointer);
+    } else { /* GI_TRANSFER_EVERYTHING */
+        /* No cleanup, everything is given to the callee. */
+        *cleanup_data = NULL;
+    }
+    return TRUE;
+}
+
+
+static gboolean
+_pygi_marshal_from_py_gslist (PyGIInvokeState   *state,
+                              PyGICallableCache *callable_cache,
+                              PyGIArgCache      *arg_cache,
+                              PyObject          *py_arg,
+                              GIArgument        *arg,
+                              gpointer          *cleanup_data)
+{
+    PyGIMarshalFromPyFunc from_py_marshaller;
+    int i;
+    Py_ssize_t length;
+    GSList *list_ = NULL;
+    PyGISequenceCache *sequence_cache = (PyGISequenceCache *)arg_cache;
+
+    if (py_arg == Py_None) {
+        arg->v_pointer = NULL;
+        return TRUE;
+    }
+
+    if (!PySequence_Check (py_arg)) {
+        PyErr_Format (PyExc_TypeError, "Must be sequence, not %s",
+                      Py_TYPE (py_arg)->tp_name);
+        return FALSE;
+    }
+
+    length = PySequence_Length (py_arg);
+    if (length < 0)
+        return FALSE;
+
+    from_py_marshaller = sequence_cache->item_cache->from_py_marshaller;
+    for (i = 0; i < length; i++) {
+        GIArgument item = {0};
+        gpointer item_cleanup_data = NULL;
+        PyObject *py_item = PySequence_GetItem (py_arg, i);
+        if (py_item == NULL)
+            goto err;
+
+        if (!from_py_marshaller ( state,
+                             callable_cache,
+                             sequence_cache->item_cache,
+                             py_item,
+                            &item,
+                            &item_cleanup_data))
+            goto err;
+
+        Py_DECREF (py_item);
+        list_ = g_slist_prepend (list_, _pygi_arg_to_hash_pointer (&item, sequence_cache->item_cache->type_info));
+        continue;
+err:
+        /* FIXME: Clean up list
+        if (sequence_cache->item_cache->from_py_cleanup != NULL) {
+            PyGIMarshalCleanupFunc cleanup = sequence_cache->item_cache->from_py_cleanup;
+        }
+        */
+
+        Py_XDECREF (py_item);
+        g_slist_free (list_);
+        _PyGI_ERROR_PREFIX ("Item %i: ", i);
+        return FALSE;
+    }
+
+    arg->v_pointer = g_slist_reverse (list_);
+
+    if (arg_cache->transfer == GI_TRANSFER_NOTHING) {
+        /* Free everything in cleanup. */
+        *cleanup_data = arg->v_pointer;
+    } else if (arg_cache->transfer == GI_TRANSFER_CONTAINER) {
+        /* Make a shallow copy so we can free the elements later in cleanup
+         * because it is possible invoke will free the list before our cleanup. */
+        *cleanup_data = g_slist_copy (arg->v_pointer);
+    } else { /* GI_TRANSFER_EVERYTHING */
+        /* No cleanup, everything is given to the callee. */
+        *cleanup_data = NULL;
+    }
+
+    return TRUE;
+}
+
+static void
+_pygi_marshal_cleanup_from_py_glist  (PyGIInvokeState *state,
+                                      PyGIArgCache    *arg_cache,
+                                      PyObject        *py_arg,
+                                      gpointer         data,
+                                      gboolean         was_processed)
+{
+    if (was_processed) {
+        GSList *list_;
+        PyGISequenceCache *sequence_cache = (PyGISequenceCache *)arg_cache;
+
+        list_ = (GSList *)data;
+
+        /* clean up items first */
+        if (sequence_cache->item_cache->from_py_cleanup != NULL) {
+            PyGIMarshalCleanupFunc cleanup_func =
+                sequence_cache->item_cache->from_py_cleanup;
+            GSList *node = list_;
+            gsize i = 0;
+            while (node != NULL) {
+                PyObject *py_item = PySequence_GetItem (py_arg, i);
+                cleanup_func (state,
+                              sequence_cache->item_cache,
+                              py_item,
+                              node->data,
+                              TRUE);
+                Py_XDECREF (py_item);
+                node = node->next;
+                i++;
+            }
+        }
+
+        if (arg_cache->type_tag == GI_TYPE_TAG_GLIST) {
+            g_list_free ( (GList *)list_);
+        } else if (arg_cache->type_tag == GI_TYPE_TAG_GSLIST) {
+            g_slist_free (list_);
+        } else {
+            g_assert_not_reached();
+        }
+    }
+}
+
+
+/*
+ * GList and GSList to Python
+ */
+static PyObject *
+_pygi_marshal_to_py_glist (PyGIInvokeState   *state,
+                           PyGICallableCache *callable_cache,
+                           PyGIArgCache      *arg_cache,
+                           GIArgument        *arg,
+                           gpointer          *cleanup_data)
+{
+    GList *list_;
+    guint length;
+    guint i;
+    GPtrArray *item_cleanups;
+
+    PyGIMarshalToPyFunc item_to_py_marshaller;
+    PyGIArgCache *item_arg_cache;
+    PyGISequenceCache *seq_cache = (PyGISequenceCache *)arg_cache;
+
+    PyObject *py_obj = NULL;
+
+    list_ = arg->v_pointer;
+    length = g_list_length (list_);
+
+    py_obj = PyList_New (length);
+    if (py_obj == NULL)
+        return NULL;
+
+    item_cleanups = g_ptr_array_sized_new (length);
+    *cleanup_data = item_cleanups;
+
+    item_arg_cache = seq_cache->item_cache;
+    item_to_py_marshaller = item_arg_cache->to_py_marshaller;
+
+    for (i = 0; list_ != NULL; list_ = g_list_next (list_), i++) {
+        GIArgument item_arg;
+        PyObject *py_item;
+        gpointer item_cleanup_data = NULL;
+
+        item_arg.v_pointer = list_->data;
+        _pygi_hash_pointer_to_arg (&item_arg, item_arg_cache->type_info);
+        py_item = item_to_py_marshaller (state,
+                                         callable_cache,
+                                         item_arg_cache,
+                                         &item_arg,
+                                         &item_cleanup_data);
+
+        g_ptr_array_index (item_cleanups, i) = item_cleanup_data;
+
+        if (py_item == NULL) {
+            Py_CLEAR (py_obj);
+            _PyGI_ERROR_PREFIX ("Item %u: ", i);
+            g_ptr_array_unref (item_cleanups);
+            return NULL;
+        }
+
+        PyList_SET_ITEM (py_obj, i, py_item);
+    }
+
+    return py_obj;
+}
+
+static PyObject *
+_pygi_marshal_to_py_gslist (PyGIInvokeState   *state,
+                            PyGICallableCache *callable_cache,
+                            PyGIArgCache      *arg_cache,
+                            GIArgument        *arg,
+                            gpointer *cleanup_data)
+{
+    GSList *list_;
+    guint length;
+    guint i;
+    GPtrArray *item_cleanups;
+
+    PyGIMarshalToPyFunc item_to_py_marshaller;
+    PyGIArgCache *item_arg_cache;
+    PyGISequenceCache *seq_cache = (PyGISequenceCache *)arg_cache;
+
+    PyObject *py_obj = NULL;
+
+    list_ = arg->v_pointer;
+    length = g_slist_length (list_);
+
+    py_obj = PyList_New (length);
+    if (py_obj == NULL)
+        return NULL;
+
+    item_cleanups = g_ptr_array_sized_new (length);
+    *cleanup_data = item_cleanups;
+
+    item_arg_cache = seq_cache->item_cache;
+    item_to_py_marshaller = item_arg_cache->to_py_marshaller;
+
+    for (i = 0; list_ != NULL; list_ = g_slist_next (list_), i++) {
+        GIArgument item_arg;
+        PyObject *py_item;
+        gpointer item_cleanup_data = NULL;
+
+        item_arg.v_pointer = list_->data;
+        _pygi_hash_pointer_to_arg (&item_arg, item_arg_cache->type_info);
+        py_item = item_to_py_marshaller (state,
+                                        callable_cache,
+                                        item_arg_cache,
+                                        &item_arg,
+                                        &item_cleanup_data);
+
+        g_ptr_array_index (item_cleanups, i) = item_cleanup_data;
+        if (py_item == NULL) {
+            Py_CLEAR (py_obj);
+            _PyGI_ERROR_PREFIX ("Item %u: ", i);
+            g_ptr_array_unref (item_cleanups);
+            return NULL;
+        }
+
+        PyList_SET_ITEM (py_obj, i, py_item);
+    }
+
+    return py_obj;
+}
+
+static void
+_pygi_marshal_cleanup_to_py_glist (PyGIInvokeState *state,
+                                   PyGIArgCache    *arg_cache,
+                                   gpointer         cleanup_data,
+                                   gpointer         data,
+                                   gboolean         was_processed)
+{
+    GPtrArray *item_cleanups = (GPtrArray *) cleanup_data;
+    PyGISequenceCache *sequence_cache = (PyGISequenceCache *)arg_cache;
+    GSList *list_ = (GSList *)data;
+
+    if (sequence_cache->item_cache->to_py_cleanup != NULL) {
+        PyGIMarshalToPyCleanupFunc cleanup_func =
+            sequence_cache->item_cache->to_py_cleanup;
+        GSList *node = list_;
+        guint i = 0;
+
+        while (node != NULL) {
+            cleanup_func (state,
+                          sequence_cache->item_cache,
+                          g_ptr_array_index(item_cleanups, i),
+                          node->data,
+                          was_processed);
+            node = node->next;
+            i++;
+        }
+    }
+
+    if (arg_cache->transfer == GI_TRANSFER_EVERYTHING ||
+            arg_cache->transfer == GI_TRANSFER_CONTAINER) {
+
+        if (arg_cache->type_tag == GI_TYPE_TAG_GLIST) {
+            g_list_free ( (GList *)list_);
+        } else if (arg_cache->type_tag == GI_TYPE_TAG_GSLIST) {
+            g_slist_free (list_);
+        } else {
+            g_assert_not_reached();
+        }
+    }
+
+    g_ptr_array_unref (item_cleanups);
+}
+
+static void
+_arg_cache_from_py_glist_setup (PyGIArgCache *arg_cache,
+                                GITransfer transfer)
+{
+    arg_cache->from_py_marshaller = _pygi_marshal_from_py_glist;
+    arg_cache->from_py_cleanup = _pygi_marshal_cleanup_from_py_glist;
+}
+
+static void
+_arg_cache_to_py_glist_setup (PyGIArgCache *arg_cache,
+                              GITransfer transfer)
+{
+    arg_cache->to_py_marshaller = _pygi_marshal_to_py_glist;
+    arg_cache->to_py_cleanup = _pygi_marshal_cleanup_to_py_glist;
+}
+
+static void
+_arg_cache_from_py_gslist_setup (PyGIArgCache *arg_cache,
+                                 GITransfer transfer)
+{
+    arg_cache->from_py_marshaller = _pygi_marshal_from_py_gslist;
+    arg_cache->from_py_cleanup = _pygi_marshal_cleanup_from_py_glist;
+}
+
+static void
+_arg_cache_to_py_gslist_setup (PyGIArgCache *arg_cache,
+                                 GITransfer transfer)
+{
+    arg_cache->to_py_marshaller = _pygi_marshal_to_py_gslist;
+    arg_cache->to_py_cleanup = _pygi_marshal_cleanup_to_py_glist;
+}
+
+
+/*
+ * GList/GSList Interface
+ */
+
+static gboolean
+pygi_arg_glist_setup_from_info (PyGIArgCache      *arg_cache,
+                                GITypeInfo        *type_info,
+                                GIArgInfo         *arg_info,
+                                GITransfer         transfer,
+                                PyGIDirection      direction,
+                                PyGICallableCache *callable_cache)
+{
+    GITypeTag type_tag = g_type_info_get_tag (type_info);
+
+    if (!pygi_arg_sequence_setup ((PyGISequenceCache *)arg_cache,
+                                  type_info,
+                                  arg_info,
+                                  transfer,
+                                  direction,
+                                  callable_cache))
+        return FALSE;
+
+    switch (type_tag) {
+        case GI_TYPE_TAG_GLIST:
+            {
+                if (direction & PYGI_DIRECTION_FROM_PYTHON)
+                    _arg_cache_from_py_glist_setup (arg_cache, transfer);
+
+                if (direction & PYGI_DIRECTION_TO_PYTHON)
+                    _arg_cache_to_py_glist_setup (arg_cache, transfer);
+                break;
+            }
+        case GI_TYPE_TAG_GSLIST:
+            {
+                if (direction & PYGI_DIRECTION_FROM_PYTHON)
+                    _arg_cache_from_py_gslist_setup (arg_cache, transfer);
+
+                if (direction & PYGI_DIRECTION_TO_PYTHON)
+                    _arg_cache_to_py_gslist_setup (arg_cache, transfer);
+
+                break;
+             }
+       default:
+           g_assert_not_reached ();
+    }
+
+    return TRUE;
+}
+
+PyGIArgCache *
+pygi_arg_glist_new_from_info (GITypeInfo        *type_info,
+                              GIArgInfo         *arg_info,
+                              GITransfer         transfer,
+                              PyGIDirection      direction,
+                              PyGICallableCache *callable_cache)
+{
+    gboolean res = FALSE;
+
+    PyGIArgCache *arg_cache = (PyGIArgCache *) g_slice_new0 (PyGIArgGList);
+    if (arg_cache == NULL)
+        return NULL;
+
+    res = pygi_arg_glist_setup_from_info (arg_cache,
+                                          type_info,
+                                          arg_info,
+                                          transfer,
+                                          direction,
+                                          callable_cache);
+    if (res) {
+        return arg_cache;
+    } else {
+        pygi_arg_cache_free (arg_cache);
+        return NULL;
+    }
+}
diff --git a/gi/pygi-list.h b/gi/pygi-list.h
new file mode 100644 (file)
index 0000000..f22e024
--- /dev/null
@@ -0,0 +1,39 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_LIST_H__
+#define __PYGI_LIST_H__
+
+#include <girepository.h>
+#include "pygi-cache.h"
+
+G_BEGIN_DECLS
+
+PyGIArgCache *pygi_arg_glist_new_from_info  (GITypeInfo        *type_info,
+                                             GIArgInfo         *arg_info,   /* may be null */
+                                             GITransfer         transfer,
+                                             PyGIDirection      direction,
+                                             PyGICallableCache *callable_cache);
+
+/* Internally dispatches GList and GSList */
+#define pygi_arg_gslist_new_from_info  pygi_arg_glist_new_from_info
+
+G_END_DECLS
+
+#endif /*__PYGI_LIST_H__*/
diff --git a/gi/pygi-marshal-cleanup.c b/gi/pygi-marshal-cleanup.c
new file mode 100644 (file)
index 0000000..98fbe47
--- /dev/null
@@ -0,0 +1,237 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pygi-marshal-cleanup.h"
+#include "pygi-foreign.h"
+#include <glib.h>
+
+static inline void
+_cleanup_caller_allocates (PyGIInvokeState    *state,
+                           PyGIArgCache       *cache,
+                           PyObject           *py_obj,
+                           gpointer            data,
+                           gboolean            was_processed)
+{
+    PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)cache;
+
+    /* check GValue first because GValue is also a boxed sub-type */
+    if (g_type_is_a (iface_cache->g_type, G_TYPE_VALUE)) {
+        if (was_processed)
+            g_value_unset (data);
+        g_slice_free (GValue, data);
+    } else if (g_type_is_a (iface_cache->g_type, G_TYPE_BOXED)) {
+        gsize size;
+        if (was_processed)
+            return; /* will be cleaned up at deallocation */
+        size = g_struct_info_get_size (iface_cache->interface_info);
+        g_slice_free1 (size, data);
+    } else if (iface_cache->is_foreign) {
+        if (was_processed)
+            return; /* will be cleaned up at deallocation */
+        pygi_struct_foreign_release ((GIBaseInfo *)iface_cache->interface_info,
+                                     data);
+    } else {
+        if (was_processed)
+            return; /* will be cleaned up at deallocation */
+        g_free (data);
+    }
+}
+
+/**
+ * Cleanup during invoke can happen in multiple
+ * stages, each of which can be the result of a
+ * successful compleation of that stage or an error
+ * occured which requires partial cleanup.
+ *
+ * For the most part, either the C interface being
+ * invoked or the python object which wraps the
+ * parameters, handle their lifecycles but in some
+ * cases, where we have intermediate objects,
+ * or when we fail processing a parameter, we need
+ * to handle the clean up manually.
+ *
+ * There are two argument processing stages.
+ * They are the in stage, where we process python
+ * parameters into their C counterparts, and the out
+ * stage, where we process out C parameters back
+ * into python objects. The in stage also sets up
+ * temporary out structures for caller allocated
+ * parameters which need to be cleaned up either on
+ * in stage failure or at the completion of the out
+ * stage (either success or failure)
+ *
+ * The in stage must call one of these cleanup functions:
+ *    - pygi_marshal_cleanup_args_from_py_marshal_success
+ *       (continue to out stage)
+ *    - pygi_marshal_cleanup_args_from_py_parameter_fail
+ *       (final, exit from invoke)
+ *
+ * The out stage must call one of these cleanup functions which are all final:
+ *    - pygi_marshal_cleanup_args_to_py_marshal_success
+ *    - pygi_marshal_cleanup_args_return_fail
+ *    - pygi_marshal_cleanup_args_to_py_parameter_fail
+ *
+ **/
+void
+pygi_marshal_cleanup_args_from_py_marshal_success (PyGIInvokeState   *state,
+                                                   PyGICallableCache *cache)
+{
+    guint i;
+    PyObject *error_type, *error_value, *error_traceback;
+    gboolean have_error = !!PyErr_Occurred ();
+
+    if (have_error)
+        PyErr_Fetch (&error_type, &error_value, &error_traceback);
+
+    for (i = 0; i < _pygi_callable_cache_args_len (cache); i++) {
+        PyGIArgCache *arg_cache = _pygi_callable_cache_get_arg (cache, i);
+        PyGIMarshalCleanupFunc cleanup_func = arg_cache->from_py_cleanup;
+        gpointer cleanup_data = state->args[i].arg_cleanup_data;
+
+        /* Only cleanup using args_cleanup_data when available.
+         * It is the responsibility of the various "from_py" marshalers to return
+         * cleanup_data which is then passed into their respective cleanup function.
+         * PyGIInvokeState.args_cleanup_data stores this data (via _invoke_marshal_in_args)
+         * for the duration of the invoke up until this point.
+         */
+        if (cleanup_func && cleanup_data != NULL && arg_cache->py_arg_index >= 0 &&
+                arg_cache->direction & PYGI_DIRECTION_FROM_PYTHON) {
+            PyObject *py_arg = PyTuple_GET_ITEM (state->py_in_args, arg_cache->py_arg_index);
+            cleanup_func (state, arg_cache, py_arg, cleanup_data, TRUE);
+            state->args[i].arg_cleanup_data = NULL;
+        }
+    }
+
+    if (have_error)
+        PyErr_Restore (error_type, error_value, error_traceback);
+}
+
+void
+pygi_marshal_cleanup_args_to_py_marshal_success (PyGIInvokeState   *state,
+                                                 PyGICallableCache *cache)
+{
+    GSList *cache_item;
+    guint i = 0;
+    PyObject *error_type, *error_value, *error_traceback;
+    gboolean have_error = !!PyErr_Occurred ();
+
+    if (have_error)
+        PyErr_Fetch (&error_type, &error_value, &error_traceback);
+
+    /* clean up the return if available */
+    if (cache->return_cache != NULL) {
+        PyGIMarshalToPyCleanupFunc cleanup_func = cache->return_cache->to_py_cleanup;
+        if (cleanup_func && state->return_arg.v_pointer != NULL)
+            cleanup_func (state,
+                          cache->return_cache,
+                          state->to_py_return_arg_cleanup_data,
+                          state->return_arg.v_pointer,
+                          TRUE);
+    }
+
+    /* Now clean up args */
+    cache_item = cache->to_py_args;
+    while (cache_item) {
+        PyGIArgCache *arg_cache = (PyGIArgCache *) cache_item->data;
+        PyGIMarshalToPyCleanupFunc cleanup_func = arg_cache->to_py_cleanup;
+        gpointer data = state->args[arg_cache->c_arg_index].arg_value.v_pointer;
+
+        if (cleanup_func != NULL && data != NULL)
+            cleanup_func (state,
+                          arg_cache,
+                          state->args[arg_cache->c_arg_index].to_py_arg_cleanup_data,
+                          data,
+                          TRUE);
+        else if (arg_cache->is_caller_allocates && data != NULL) {
+            _cleanup_caller_allocates (state,
+                                       arg_cache,
+                                       state->args[arg_cache->c_arg_index].to_py_arg_cleanup_data,
+                                       data,
+                                       TRUE);
+        }
+
+        i++;
+        cache_item = cache_item->next;
+    }
+
+    if (have_error)
+        PyErr_Restore (error_type, error_value, error_traceback);
+}
+
+void
+pygi_marshal_cleanup_args_from_py_parameter_fail (PyGIInvokeState   *state,
+                                                  PyGICallableCache *cache,
+                                                  gssize failed_arg_index)
+{
+    guint i;
+    PyObject *error_type, *error_value, *error_traceback;
+    gboolean have_error = !!PyErr_Occurred ();
+
+    if (have_error)
+        PyErr_Fetch (&error_type, &error_value, &error_traceback);
+
+    state->failed = TRUE;
+
+    for (i = 0; i < _pygi_callable_cache_args_len (cache)  && i <= (guint)failed_arg_index; i++) {
+        PyGIArgCache *arg_cache = _pygi_callable_cache_get_arg (cache, i);
+        PyGIMarshalCleanupFunc cleanup_func = arg_cache->from_py_cleanup;
+        gpointer cleanup_data = state->args[i].arg_cleanup_data;
+        PyObject *py_arg = NULL;
+
+        if (arg_cache->py_arg_index < 0) {
+            continue;
+        }
+        py_arg = PyTuple_GET_ITEM (state->py_in_args, arg_cache->py_arg_index);
+
+        if (cleanup_func && cleanup_data != NULL &&
+                arg_cache->direction == PYGI_DIRECTION_FROM_PYTHON) {
+            cleanup_func (state,
+                          arg_cache,
+                          py_arg,
+                          cleanup_data,
+                          i < (guint)failed_arg_index);
+
+        } else if (arg_cache->is_caller_allocates && cleanup_data != NULL) {
+            _cleanup_caller_allocates (state,
+                                       arg_cache,
+                                       py_arg,
+                                       cleanup_data,
+                                       FALSE);
+        }
+        state->args[i].arg_cleanup_data = NULL;
+    }
+
+    if (have_error)
+        PyErr_Restore (error_type, error_value, error_traceback);
+}
+
+void
+pygi_marshal_cleanup_args_return_fail (PyGIInvokeState   *state,
+                                       PyGICallableCache *cache)
+{
+    state->failed = TRUE;
+}
+
+void
+pygi_marshal_cleanup_args_to_py_parameter_fail (PyGIInvokeState   *state,
+                                              PyGICallableCache *cache,
+                                              gssize failed_to_py_arg_index)
+{
+    state->failed = TRUE;
+}
diff --git a/gi/pygi-marshal-cleanup.h b/gi/pygi-marshal-cleanup.h
new file mode 100644 (file)
index 0000000..18ba007
--- /dev/null
@@ -0,0 +1,44 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_MARSHAL_CLEANUP_H__
+#define __PYGI_MARSHAL_CLEANUP_H__
+
+#include "pygi-struct.h"
+#include "pygi-invoke-state-struct.h"
+#include "pygi-cache.h"
+
+G_BEGIN_DECLS
+
+void pygi_marshal_cleanup_args_from_py_marshal_success  (PyGIInvokeState   *state,
+                                                         PyGICallableCache *cache);
+void pygi_marshal_cleanup_args_from_py_parameter_fail   (PyGIInvokeState   *state,
+                                                         PyGICallableCache *cache,
+                                                         gssize failed_arg_index);
+
+void pygi_marshal_cleanup_args_to_py_marshal_success (PyGIInvokeState   *state,
+                                                      PyGICallableCache *cache);
+void pygi_marshal_cleanup_args_return_fail           (PyGIInvokeState   *state,
+                                                      PyGICallableCache *cache);
+void pygi_marshal_cleanup_args_to_py_parameter_fail  (PyGIInvokeState   *state,
+                                                      PyGICallableCache *cache,
+                                                      gssize failed_to_py_arg_index);
+G_END_DECLS
+
+#endif /* __PYGI_MARSHAL_CLEANUP_H__ */
diff --git a/gi/pygi-object.c b/gi/pygi-object.c
new file mode 100644 (file)
index 0000000..bd5e085
--- /dev/null
@@ -0,0 +1,377 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include <glib.h>
+
+#include "pygi-python-compat.h"
+#include "pygi-object.h"
+#include "pygobject-object.h"
+#include "pygparamspec.h"
+
+/*
+ * GObject from Python
+ */
+
+typedef gboolean (*PyGIObjectMarshalFromPyFunc) (PyObject *py_arg,
+                                                 GIArgument *arg,
+                                                 GITransfer transfer);
+
+/* _pygi_marshal_from_py_gobject:
+ * py_arg: (in):
+ * arg: (out):
+ */
+static gboolean
+_pygi_marshal_from_py_gobject (PyObject *py_arg, /*in*/
+                               GIArgument *arg,  /*out*/
+                               GITransfer transfer) {
+    GObject *gobj;
+
+    if (py_arg == Py_None) {
+        arg->v_pointer = NULL;
+        return TRUE;
+    }
+
+    if (!pygobject_check (py_arg, &PyGObject_Type)) {
+        PyObject *repr = PyObject_Repr (py_arg);
+        PyErr_Format(PyExc_TypeError, "expected GObject but got %s",
+                     PYGLIB_PyUnicode_AsString (repr));
+        Py_DECREF (repr);
+        return FALSE;
+    }
+
+    gobj = pygobject_get (py_arg);
+    if (gobj == NULL) {
+        PyErr_Format(PyExc_RuntimeError, "object at %p of type %s is not initialized",
+                     py_arg, Py_TYPE(py_arg)->tp_name);
+        return FALSE;
+    }
+
+    if (transfer == GI_TRANSFER_EVERYTHING) {
+        /* For transfer everything, add a new ref that the callee will take ownership of.
+         * Pythons existing ref to the GObject will be managed with the PyGObject wrapper.
+         */
+        g_object_ref (gobj);
+    }
+
+    arg->v_pointer = gobj;
+    return TRUE;
+}
+
+/* pygi_arg_gobject_out_arg_from_py:
+ * py_arg: (in):
+ * arg: (out):
+ *
+ * A specialization for marshaling Python GObjects used for out/return values
+ * from a Python implemented vfuncs, signals, or an assignment to a GObject property.
+ */
+gboolean
+pygi_arg_gobject_out_arg_from_py (PyObject *py_arg, /*in*/
+                                  GIArgument *arg,  /*out*/
+                                  GITransfer transfer) {
+    GObject *gobj;
+    if (!_pygi_marshal_from_py_gobject (py_arg, arg, transfer)) {
+        return FALSE;
+    }
+
+    /* HACK: At this point the basic marshaling of the GObject was successful
+     * but we add some special case hacks for vfunc returns due to buggy APIs:
+     * https://bugzilla.gnome.org/show_bug.cgi?id=693393
+     */
+    gobj = arg->v_pointer;
+    if (Py_REFCNT (py_arg) == 1 && gobj->ref_count == 1) {
+        /* If both object ref counts are only 1 at this point (the reference held
+         * in a return tuple), we assume the GObject will be free'd before reaching
+         * its target and become invalid. So instead of getting invalid object errors
+         * we add a new GObject ref.
+         */
+        g_object_ref (gobj);
+
+        if (((PyGObject *)py_arg)->private_flags.flags & PYGOBJECT_GOBJECT_WAS_FLOATING) {
+            /*
+             * We want to re-float instances that were floating and the Python
+             * wrapper assumed ownership. With the additional caveat that there
+             * are not any strong references beyond the return tuple.
+             */
+            g_object_force_floating (gobj);
+
+        } else {
+            PyObject *repr = PyObject_Repr (py_arg);
+            gchar *msg = g_strdup_printf ("Expecting to marshal a borrowed reference for %s, "
+                                          "but nothing in Python is holding a reference to this object. "
+                                          "See: https://bugzilla.gnome.org/show_bug.cgi?id=687522",
+                                          PYGLIB_PyUnicode_AsString(repr));
+            Py_DECREF (repr);
+            if (PyErr_WarnEx (PyExc_RuntimeWarning, msg, 2)) {
+                g_free (msg);
+                return FALSE;
+            }
+            g_free (msg);
+        }
+    }
+
+    return TRUE;
+}
+
+static gboolean
+_pygi_marshal_from_py_interface_object (PyGIInvokeState             *state,
+                                        PyGICallableCache           *callable_cache,
+                                        PyGIArgCache                *arg_cache,
+                                        PyObject                    *py_arg,
+                                        GIArgument                  *arg,
+                                        gpointer                    *cleanup_data,
+                                        PyGIObjectMarshalFromPyFunc  func)
+{
+    PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)arg_cache;
+
+    if (py_arg == Py_None) {
+        arg->v_pointer = NULL;
+        return TRUE;
+    }
+
+    if (PyObject_IsInstance (py_arg, iface_cache->py_type) ||
+            (pygobject_check (py_arg, &PyGObject_Type) &&
+             g_type_is_a (G_OBJECT_TYPE (pygobject_get (py_arg)), iface_cache->g_type))) {
+
+        gboolean res;
+        res = func (py_arg, arg, arg_cache->transfer);
+        *cleanup_data = arg->v_pointer;
+        return res;
+
+    } else {
+        PyObject *module = PyObject_GetAttrString(py_arg, "__module__");
+
+        PyErr_Format (PyExc_TypeError, "argument %s: Expected %s, but got %s%s%s",
+                      arg_cache->arg_name ? arg_cache->arg_name : "self",
+                      ( (PyGIInterfaceCache *)arg_cache)->type_name,
+                      module ? PYGLIB_PyUnicode_AsString(module) : "",
+                      module ? "." : "",
+                      Py_TYPE (py_arg)->tp_name);
+        if (module)
+            Py_DECREF (module);
+        return FALSE;
+    }
+}
+
+static gboolean
+_pygi_marshal_from_py_called_from_c_interface_object (PyGIInvokeState   *state,
+                                                      PyGICallableCache *callable_cache,
+                                                      PyGIArgCache      *arg_cache,
+                                                      PyObject          *py_arg,
+                                                      GIArgument        *arg,
+                                                      gpointer          *cleanup_data)
+{
+    return _pygi_marshal_from_py_interface_object (state,
+                                                   callable_cache,
+                                                   arg_cache,
+                                                   py_arg,
+                                                   arg,
+                                                   cleanup_data,
+                                                   pygi_arg_gobject_out_arg_from_py);
+}
+
+static gboolean
+_pygi_marshal_from_py_called_from_py_interface_object (PyGIInvokeState   *state,
+                                                       PyGICallableCache *callable_cache,
+                                                       PyGIArgCache      *arg_cache,
+                                                       PyObject          *py_arg,
+                                                       GIArgument        *arg,
+                                                       gpointer          *cleanup_data)
+{
+    return _pygi_marshal_from_py_interface_object (state,
+                                                   callable_cache,
+                                                   arg_cache,
+                                                   py_arg,
+                                                   arg,
+                                                   cleanup_data,
+                                                   _pygi_marshal_from_py_gobject);
+}
+
+static void
+_pygi_marshal_cleanup_from_py_interface_object (PyGIInvokeState *state,
+                                                PyGIArgCache    *arg_cache,
+                                                PyObject        *py_arg,
+                                                gpointer         data,
+                                                gboolean         was_processed)
+{
+    /* If we processed the parameter but fail before invoking the method,
+       we need to remove the ref we added */
+    if (was_processed && state->failed && data != NULL &&
+            arg_cache->transfer == GI_TRANSFER_EVERYTHING)
+        g_object_unref (G_OBJECT(data));
+}
+
+
+/*
+ * GObject to Python
+ */
+
+PyObject *
+pygi_arg_gobject_to_py (GIArgument *arg, GITransfer transfer) {
+    PyObject *pyobj;
+
+    if (arg->v_pointer == NULL) {
+        pyobj = Py_None;
+        Py_INCREF (pyobj);
+
+    } else if (G_IS_PARAM_SPEC(arg->v_pointer)) {
+        pyobj = pyg_param_spec_new (arg->v_pointer);
+        if (transfer == GI_TRANSFER_EVERYTHING)
+            g_param_spec_unref (arg->v_pointer);
+
+    } else {
+         pyobj = pygobject_new_full (arg->v_pointer,
+                                     /*steal=*/ transfer == GI_TRANSFER_EVERYTHING,
+                                     /*type=*/  NULL);
+    }
+
+    return pyobj;
+}
+
+PyObject *
+pygi_arg_gobject_to_py_called_from_c (GIArgument *arg,
+                                      GITransfer  transfer)
+{
+    PyObject *object;
+
+    /* HACK:
+     * The following hack is to work around GTK+ sending signals which
+     * contain floating widgets in them. This assumes control of how
+     * references are added by the PyGObject wrapper and avoids the sink
+     * behavior by explicitly passing GI_TRANSFER_EVERYTHING as the transfer
+     * mode and then re-forcing the object as floating afterwards.
+     *
+     * See: https://bugzilla.gnome.org/show_bug.cgi?id=693400
+     */
+    if (arg->v_pointer != NULL &&
+            transfer == GI_TRANSFER_NOTHING &&
+            !G_IS_PARAM_SPEC (arg->v_pointer) &&
+            g_object_is_floating (arg->v_pointer)) {
+
+        g_object_ref (arg->v_pointer);
+        object = pygi_arg_gobject_to_py (arg, GI_TRANSFER_EVERYTHING);
+        g_object_force_floating (arg->v_pointer);
+    } else {
+        object = pygi_arg_gobject_to_py (arg, transfer);
+    }
+
+    return object;
+}
+
+static PyObject *
+_pygi_marshal_to_py_called_from_c_interface_object_cache_adapter (PyGIInvokeState   *state,
+                                                                  PyGICallableCache *callable_cache,
+                                                                  PyGIArgCache      *arg_cache,
+                                                                  GIArgument        *arg,
+                                                                  gpointer          *cleanup_data)
+{
+    return pygi_arg_gobject_to_py_called_from_c (arg, arg_cache->transfer);
+}
+
+static PyObject *
+_pygi_marshal_to_py_called_from_py_interface_object_cache_adapter (PyGIInvokeState   *state,
+                                                                   PyGICallableCache *callable_cache,
+                                                                   PyGIArgCache      *arg_cache,
+                                                                   GIArgument        *arg,
+                                                                   gpointer          *cleanup_data)
+{
+    return pygi_arg_gobject_to_py (arg, arg_cache->transfer);
+}
+
+static void
+_pygi_marshal_cleanup_to_py_interface_object (PyGIInvokeState *state,
+                                              PyGIArgCache    *arg_cache,
+                                              gpointer         cleanup_data,
+                                              gpointer         data,
+                                              gboolean         was_processed)
+{
+    /* If we error out and the object is not marshalled into a PyGObject
+       we must take care of removing the ref */
+    if (!was_processed && arg_cache->transfer == GI_TRANSFER_EVERYTHING)
+        g_object_unref (G_OBJECT(data));
+}
+
+static gboolean
+pygi_arg_gobject_setup_from_info (PyGIArgCache      *arg_cache,
+                                  GITypeInfo        *type_info,
+                                  GIArgInfo         *arg_info,
+                                  GITransfer         transfer,
+                                  PyGIDirection      direction,
+                                  PyGICallableCache *callable_cache)
+{
+    /* NOTE: usage of pygi_arg_interface_new_from_info already calls
+     * pygi_arg_interface_setup so no need to do it here.
+     */
+
+    if (direction & PYGI_DIRECTION_FROM_PYTHON) {
+        if (callable_cache->calling_context == PYGI_CALLING_CONTEXT_IS_FROM_C) {
+            arg_cache->from_py_marshaller = _pygi_marshal_from_py_called_from_c_interface_object;
+        } else {
+            arg_cache->from_py_marshaller = _pygi_marshal_from_py_called_from_py_interface_object;
+        }
+
+        arg_cache->from_py_cleanup = _pygi_marshal_cleanup_from_py_interface_object;
+    }
+
+    if (direction & PYGI_DIRECTION_TO_PYTHON) {
+        if (callable_cache->calling_context == PYGI_CALLING_CONTEXT_IS_FROM_C) {
+            arg_cache->to_py_marshaller = _pygi_marshal_to_py_called_from_c_interface_object_cache_adapter;
+        } else {
+            arg_cache->to_py_marshaller = _pygi_marshal_to_py_called_from_py_interface_object_cache_adapter;
+        }
+
+        arg_cache->to_py_cleanup = _pygi_marshal_cleanup_to_py_interface_object;
+    }
+
+    return TRUE;
+}
+
+PyGIArgCache *
+pygi_arg_gobject_new_from_info (GITypeInfo        *type_info,
+                                GIArgInfo         *arg_info,
+                                GITransfer         transfer,
+                                PyGIDirection      direction,
+                                GIInterfaceInfo   *iface_info,
+                                PyGICallableCache *callable_cache)
+{
+    gboolean res = FALSE;
+    PyGIArgCache *cache = NULL;
+
+    cache = pygi_arg_interface_new_from_info (type_info,
+                                              arg_info,
+                                              transfer,
+                                              direction,
+                                              iface_info);
+    if (cache == NULL)
+        return NULL;
+
+    res = pygi_arg_gobject_setup_from_info (cache,
+                                            type_info,
+                                            arg_info,
+                                            transfer,
+                                            direction,
+                                            callable_cache);
+    if (res) {
+        return cache;
+    } else {
+        pygi_arg_cache_free (cache);
+        return NULL;
+    }
+}
diff --git a/gi/pygi-object.h b/gi/pygi-object.h
new file mode 100644 (file)
index 0000000..360bce1
--- /dev/null
@@ -0,0 +1,52 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_OBJECT_H__
+#define __PYGI_OBJECT_H__
+
+#include <girepository.h>
+#include "pygi-cache.h"
+
+G_BEGIN_DECLS
+
+gboolean
+pygi_arg_gobject_out_arg_from_py     (PyObject          *py_arg,     /* in */
+                                      GIArgument        *arg,        /* out */
+                                      GITransfer         transfer);
+
+PyObject *
+pygi_arg_gobject_to_py               (GIArgument        *arg,
+                                      GITransfer         transfer);
+
+PyObject *
+pygi_arg_gobject_to_py_called_from_c (GIArgument        *arg,
+                                      GITransfer         transfer);
+
+
+PyGIArgCache *
+pygi_arg_gobject_new_from_info       (GITypeInfo        *type_info,
+                                      GIArgInfo         *arg_info,   /* may be null */
+                                      GITransfer         transfer,
+                                      PyGIDirection      direction,
+                                      GIInterfaceInfo   *iface_info,
+                                      PyGICallableCache *callable_cache);
+
+G_END_DECLS
+
+#endif /*__PYGI_OBJECT_H__*/
diff --git a/gi/pygi-property.c b/gi/pygi-property.c
new file mode 100644 (file)
index 0000000..595167b
--- /dev/null
@@ -0,0 +1,385 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2010  Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "pygi-property.h"
+#include "pygi-value.h"
+#include "pygi-argument.h"
+#include "pygparamspec.h"
+#include "pygi-type.h"
+
+#include <girepository.h>
+
+static GIPropertyInfo *
+lookup_property_from_object_info (GIObjectInfo *info, const gchar *attr_name)
+{
+    gssize n_infos;
+    gint i;
+
+    n_infos = g_object_info_get_n_properties (info);
+    for (i = 0; i < n_infos; i++) {
+        GIPropertyInfo *property_info;
+
+        property_info = g_object_info_get_property (info, i);
+        g_assert (info != NULL);
+
+        if (strcmp (attr_name, g_base_info_get_name (property_info)) == 0) {
+            return property_info;
+        }
+
+        g_base_info_unref (property_info);
+    }
+
+    return NULL;
+}
+
+static GIPropertyInfo *
+lookup_property_from_interface_info (GIInterfaceInfo *info,
+                                     const gchar *attr_name)
+{
+    gssize n_infos;
+    gint i;
+
+    n_infos = g_interface_info_get_n_properties (info);
+    for (i = 0; i < n_infos; i++) {
+        GIPropertyInfo *property_info;
+
+        property_info = g_interface_info_get_property (info, i);
+        g_assert (info != NULL);
+
+        if (strcmp (attr_name, g_base_info_get_name (property_info)) == 0) {
+            return property_info;
+        }
+
+        g_base_info_unref (property_info);
+    }
+
+    return NULL;
+}
+
+static GIPropertyInfo *
+_pygi_lookup_property_from_g_type (GType g_type, const gchar *attr_name)
+{
+    GIPropertyInfo *ret = NULL;
+    GIRepository *repository;
+    GIBaseInfo *info;
+
+    repository = g_irepository_get_default();
+    info = g_irepository_find_by_gtype (repository, g_type);
+    if (info == NULL)
+       return NULL;
+
+    if (GI_IS_OBJECT_INFO (info))
+        ret = lookup_property_from_object_info ((GIObjectInfo *) info,
+                                                attr_name);
+    else if (GI_IS_INTERFACE_INFO (info))
+        ret = lookup_property_from_interface_info ((GIInterfaceInfo *) info,
+                                                   attr_name);
+
+    g_base_info_unref (info);
+    return ret;
+}
+
+PyObject *
+pygi_call_do_get_property (PyObject *instance, GParamSpec *pspec)
+{
+    PyObject *py_pspec;
+    PyObject *retval;
+
+    py_pspec = pyg_param_spec_new (pspec);
+    retval = PyObject_CallMethod (instance, "do_get_property", "O", py_pspec);
+    Py_DECREF (py_pspec);
+    return retval;
+}
+
+PyObject *
+pygi_get_property_value (PyGObject *instance, GParamSpec *pspec)
+{
+    GIPropertyInfo *property_info = NULL;
+    GValue value = { 0, };
+    PyObject *py_value = NULL;
+    GType fundamental;
+    gboolean handled;
+
+    if (!(pspec->flags & G_PARAM_READABLE)) {
+        PyErr_Format(PyExc_TypeError, "property %s is not readable",
+                     g_param_spec_get_name (pspec));
+        return NULL;
+    }
+
+    /* Fast path which calls the Python getter implementation directly.
+     * See: https://bugzilla.gnome.org/show_bug.cgi?id=723872 */
+    if (pyg_gtype_is_custom (pspec->owner_type)) {
+        return pygi_call_do_get_property ((PyObject *)instance, pspec);
+    }
+
+    Py_BEGIN_ALLOW_THREADS;
+    g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+    g_object_get_property (instance->obj, pspec->name, &value);
+    fundamental = G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (&value));
+    Py_END_ALLOW_THREADS;
+
+
+    /* Fast path basic types which don't need GI type info. */
+    py_value = pygi_value_to_py_basic_type (&value, fundamental, &handled);
+    if (handled) {
+        goto out;
+    }
+
+    /* Attempt to marshal through GI.
+     * The owner_type of the pspec gives us the exact type that introduced the
+     * property, even if it is a parent class of the instance in question. */
+    property_info = _pygi_lookup_property_from_g_type (pspec->owner_type, pspec->name);
+    if (property_info) {
+        GITypeInfo *type_info = NULL;
+        gboolean free_array = FALSE;
+        GIArgument arg = { 0, };
+        GITransfer transfer = GI_TRANSFER_NOTHING;
+
+        type_info = g_property_info_get_type (property_info);
+        arg = _pygi_argument_from_g_value (&value, type_info);
+
+        /* Arrays are special cased, see note in _pygi_argument_to_array. */
+        if (g_type_info_get_tag (type_info) == GI_TYPE_TAG_ARRAY) {
+            arg.v_pointer = _pygi_argument_to_array (&arg, NULL, NULL, NULL,
+                                                     type_info, &free_array);
+        } else if (g_type_is_a (pspec->value_type, G_TYPE_BOXED)) {
+          arg.v_pointer = g_value_dup_boxed (&value);
+          transfer = GI_TRANSFER_EVERYTHING;
+        }
+
+        py_value = _pygi_argument_to_object (&arg, type_info, transfer);
+
+        if (free_array) {
+            g_array_free (arg.v_pointer, FALSE);
+        }
+
+        g_base_info_unref (type_info);
+        g_base_info_unref (property_info);
+    }
+
+    /* Fallback to GValue marshalling. */
+    if (py_value == NULL) {
+        py_value = pyg_param_gvalue_as_pyobject (&value, TRUE, pspec);
+    }
+
+out:
+    g_value_unset (&value);
+    return py_value;
+}
+
+PyObject *
+pygi_get_property_value_by_name (PyGObject *self, gchar *param_name)
+{
+    GParamSpec *pspec;
+
+    pspec = g_object_class_find_property (G_OBJECT_GET_CLASS(self->obj),
+                                          param_name);
+    if (!pspec) {
+        PyErr_Format (PyExc_TypeError,
+                      "object of type `%s' does not have property `%s'",
+                      g_type_name (G_OBJECT_TYPE (self->obj)), param_name);
+        return NULL;
+    }
+
+    return pygi_get_property_value (self, pspec);
+}
+
+gint
+pygi_set_property_value (PyGObject *instance,
+                         GParamSpec *pspec,
+                         PyObject *py_value)
+{
+    GIPropertyInfo *property_info = NULL;
+    GITypeInfo *type_info = NULL;
+    GITypeTag type_tag;
+    GITransfer transfer;
+    GValue value = { 0, };
+    GIArgument arg = { 0, };
+    gint ret_value = -1;
+
+    /* The owner_type of the pspec gives us the exact type that introduced the
+     * property, even if it is a parent class of the instance in question. */
+    property_info = _pygi_lookup_property_from_g_type (pspec->owner_type,
+                                                       pspec->name);
+    if (property_info == NULL)
+        goto out;
+
+    if (! (pspec->flags & G_PARAM_WRITABLE))
+        goto out;
+
+    type_info = g_property_info_get_type (property_info);
+    transfer = g_property_info_get_ownership_transfer (property_info);
+    arg = _pygi_argument_from_object (py_value, type_info, transfer);
+
+    if (PyErr_Occurred())
+        goto out;
+
+    g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+    /* FIXME: Lots of types still unhandled */
+    type_tag = g_type_info_get_tag (type_info);
+    switch (type_tag) {
+        case GI_TYPE_TAG_INTERFACE:
+        {
+            GIBaseInfo *info;
+            GIInfoType info_type;
+            GType type;
+
+            info = g_type_info_get_interface (type_info);
+            type = g_registered_type_info_get_g_type (info);
+            info_type = g_base_info_get_type (info);
+
+            g_base_info_unref (info);
+
+            switch (info_type) {
+                case GI_INFO_TYPE_ENUM:
+                    g_value_set_enum (&value, arg.v_int);
+                    break;
+                case GI_INFO_TYPE_FLAGS:
+                    g_value_set_flags (&value, arg.v_uint);
+                    break;
+                case GI_INFO_TYPE_INTERFACE:
+                case GI_INFO_TYPE_OBJECT:
+                    g_value_set_object (&value, arg.v_pointer);
+                    break;
+                case GI_INFO_TYPE_BOXED:
+                case GI_INFO_TYPE_STRUCT:
+                case GI_INFO_TYPE_UNION:
+                    if (g_type_is_a (type, G_TYPE_BOXED)) {
+                        g_value_set_boxed (&value, arg.v_pointer);
+                    } else if (g_type_is_a (type, G_TYPE_VARIANT)) {
+                        g_value_set_variant (&value, arg.v_pointer);
+                    } else {
+                        PyErr_Format (PyExc_NotImplementedError,
+                                      "Setting properties of type '%s' is not implemented",
+                                      g_type_name (type));
+                        goto out;
+                    }
+                    break;
+                default:
+                    PyErr_Format (PyExc_NotImplementedError,
+                                  "Setting properties of type '%s' is not implemented",
+                                  g_type_name (type));
+                    goto out;
+            }
+            break;
+        }
+        case GI_TYPE_TAG_BOOLEAN:
+            g_value_set_boolean (&value, arg.v_boolean);
+            break;
+        case GI_TYPE_TAG_INT8:
+            g_value_set_schar (&value, arg.v_int8);
+            break;
+        case GI_TYPE_TAG_INT16:
+        case GI_TYPE_TAG_INT32:
+            if (G_VALUE_HOLDS_LONG (&value))
+                g_value_set_long (&value, arg.v_long);
+            else
+                g_value_set_int (&value, arg.v_int);
+            break;
+        case GI_TYPE_TAG_INT64:
+            if (G_VALUE_HOLDS_LONG (&value))
+                g_value_set_long (&value, arg.v_long);
+            else
+                g_value_set_int64 (&value, arg.v_int64);
+            break;
+        case GI_TYPE_TAG_UINT8:
+            g_value_set_uchar (&value, arg.v_uint8);
+            break;
+        case GI_TYPE_TAG_UINT16:
+        case GI_TYPE_TAG_UINT32:
+            if (G_VALUE_HOLDS_ULONG (&value))
+                g_value_set_ulong (&value, arg.v_ulong);
+            else
+                g_value_set_uint (&value, arg.v_uint);
+            break;
+        case GI_TYPE_TAG_UINT64:
+            if (G_VALUE_HOLDS_ULONG (&value))
+                g_value_set_ulong (&value, arg.v_ulong);
+            else
+                g_value_set_uint64 (&value, arg.v_uint64);
+            break;
+        case GI_TYPE_TAG_FLOAT:
+            g_value_set_float (&value, arg.v_float);
+            break;
+        case GI_TYPE_TAG_DOUBLE:
+            g_value_set_double (&value, arg.v_double);
+            break;
+        case GI_TYPE_TAG_GTYPE:
+            g_value_set_gtype (&value, arg.v_size);
+            break;
+        case GI_TYPE_TAG_UTF8:
+        case GI_TYPE_TAG_FILENAME:
+            g_value_set_string (&value, arg.v_string);
+            break;
+        case GI_TYPE_TAG_GHASH:
+            g_value_set_boxed (&value, arg.v_pointer);
+            break;
+        case GI_TYPE_TAG_GLIST:
+            if (G_VALUE_HOLDS_BOXED(&value))
+                g_value_set_boxed (&value, arg.v_pointer);
+            else
+                g_value_set_pointer (&value, arg.v_pointer);
+            break;
+        case GI_TYPE_TAG_ARRAY:
+        {
+            /* This is assumes GI_TYPE_TAG_ARRAY is always a GStrv
+             * https://bugzilla.gnome.org/show_bug.cgi?id=688232
+             */
+            GArray *arg_items = (GArray*) arg.v_pointer;
+            gchar** strings;
+            guint i;
+
+            if (arg_items == NULL)
+                goto out;
+
+            strings = g_new0 (char*, arg_items->len + 1);
+            for (i = 0; i < arg_items->len; ++i) {
+                strings[i] = g_array_index (arg_items, GIArgument, i).v_string;
+            }
+            strings[arg_items->len] = NULL;
+            g_value_take_boxed (&value, strings);
+            g_array_free (arg_items, TRUE);
+            break;
+        }
+        default:
+            PyErr_Format (PyExc_NotImplementedError,
+                          "Setting properties of type %s is not implemented",
+                          g_type_tag_to_string (g_type_info_get_tag (type_info)));
+            goto out;
+    }
+
+    g_object_set_property (instance->obj, pspec->name, &value);
+    g_value_unset (&value);
+
+    ret_value = 0;
+
+out:
+    if (property_info != NULL)
+        g_base_info_unref (property_info);
+    if (type_info != NULL)
+        g_base_info_unref (type_info);
+
+    return ret_value;
+}
+
diff --git a/gi/pygi-property.h b/gi/pygi-property.h
new file mode 100644 (file)
index 0000000..d641b01
--- /dev/null
@@ -0,0 +1,48 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2010  Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef __PYGI_PROPERTY_H__
+#define __PYGI_PROPERTY_H__
+
+#include <Python.h>
+#include <girepository.h>
+
+#include "pygobject-internal.h"
+
+PyObject *
+pygi_get_property_value (PyGObject *instance,
+                         GParamSpec *pspec);
+
+PyObject *
+pygi_get_property_value_by_name (PyGObject *self,
+                                 gchar *param_name);
+PyObject *
+pygi_call_do_get_property       (PyObject *instance,
+                                 GParamSpec *pspec);
+
+gint
+pygi_set_property_value (PyGObject *instance,
+                         GParamSpec *pspec,
+                         PyObject *py_value);
+
+#endif /* __PYGI_PROPERTY_H__ */
diff --git a/gi/pygi-python-compat.h b/gi/pygi-python-compat.h
new file mode 100644 (file)
index 0000000..76999db
--- /dev/null
@@ -0,0 +1,192 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pyglib - Python bindings for GLib toolkit.
+ * Copyright (C) 2008  Johan Dahlin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGLIB_PYTHON_COMPAT_H__
+#define __PYGLIB_PYTHON_COMPAT_H__
+
+#ifndef PY_SSIZE_T_CLEAN
+#error "PY_SSIZE_T_CLEAN not defined"
+#endif
+
+#include <Python.h>
+
+#define PYGLIB_MODULE_ERROR_RETURN NULL
+
+#ifdef __GNUC__
+#define PYGI_MODINIT_FUNC __attribute__((visibility("default"))) PyMODINIT_FUNC
+#else
+#define PYGI_MODINIT_FUNC PyMODINIT_FUNC
+#endif
+
+/* Compilation on Python 2.x */
+#if PY_VERSION_HEX < 0x03000000
+
+#define PYGLIB_PyUnicode_Check PyString_Check
+#define PYGLIB_PyUnicode_AsString PyString_AsString
+#define PYGLIB_PyUnicode_AsStringAndSize PyString_AsStringAndSize
+#define PYGLIB_PyUnicode_FromString PyString_FromString
+#define PYGLIB_PyUnicode_FromStringAndSize PyString_FromStringAndSize
+#define PYGLIB_PyUnicode_FromFormat PyString_FromFormat
+#define PYGLIB_PyUnicode_Type PyString_Type
+#define PYGLIB_PyUnicode_InternFromString PyString_InternFromString
+#define PYGLIB_PyUnicode_InternInPlace PyString_InternInPlace
+#define PYGLIB_PyUnicode_Format PyString_Format
+
+#define PYGLIB_PyBytes_FromString PyString_FromString
+#define PYGLIB_PyBytes_FromStringAndSize PyString_FromStringAndSize
+#define PYGLIB_PyBytes_Resize _PyString_Resize
+#define PYGLIB_PyBytes_AsString PyString_AsString
+#define PYGLIB_PyBytes_AsStringAndSize PyString_AsStringAndSize
+#define PYGLIB_PyBytes_Size PyString_Size
+#define PYGLIB_PyBytes_Check PyString_Check
+
+#define PYGLIB_PyLong_Check PyInt_Check
+#define PYGLIB_PyLong_FromLong PyInt_FromLong
+#define PYGLIB_PyLong_FromSsize_t PyInt_FromSsize_t
+#define PYGLIB_PyLong_FromSize_t PyInt_FromSize_t
+#define PYGLIB_PyLong_AsLong  PyInt_AsLong
+#define PYGLIB_PyLong_AsSsize_t  PyInt_AsSsize_t
+#define PYGLIB_PyLongObject PyIntObject
+#define PYGLIB_PyLong_Type PyInt_Type
+#define PYGLIB_PyLong_AS_LONG PyInt_AS_LONG
+
+#define PYGLIB_Py_hash_t long
+
+/* Python 2.7 lacks a PyInt_FromUnsignedLong function; use signed longs, and
+ * rely on PyInt_AsUnsignedLong() to interpret them correctly */
+#define PYGLIB_PyLong_FromUnsignedLong PyInt_FromLong
+#define PYGLIB_PyLong_AsUnsignedLong(o) PyInt_AsUnsignedLongMask((PyObject*)(o))
+
+#define PYGLIB_PyNumber_Long PyNumber_Int
+
+#define PYGLIB_MODULE_START(symbol, modname)           \
+PyObject * pyglib_##symbol##_module_create(void);       \
+PYGI_MODINIT_FUNC init##symbol(void);                   \
+PYGI_MODINIT_FUNC init##symbol(void) {                  \
+    pyglib_##symbol##_module_create();                  \
+};                                                      \
+PyObject * pyglib_##symbol##_module_create(void)        \
+{                                                       \
+    PyObject *module;                                   \
+    module = Py_InitModule(modname, symbol##_functions);
+
+#define PYGLIB_MODULE_END return module; }
+
+#define PYGLIB_DEFINE_TYPE(typename, symbol, csymbol)  \
+PyTypeObject symbol = {                                 \
+    PyObject_HEAD_INIT(NULL)                            \
+    0,                                                  \
+    typename,                                          \
+    sizeof(csymbol),                                    \
+    0,                                                  \
+};
+
+#define PYGLIB_REGISTER_TYPE(d, type, name)            \
+    if (!type.tp_alloc)                                 \
+       type.tp_alloc = PyType_GenericAlloc;            \
+    if (!type.tp_new)                                   \
+       type.tp_new = PyType_GenericNew;                \
+    if (PyType_Ready(&type))                            \
+       return -1;                                         \
+    PyDict_SetItemString(d, name, (PyObject *)&type);
+
+#else
+
+#define PYGLIB_MODULE_START(symbol, modname)           \
+    static struct PyModuleDef _##symbol##module = {     \
+    PyModuleDef_HEAD_INIT,                              \
+    modname,                                            \
+    NULL,                                               \
+    -1,                                                 \
+    symbol##_functions,                                 \
+    NULL,                                               \
+    NULL,                                               \
+    NULL,                                               \
+    NULL                                                \
+};                                                      \
+PyObject * pyglib_##symbol##_module_create(void);       \
+PYGI_MODINIT_FUNC PyInit_##symbol(void);                \
+PYGI_MODINIT_FUNC PyInit_##symbol(void) {               \
+    return pyglib_##symbol##_module_create();           \
+};                                                      \
+PyObject * pyglib_##symbol##_module_create(void)        \
+{                                                       \
+    PyObject *module;                                   \
+    module = PyModule_Create(&_##symbol##module);
+
+#define PYGLIB_MODULE_END return module; }
+
+#define PYGLIB_DEFINE_TYPE(typename, symbol, csymbol)  \
+PyTypeObject symbol = {                                 \
+    PyVarObject_HEAD_INIT(NULL, 0)                      \
+    typename,                                           \
+    sizeof(csymbol)                                     \
+};
+
+#define PYGLIB_REGISTER_TYPE(d, type, name)                \
+    if (!type.tp_alloc)                                 \
+           type.tp_alloc = PyType_GenericAlloc;            \
+    if (!type.tp_new)                                   \
+           type.tp_new = PyType_GenericNew;                \
+    if (PyType_Ready(&type))                            \
+           return -1;                                         \
+    PyDict_SetItemString(d, name, (PyObject *)&type);
+
+#define PYGLIB_PyUnicode_Check PyUnicode_Check
+#define PYGLIB_PyUnicode_AsString _PyUnicode_AsString
+#define PYGLIB_PyUnicode_AsStringAndSize(obj, buf, size) \
+    (((*(buf) = _PyUnicode_AsStringAndSize(obj, size)) != NULL) ? 0 : -1) 
+#define PYGLIB_PyUnicode_FromString PyUnicode_FromString
+#define PYGLIB_PyUnicode_FromStringAndSize PyUnicode_FromStringAndSize
+#define PYGLIB_PyUnicode_FromFormat PyUnicode_FromFormat
+#define PYGLIB_PyUnicode_Resize PyUnicode_Resize
+#define PYGLIB_PyUnicode_Type PyUnicode_Type
+#define PYGLIB_PyUnicode_InternFromString PyUnicode_InternFromString
+#define PYGLIB_PyUnicode_InternInPlace PyUnicode_InternInPlace
+#define PYGLIB_PyUnicode_Format PyUnicode_Format
+
+#define PYGLIB_PyLong_Check PyLong_Check
+#define PYGLIB_PyLong_FromLong PyLong_FromLong
+#define PYGLIB_PyLong_FromSsize_t PyLong_FromSsize_t
+#define PYGLIB_PyLong_FromSize_t PyLong_FromSize_t
+#define PYGLIB_PyLong_AsLong PyLong_AsLong
+#define PYGLIB_PyLong_AsSsize_t PyLong_AsSsize_t
+#define PYGLIB_PyLong_AS_LONG(o) PyLong_AS_LONG((PyObject*)(o))
+#define PYGLIB_PyLongObject PyLongObject
+#define PYGLIB_PyLong_Type PyLong_Type
+
+#define PYGLIB_PyLong_FromUnsignedLong PyLong_FromUnsignedLong
+#define PYGLIB_PyLong_AsUnsignedLong(o) PyLong_AsUnsignedLongMask((PyObject*)(o))
+
+#define PYGLIB_PyBytes_FromString PyBytes_FromString
+#define PYGLIB_PyBytes_FromStringAndSize PyBytes_FromStringAndSize
+#define PYGLIB_PyBytes_Resize(o, len) _PyBytes_Resize(o, len)
+#define PYGLIB_PyBytes_AsString PyBytes_AsString
+#define PYGLIB_PyBytes_AsStringAndSize PyBytes_AsStringAndSize
+#define PYGLIB_PyBytes_Size PyBytes_Size
+#define PYGLIB_PyBytes_Check PyBytes_Check
+
+#define PYGLIB_PyNumber_Long PyNumber_Long
+
+#define PYGLIB_Py_hash_t Py_hash_t
+
+#endif
+
+#define PYGLIB_Py_hash_t_FromVoidPtr(ptr) ((PYGLIB_Py_hash_t)(gintptr)(ptr))
+
+#endif /* __PYGLIB_PYTHON_COMPAT_H__ */
diff --git a/gi/pygi-repository.c b/gi/pygi-repository.c
new file mode 100644 (file)
index 0000000..133f89c
--- /dev/null
@@ -0,0 +1,403 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+ *
+ *   pygi-repository.c: GIRepository wrapper.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pygi-repository.h"
+#include "pygi-info.h"
+#include "pygi-basictype.h"
+#include "pygi-python-compat.h"
+
+PyObject *PyGIRepositoryError;
+
+PYGLIB_DEFINE_TYPE("gi.Repository", PyGIRepository_Type, PyGIRepository);
+
+static PyObject *
+_wrap_g_irepository_enumerate_versions (PyGIRepository *self,
+                                        PyObject       *args,
+                                        PyObject       *kwargs)
+{
+    static char *kwlist[] = { "namespace", NULL };
+    const char *namespace_;
+    GList *versions, *item;
+    PyObject *ret = NULL;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs, "s:Repository.enumerate_versions",
+                                      kwlist, &namespace_)) {
+        return NULL;
+    }
+
+    versions = g_irepository_enumerate_versions (self->repository, namespace_);
+    ret = PyList_New(0);
+    for (item = versions; item; item = item->next) {
+        char *version = item->data;
+        PyObject *py_version = pygi_utf8_to_py (version);
+        PyList_Append(ret, py_version);
+        Py_DECREF(py_version);
+        g_free (version);
+    }
+    g_list_free(versions);
+
+    return ret;
+}
+
+static PyObject *
+_wrap_g_irepository_get_default (PyObject *self)
+{
+    static PyGIRepository *repository = NULL;
+
+    if (!repository) {
+        repository = (PyGIRepository *) PyObject_New (PyGIRepository, &PyGIRepository_Type);
+        if (repository == NULL) {
+            return NULL;
+        }
+
+        repository->repository = g_irepository_get_default();
+    }
+
+    Py_INCREF ( (PyObject *) repository);
+    return (PyObject *) repository;
+}
+
+static PyObject *
+_wrap_g_irepository_require (PyGIRepository *self,
+                             PyObject       *args,
+                             PyObject       *kwargs)
+{
+    static char *kwlist[] = { "namespace", "version", "lazy", NULL };
+
+    const char *namespace_;
+    const char *version = NULL;
+    PyObject *lazy = NULL;
+    GIRepositoryLoadFlags flags = 0;
+    GError *error;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs, "s|zO:Repository.require",
+                                      kwlist, &namespace_, &version, &lazy)) {
+        return NULL;
+    }
+
+    if (lazy != NULL && PyObject_IsTrue (lazy)) {
+        flags |= G_IREPOSITORY_LOAD_FLAG_LAZY;
+    }
+
+    error = NULL;
+    g_irepository_require (self->repository, namespace_, version, flags, &error);
+    if (error != NULL) {
+        PyErr_SetString (PyGIRepositoryError, error->message);
+        g_error_free (error);
+        return NULL;
+    }
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+_wrap_g_irepository_is_registered (PyGIRepository *self,
+                                   PyObject       *args,
+                                   PyObject       *kwargs)
+{
+    static char *kwlist[] = { "namespace", "version", NULL };
+    const char *namespace_;
+    const char *version = NULL;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs, "s|z:Repository.is_registered",
+                                      kwlist, &namespace_, &version)) {
+        return NULL;
+    }
+
+    return pygi_gboolean_to_py (g_irepository_is_registered (self->repository,
+                                                             namespace_, version));
+}
+
+static PyObject *
+_wrap_g_irepository_find_by_name (PyGIRepository *self,
+                                  PyObject       *args,
+                                  PyObject       *kwargs)
+{
+    static char *kwlist[] = { "namespace", "name", NULL };
+
+    const char *namespace_;
+    const char *name;
+    GIBaseInfo *info;
+    PyObject *py_info;
+    size_t len;
+    char *trimmed_name = NULL;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "ss:Repository.find_by_name", kwlist, &namespace_, &name)) {
+        return NULL;
+    }
+
+    /* If the given name ends with an underscore, it might be due to usage
+     * as an accessible replacement for something in GI with the same name
+     * as a Python keyword. Test for this and trim it out if necessary.
+     */
+    len = strlen (name);
+    if (len > 0 && name[len-1] == '_') {
+        trimmed_name = g_strndup (name, len-1);
+        if (_pygi_is_python_keyword (trimmed_name)) {
+            name = trimmed_name;
+        }
+    }
+
+    info = g_irepository_find_by_name (self->repository, namespace_, name);
+    g_free (trimmed_name);
+
+    if (info == NULL) {
+        Py_RETURN_NONE;
+    }
+
+    py_info = _pygi_info_new (info);
+
+    g_base_info_unref (info);
+
+    return py_info;
+}
+
+static PyObject *
+_wrap_g_irepository_get_infos (PyGIRepository *self,
+                               PyObject       *args,
+                               PyObject       *kwargs)
+{
+    static char *kwlist[] = { "namespace", NULL };
+
+    const char *namespace_;
+    gssize n_infos;
+    PyObject *infos;
+    gint i;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs, "s:Repository.get_infos",
+                                      kwlist, &namespace_)) {
+        return NULL;
+    }
+
+    n_infos = g_irepository_get_n_infos (self->repository, namespace_);
+    if (n_infos < 0) {
+        PyErr_Format (PyExc_RuntimeError, "Namespace '%s' not loaded", namespace_);
+        return NULL;
+    }
+
+    infos = PyTuple_New (n_infos);
+
+    for (i = 0; i < n_infos; i++) {
+        GIBaseInfo *info;
+        PyObject *py_info;
+
+        info = g_irepository_get_info (self->repository, namespace_, i);
+        g_assert (info != NULL);
+
+        py_info = _pygi_info_new (info);
+
+        g_base_info_unref (info);
+
+        if (py_info == NULL) {
+            Py_CLEAR (infos);
+            break;
+        }
+
+        PyTuple_SET_ITEM (infos, i, py_info);
+    }
+
+    return infos;
+}
+
+static PyObject *
+_wrap_g_irepository_get_typelib_path (PyGIRepository *self,
+                                      PyObject       *args,
+                                      PyObject       *kwargs)
+{
+    static char *kwlist[] = { "namespace", NULL };
+    const char *namespace_;
+    const gchar *typelib_path;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "s:Repository.get_typelib_path", kwlist, &namespace_)) {
+        return NULL;
+    }
+
+    typelib_path = g_irepository_get_typelib_path (self->repository, namespace_);
+    if (typelib_path == NULL) {
+        PyErr_Format (PyExc_RuntimeError, "Namespace '%s' not loaded", namespace_);
+        return NULL;
+    }
+
+    return pygi_filename_to_py (typelib_path);
+}
+
+static PyObject *
+_wrap_g_irepository_get_version (PyGIRepository *self,
+                                 PyObject       *args,
+                                 PyObject       *kwargs)
+{
+    static char *kwlist[] = { "namespace", NULL };
+    const char *namespace_;
+    const gchar *version;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "s:Repository.get_version", kwlist, &namespace_)) {
+        return NULL;
+    }
+
+    version = g_irepository_get_version (self->repository, namespace_);
+    if (version == NULL) {
+        PyErr_Format (PyExc_RuntimeError, "Namespace '%s' not loaded", namespace_);
+        return NULL;
+    }
+
+    return pygi_utf8_to_py (version);
+}
+
+static PyObject *
+_wrap_g_irepository_get_loaded_namespaces (PyGIRepository *self)
+{
+    char **namespaces;
+    PyObject *py_namespaces;
+    gssize i;
+
+    namespaces = g_irepository_get_loaded_namespaces (self->repository);
+
+    py_namespaces = PyList_New (0);
+    for (i = 0; namespaces[i] != NULL; i++) {
+        PyObject *py_namespace = pygi_utf8_to_py (namespaces[i]);
+        PyList_Append (py_namespaces, py_namespace);
+        Py_DECREF(py_namespace);
+        g_free (namespaces[i]);
+    }
+
+    g_free (namespaces);
+
+    return py_namespaces;
+}
+
+static PyObject *
+_wrap_g_irepository_get_dependencies (PyGIRepository *self,
+                                      PyObject       *args,
+                                      PyObject       *kwargs)
+{
+    static char *kwlist[] = { "namespace", NULL };
+    const char *namespace_;
+    char **namespaces;
+    PyObject *py_namespaces;
+    gssize i;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "s:Repository.get_dependencies", kwlist, &namespace_)) {
+        return NULL;
+    }
+
+    py_namespaces = PyList_New (0);
+    /* Returns NULL in case of no dependencies */
+    namespaces = g_irepository_get_dependencies (self->repository, namespace_);
+    if (namespaces == NULL) {
+        return py_namespaces;
+    }
+
+    for (i = 0; namespaces[i] != NULL; i++) {
+        PyObject *py_namespace = pygi_utf8_to_py (namespaces[i]);
+        PyList_Append (py_namespaces, py_namespace);
+        Py_DECREF(py_namespace);
+    }
+
+    g_strfreev (namespaces);
+
+    return py_namespaces;
+}
+
+
+static PyObject *
+_wrap_g_irepository_get_immediate_dependencies (PyGIRepository *self,
+                                                PyObject       *args,
+                                                PyObject       *kwargs)
+{
+    static char *kwlist[] = { "namespace", NULL };
+    const char *namespace_;
+    char **namespaces;
+    PyObject *py_namespaces;
+    gssize i;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs,
+                                      "s:Repository.get_immediate_dependencies",
+                                      kwlist, &namespace_)) {
+        return NULL;
+    }
+
+    py_namespaces = PyList_New (0);
+    namespaces = g_irepository_get_immediate_dependencies (self->repository,
+                                                           namespace_);
+
+    for (i = 0; namespaces[i] != NULL; i++) {
+        PyObject *py_namespace = pygi_utf8_to_py (namespaces[i]);
+        PyList_Append (py_namespaces, py_namespace);
+        Py_DECREF (py_namespace);
+    }
+
+    g_strfreev (namespaces);
+
+    return py_namespaces;
+}
+
+
+static PyMethodDef _PyGIRepository_methods[] = {
+    { "enumerate_versions", (PyCFunction) _wrap_g_irepository_enumerate_versions, METH_VARARGS | METH_KEYWORDS },
+    { "get_default", (PyCFunction) _wrap_g_irepository_get_default, METH_STATIC | METH_NOARGS },
+    { "require", (PyCFunction) _wrap_g_irepository_require, METH_VARARGS | METH_KEYWORDS },
+    { "get_infos", (PyCFunction) _wrap_g_irepository_get_infos, METH_VARARGS | METH_KEYWORDS },
+    { "find_by_name", (PyCFunction) _wrap_g_irepository_find_by_name, METH_VARARGS | METH_KEYWORDS },
+    { "get_typelib_path", (PyCFunction) _wrap_g_irepository_get_typelib_path, METH_VARARGS | METH_KEYWORDS },
+    { "get_version", (PyCFunction) _wrap_g_irepository_get_version, METH_VARARGS | METH_KEYWORDS },
+    { "get_loaded_namespaces", (PyCFunction) _wrap_g_irepository_get_loaded_namespaces, METH_NOARGS },
+    { "get_dependencies", (PyCFunction) _wrap_g_irepository_get_dependencies, METH_VARARGS | METH_KEYWORDS  },
+    { "get_immediate_dependencies", (PyCFunction) _wrap_g_irepository_get_immediate_dependencies, METH_VARARGS | METH_KEYWORDS  },
+    { "is_registered", (PyCFunction) _wrap_g_irepository_is_registered, METH_VARARGS | METH_KEYWORDS  },
+    { NULL, NULL, 0 }
+};
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_repository_register_types (PyObject *m)
+{
+    Py_TYPE(&PyGIRepository_Type) = &PyType_Type;
+
+    PyGIRepository_Type.tp_flags = Py_TPFLAGS_DEFAULT;
+    PyGIRepository_Type.tp_methods = _PyGIRepository_methods;
+
+    if (PyType_Ready (&PyGIRepository_Type) < 0)
+        return -1;
+
+    Py_INCREF ((PyObject *) &PyGIRepository_Type);
+    if (PyModule_AddObject (m, "Repository", (PyObject *) &PyGIRepository_Type) < 0) {
+        Py_DECREF ((PyObject *) &PyGIRepository_Type);
+        return -1;
+    }
+
+    PyGIRepositoryError = PyErr_NewException ("gi.RepositoryError", NULL, NULL);
+    if (PyGIRepositoryError == NULL)
+        return -1;
+
+    Py_INCREF (PyGIRepositoryError);
+    if (PyModule_AddObject (m, "RepositoryError", PyGIRepositoryError) < 0) {
+        Py_DECREF (PyGIRepositoryError);
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/gi/pygi-repository.h b/gi/pygi-repository.h
new file mode 100644 (file)
index 0000000..2207de3
--- /dev/null
@@ -0,0 +1,43 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_REPOSITORY_H__
+#define __PYGI_REPOSITORY_H__
+
+#include <Python.h>
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+    PyObject_HEAD
+    GIRepository *repository;
+} PyGIRepository;
+
+/* Private */
+
+extern PyTypeObject PyGIRepository_Type;
+
+extern PyObject *PyGIRepositoryError;
+
+int pygi_repository_register_types (PyObject *m);
+
+G_END_DECLS
+
+#endif /* __PYGI_REPOSITORY_H__ */
diff --git a/gi/pygi-resulttuple.c b/gi/pygi-resulttuple.c
new file mode 100644 (file)
index 0000000..0d060d0
--- /dev/null
@@ -0,0 +1,368 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2015 Christoph Reiter <reiter.christoph@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include <glib.h>
+#include "pygi-resulttuple.h"
+#include "pygi-python-compat.h"
+
+static char repr_format_key[] = "__repr_format";
+static char tuple_indices_key[] = "__tuple_indices";
+
+#define PYGI_USE_FREELIST
+
+#ifdef PYPY_VERSION
+#undef PYGI_USE_FREELIST
+#endif
+
+#ifdef PYGI_USE_FREELIST
+/* A free list similar to the one used for the CPython tuple. Difference
+ * is that zero length tuples aren't cached (as we don't need them)
+ * and that the freelist is smaller as we don't free it with the cyclic GC
+ * as CPython does. This wastes 21kB max.
+ */
+#define PyGIResultTuple_MAXSAVESIZE 10
+#define PyGIResultTuple_MAXFREELIST 100
+static PyObject *free_list[PyGIResultTuple_MAXSAVESIZE];
+static int numfree[PyGIResultTuple_MAXSAVESIZE];
+#endif
+
+PYGLIB_DEFINE_TYPE ("gi._gi.ResultTuple", PyGIResultTuple_Type, PyTupleObject)
+
+/**
+ * ResultTuple.__repr__() implementation.
+ * Takes the _ResultTuple.__repr_format format string and applies the tuple
+ * values to it.
+ */
+static PyObject*
+resulttuple_repr(PyObject *self) {
+    PyObject *format,  *repr, *format_attr;
+
+    format_attr = PYGLIB_PyUnicode_FromString (repr_format_key);
+    format = PyTuple_Type.tp_getattro (self, format_attr);
+    Py_DECREF (format_attr);
+    if (format == NULL)
+        return NULL;
+    repr = PYGLIB_PyUnicode_Format (format, self);
+    Py_DECREF (format);
+    return repr;
+}
+
+/**
+ * PyGIResultTuple_Type.tp_getattro implementation.
+ * Looks up the tuple index in _ResultTuple.__tuple_indices and returns the
+ * tuple item.
+ */
+static PyObject*
+resulttuple_getattro(PyObject *self, PyObject *name) {
+    PyObject *mapping, *index, *mapping_attr, *item;
+
+    mapping_attr = PYGLIB_PyUnicode_FromString (tuple_indices_key);
+    mapping = PyTuple_Type.tp_getattro (self, mapping_attr);
+    Py_DECREF (mapping_attr);
+    if (mapping == NULL)
+        return NULL;
+    g_assert (PyDict_Check (mapping));
+    index = PyDict_GetItem (mapping, name);
+
+    if (index != NULL) {
+        item = PyTuple_GET_ITEM (self, PYGLIB_PyLong_AsSsize_t (index));
+        Py_INCREF (item);
+    } else {
+        item = PyTuple_Type.tp_getattro (self, name);
+    }
+    Py_DECREF (mapping);
+
+    return item;
+}
+
+/**
+ * ResultTuple.__reduce__() implementation.
+ * Always returns (tuple, tuple(self))
+ * Needed so that pickling doesn't depend on our tuple subclass and unpickling
+ * works without it. As a result unpickle will give back in a normal tuple.
+ */
+static PyObject *
+resulttuple_reduce(PyObject *self)
+{
+    PyObject *tuple = PySequence_Tuple (self);
+    if (tuple == NULL)
+        return NULL;
+    return Py_BuildValue ("(O, (N))", &PyTuple_Type, tuple);
+}
+
+/**
+ * Extends __dir__ with the extra attributes accessible through
+ * resulttuple_getattro()
+ */
+static PyObject *
+resulttuple_dir(PyObject *self)
+{
+    PyObject *mapping_attr;
+    PyObject *items = NULL;
+    PyObject *mapping = NULL;
+    PyObject *mapping_values = NULL;
+    PyObject *result = NULL;
+
+    mapping_attr = PYGLIB_PyUnicode_FromString (tuple_indices_key);
+    mapping = PyTuple_Type.tp_getattro (self, mapping_attr);
+    Py_DECREF (mapping_attr);
+    if (mapping == NULL)
+        goto error;
+    items = PyObject_Dir ((PyObject*)Py_TYPE (self));
+    if (items == NULL)
+        goto error;
+    mapping_values = PyDict_Keys (mapping);
+    if (mapping_values == NULL)
+        goto error;
+    result = PySequence_InPlaceConcat (items, mapping_values);
+
+error:
+    Py_XDECREF (items);
+    Py_XDECREF (mapping);
+    Py_XDECREF (mapping_values);
+
+    return result;
+}
+
+/**
+ * resulttuple_new_type:
+ * @args: one list object containing tuple item names and None
+ *
+ * Exposes pygi_resulttuple_new_type() as ResultTuple._new_type()
+ * to allow creation of result types for unit tests.
+ *
+ * Returns: A new PyTypeObject which is a subclass of PyGIResultTuple_Type
+ *    or %NULL in case of an error.
+ */
+static PyObject *
+resulttuple_new_type(PyObject *self, PyObject *args) {
+    PyObject *tuple_names, *new_type;
+
+    if (!PyArg_ParseTuple (args, "O:ResultTuple._new_type", &tuple_names))
+        return NULL;
+
+    if (!PyList_Check (tuple_names)) {
+        PyErr_SetString (PyExc_TypeError, "not a list");
+        return NULL;
+    }
+
+    new_type = (PyObject *)pygi_resulttuple_new_type (tuple_names);
+    return new_type;
+}
+
+static PyMethodDef resulttuple_methods[] = {
+    {"__reduce__", (PyCFunction)resulttuple_reduce, METH_NOARGS},
+    {"__dir__", (PyCFunction)resulttuple_dir, METH_NOARGS},
+    {"_new_type", (PyCFunction)resulttuple_new_type,
+     METH_VARARGS | METH_STATIC},
+    {NULL, NULL, 0},
+};
+
+/**
+ * pygi_resulttuple_new_type:
+ * @tuple_names: A python list containing str or None items.
+ *
+ * Similar to namedtuple() creates a new tuple subclass which
+ * allows to access items by name and have a pretty __repr__.
+ * Each item in the passed name list corresponds to an item with
+ * the same index in the tuple class. If the name is None the item/index
+ * is unnamed.
+ *
+ * Returns: A new PyTypeObject which is a subclass of PyGIResultTuple_Type
+ *    or %NULL in case of an error.
+ */
+PyTypeObject*
+pygi_resulttuple_new_type(PyObject *tuple_names) {
+    PyTypeObject *new_type;
+    PyObject *class_dict, *format_string, *empty_format, *named_format,
+        *format_list, *sep, *index_dict, *slots, *paren_format, *new_type_args,
+        *paren_string;
+    Py_ssize_t len, i;
+
+    g_assert (PyList_Check (tuple_names));
+
+    class_dict = PyDict_New ();
+
+    /* To save some memory don't use an instance dict */
+    slots = PyTuple_New (0);
+    PyDict_SetItemString (class_dict, "__slots__", slots);
+    Py_DECREF (slots);
+
+    format_list = PyList_New (0);
+    index_dict = PyDict_New ();
+
+    empty_format = PYGLIB_PyUnicode_FromString ("%r");
+    named_format = PYGLIB_PyUnicode_FromString ("%s=%%r");
+    len = PyList_Size (tuple_names);
+    for (i = 0; i < len; i++) {
+        PyObject *item, *named_args, *named_build, *index;
+        item = PyList_GET_ITEM (tuple_names, i);
+        if (item == Py_None) {
+            PyList_Append (format_list, empty_format);
+        } else {
+            named_args = Py_BuildValue ("(O)", item);
+            named_build = PYGLIB_PyUnicode_Format (named_format, named_args);
+            Py_DECREF (named_args);
+            PyList_Append (format_list, named_build);
+            Py_DECREF (named_build);
+            index = PYGLIB_PyLong_FromSsize_t (i);
+            PyDict_SetItem (index_dict, item, index);
+            Py_DECREF (index);
+        }
+    }
+    Py_DECREF (empty_format);
+    Py_DECREF (named_format);
+
+    sep = PYGLIB_PyUnicode_FromString (", ");
+    format_string = PyObject_CallMethod (sep, "join", "O", format_list);
+    Py_DECREF (sep);
+    Py_DECREF (format_list);
+    paren_format = PYGLIB_PyUnicode_FromString ("(%s)");
+    paren_string = PYGLIB_PyUnicode_Format (paren_format, format_string);
+    Py_DECREF (paren_format);
+    Py_DECREF (format_string);
+
+    PyDict_SetItemString (class_dict, repr_format_key, paren_string);
+    Py_DECREF (paren_string);
+
+    PyDict_SetItemString (class_dict, tuple_indices_key, index_dict);
+    Py_DECREF (index_dict);
+
+    new_type_args = Py_BuildValue ("s(O)O", "_ResultTuple",
+                                   &PyGIResultTuple_Type, class_dict);
+    new_type = (PyTypeObject *)PyType_Type.tp_new (&PyType_Type,
+                                                   new_type_args, NULL);
+    Py_DECREF (new_type_args);
+    Py_DECREF (class_dict);
+
+    if (new_type != NULL) {
+        /* disallow subclassing as that would break the free list caching
+         * since we assume that all subclasses use PyTupleObject */
+        new_type->tp_flags &= ~Py_TPFLAGS_BASETYPE;
+    }
+
+    return new_type;
+}
+
+
+/**
+ * pygi_resulttuple_new:
+ * @subclass: A PyGIResultTuple_Type subclass which will be the type of the
+ *    returned instance.
+ * @len: Length of the returned tuple
+ *
+ * Like PyTuple_New(). Return an uninitialized tuple of the given @length.
+ *
+ * Returns: An instance of @subclass or %NULL on error.
+ */
+PyObject *
+pygi_resulttuple_new(PyTypeObject *subclass, Py_ssize_t len) {
+#ifdef PYGI_USE_FREELIST
+    PyObject *self;
+    Py_ssize_t i;
+
+    /* Check the free list for a tuple object with the needed size;
+     * clear it and change the class to ours.
+     */
+    if (len > 0 && len < PyGIResultTuple_MAXSAVESIZE) {
+        self = free_list[len];
+        if (self != NULL) {
+            free_list[len] = PyTuple_GET_ITEM (self, 0);
+            numfree[len]--;
+            for (i=0; i < len; i++) {
+                PyTuple_SET_ITEM (self, i, NULL);
+            }
+            Py_TYPE (self) = subclass;
+            Py_INCREF (subclass);
+            _Py_NewReference (self);
+            PyObject_GC_Track (self);
+            return self;
+        }
+    }
+#endif
+
+    /* For zero length tuples and in case the free list is empty, alloc
+     * as usual.
+     */
+    return subclass->tp_alloc (subclass, len);
+}
+
+#ifdef PYGI_USE_FREELIST
+static void resulttuple_dealloc(PyObject *self) {
+    Py_ssize_t i, len;
+
+    PyObject_GC_UnTrack (self);
+    Py_TRASHCAN_SAFE_BEGIN (self)
+
+    /* Free the tuple items and, if there is space, save the tuple object
+     * pointer to the front of the free list for its size. Otherwise free it.
+     */
+    len = Py_SIZE (self);
+    if (len > 0) {
+        for (i=0; i < len; i++) {
+            Py_XDECREF (PyTuple_GET_ITEM (self, i));
+        }
+
+        if (len < PyGIResultTuple_MAXSAVESIZE && numfree[len] < PyGIResultTuple_MAXFREELIST) {
+            PyTuple_SET_ITEM (self, 0, free_list[len]);
+            numfree[len]++;
+            free_list[len] = self;
+            goto done;
+        }
+    }
+
+    Py_TYPE (self)->tp_free (self);
+
+done:
+    Py_TRASHCAN_SAFE_END (self)
+}
+#endif
+
+/**
+ * pygi_resulttuple_register_types:
+ * @module: A Python modules to which ResultTuple gets added to.
+ *
+ * Initializes the ResultTuple class and adds it to the passed @module.
+ *
+ * Returns: -1 on error, 0 on success.
+ */
+int pygi_resulttuple_register_types(PyObject *module) {
+
+    PyGIResultTuple_Type.tp_base = &PyTuple_Type;
+    PyGIResultTuple_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+    PyGIResultTuple_Type.tp_repr = (reprfunc)resulttuple_repr;
+    PyGIResultTuple_Type.tp_getattro = (getattrofunc)resulttuple_getattro;
+    PyGIResultTuple_Type.tp_methods = resulttuple_methods;
+#ifdef PYGI_USE_FREELIST
+    PyGIResultTuple_Type.tp_dealloc = (destructor)resulttuple_dealloc;
+#endif
+
+    if (PyType_Ready (&PyGIResultTuple_Type) < 0)
+        return -1;
+
+    Py_INCREF (&PyGIResultTuple_Type);
+    if (PyModule_AddObject (module, "ResultTuple",
+                            (PyObject *)&PyGIResultTuple_Type) < 0) {
+        Py_DECREF (&PyGIResultTuple_Type);
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/gi/pygi-resulttuple.h b/gi/pygi-resulttuple.h
new file mode 100644 (file)
index 0000000..3f63ca0
--- /dev/null
@@ -0,0 +1,34 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2015 Christoph Reiter <reiter.christoph@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_RESULTTUPLE_H__
+#define __PYGI_RESULTTUPLE_H__
+
+#include "Python.h"
+
+int
+pygi_resulttuple_register_types    (PyObject *d);
+
+PyTypeObject *
+pygi_resulttuple_new_type          (PyObject *tuple_names);
+
+PyObject*
+pygi_resulttuple_new               (PyTypeObject *subclass, Py_ssize_t len);
+
+#endif /* __PYGI_RESULTTUPLE_H__ */
diff --git a/gi/pygi-signal-closure.c b/gi/pygi-signal-closure.c
new file mode 100644 (file)
index 0000000..d553d76
--- /dev/null
@@ -0,0 +1,283 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2011  Laszlo Pandy <lpandy@src.gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pygi-signal-closure.h"
+#include "pygi-value.h"
+#include "pygi-argument.h"
+#include "pygi-boxed.h"
+
+static GISignalInfo *
+_pygi_lookup_signal_from_g_type (GType g_type,
+                                 const gchar *signal_name)
+{
+    GIRepository *repository;
+    GIBaseInfo *info;
+    GISignalInfo *signal_info = NULL;
+
+    repository = g_irepository_get_default();
+    info = g_irepository_find_by_gtype (repository, g_type);
+    if (info == NULL)
+        return NULL;
+
+    if (GI_IS_OBJECT_INFO (info))
+        signal_info = g_object_info_find_signal ((GIObjectInfo *) info,
+                                                 signal_name);
+    else if (GI_IS_INTERFACE_INFO (info))
+        signal_info = g_interface_info_find_signal ((GIInterfaceInfo *) info,
+                                                    signal_name);
+
+    g_base_info_unref (info);
+    return signal_info;
+}
+
+static void
+pygi_signal_closure_invalidate(gpointer data,
+                               GClosure *closure)
+{
+    PyGClosure *pc = (PyGClosure *)closure;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+    Py_XDECREF(pc->callback);
+    Py_XDECREF(pc->extra_args);
+    Py_XDECREF(pc->swap_data);
+    PyGILState_Release(state);
+
+    pc->callback = NULL;
+    pc->extra_args = NULL;
+    pc->swap_data = NULL;
+
+    g_base_info_unref (((PyGISignalClosure *) pc)->signal_info);
+    ((PyGISignalClosure *) pc)->signal_info = NULL;
+}
+
+static void
+pygi_signal_closure_marshal(GClosure *closure,
+                            GValue *return_value,
+                            guint n_param_values,
+                            const GValue *param_values,
+                            gpointer invocation_hint,
+                            gpointer marshal_data)
+{
+    PyGILState_STATE state;
+    PyGClosure *pc = (PyGClosure *)closure;
+    PyObject *params, *ret = NULL;
+    guint i;
+    GISignalInfo *signal_info;
+    gint n_sig_info_args;
+    gint sig_info_highest_arg;
+    GSList *list_item = NULL;
+    GSList *pass_by_ref_structs = NULL;
+
+    state = PyGILState_Ensure();
+
+    signal_info = ((PyGISignalClosure *)closure)->signal_info;
+    n_sig_info_args = g_callable_info_get_n_args(signal_info);
+    g_assert_cmpint (n_sig_info_args, >=, 0);
+    /* the first argument to a signal callback is instance,
+       but instance is not counted in the introspection data */
+    sig_info_highest_arg = n_sig_info_args + 1;
+    g_assert_cmpint(sig_info_highest_arg, ==, n_param_values);
+
+    /* construct Python tuple for the parameter values */
+    params = PyTuple_New(n_param_values);
+    for (i = 0; i < n_param_values; i++) {
+        /* swap in a different initial data for connect_object() */
+        if (i == 0 && G_CCLOSURE_SWAP_DATA(closure)) {
+            g_return_if_fail(pc->swap_data != NULL);
+            Py_INCREF(pc->swap_data);
+            PyTuple_SetItem(params, 0, pc->swap_data);
+
+        } else if (i == 0) {
+            PyObject *item = pyg_value_as_pyobject(&param_values[i], FALSE);
+
+            if (!item) {
+                goto out;
+            }
+            PyTuple_SetItem(params, i, item);
+
+        } else if (i < (guint)sig_info_highest_arg) {
+            GIArgInfo arg_info;
+            GITypeInfo type_info;
+            GITypeTag type_tag;
+            GIArgument arg = { 0, };
+            PyObject *item = NULL;
+            gboolean free_array = FALSE;
+            gboolean pass_struct_by_ref = FALSE;
+
+            g_callable_info_load_arg(signal_info, i - 1, &arg_info);
+            g_arg_info_load_type(&arg_info, &type_info);
+
+            arg = _pygi_argument_from_g_value(&param_values[i], &type_info);
+
+            type_tag = g_type_info_get_tag (&type_info);
+            if (type_tag == GI_TYPE_TAG_ARRAY) {
+                /* Skip the self argument of param_values */
+                arg.v_pointer = _pygi_argument_to_array (&arg,
+                                                         _pygi_argument_array_length_marshal,
+                                                         (void *)(param_values + 1),
+                                                         signal_info,
+                                                         &type_info,
+                                                         &free_array);
+            }
+
+            /* Hack to ensure struct arguments are passed-by-reference allowing
+             * callback implementors to modify the struct values. This is needed
+             * for keeping backwards compatibility and should be removed in future
+             * versions which support signal output arguments as return values.
+             * See: https://bugzilla.gnome.org/show_bug.cgi?id=735486
+             *
+             * Note the logic here must match the logic path taken in _pygi_argument_to_object.
+             */
+            if (type_tag == GI_TYPE_TAG_INTERFACE) {
+                GIBaseInfo *info = g_type_info_get_interface (&type_info);
+                GIInfoType info_type = g_base_info_get_type (info);
+
+                if (info_type == GI_INFO_TYPE_STRUCT ||
+                        info_type == GI_INFO_TYPE_BOXED ||
+                        info_type == GI_INFO_TYPE_UNION) {
+
+                    GType gtype = g_registered_type_info_get_g_type ((GIRegisteredTypeInfo *) info);
+                    gboolean is_foreign = (info_type == GI_INFO_TYPE_STRUCT) &&
+                                          (g_struct_info_is_foreign ((GIStructInfo *) info));
+
+                    if (!is_foreign && !g_type_is_a (gtype, G_TYPE_VALUE) &&
+                            g_type_is_a (gtype, G_TYPE_BOXED)) {
+                        pass_struct_by_ref = TRUE;
+                    }
+                }
+
+                g_base_info_unref (info);
+            }
+
+            if (pass_struct_by_ref) {
+                /* transfer everything will ensure the struct is not copied when wrapped. */
+                item = _pygi_argument_to_object (&arg, &type_info, GI_TRANSFER_EVERYTHING);
+                if (item && PyObject_IsInstance (item, (PyObject *) &PyGIBoxed_Type)) {
+                    ((PyGBoxed *)item)->free_on_dealloc = FALSE;
+                    pass_by_ref_structs = g_slist_prepend (pass_by_ref_structs, item);
+                }
+
+            } else {
+                item = _pygi_argument_to_object (&arg, &type_info, GI_TRANSFER_NOTHING);
+            }
+
+            if (free_array) {
+                g_array_free (arg.v_pointer, FALSE);
+            }
+
+            if (item == NULL) {
+                PyErr_Print ();
+                goto out;
+            }
+            PyTuple_SetItem(params, i, item);
+        }
+    }
+    /* params passed to function may have extra arguments */
+    if (pc->extra_args) {
+        PyObject *tuple = params;
+        params = PySequence_Concat(tuple, pc->extra_args);
+        Py_DECREF(tuple);
+    }
+    ret = PyObject_CallObject(pc->callback, params);
+    if (ret == NULL) {
+        if (pc->exception_handler)
+            pc->exception_handler(return_value, n_param_values, param_values);
+        else
+            PyErr_Print();
+        goto out;
+    }
+
+    if (G_IS_VALUE(return_value) && pyg_value_from_pyobject(return_value, ret) != 0) {
+        PyErr_SetString(PyExc_TypeError,
+                        "can't convert return value to desired type");
+
+        if (pc->exception_handler)
+            pc->exception_handler(return_value, n_param_values, param_values);
+        else
+            PyErr_Print();
+    }
+    Py_DECREF(ret);
+
+    /* Run through the list of structs which have been passed by reference and
+     * check if they are being held longer than the duration of the callback
+     * execution. This is determined if the ref count is greater than 1.
+     * A single ref is held by the argument list and any more would mean the callback
+     * stored a ref somewhere else. In this case we make an internal copy of
+     * the boxed struct so Python can own the memory to it.
+     */
+    list_item = pass_by_ref_structs;
+    while (list_item) {
+        PyObject *item = list_item->data;
+        if (Py_REFCNT (item) > 1) {
+            pygi_boxed_copy_in_place ((PyGIBoxed *)item);
+        }
+        list_item = g_slist_next (list_item);
+    }
+
+ out:
+    g_slist_free (pass_by_ref_structs);
+    Py_DECREF(params);
+    PyGILState_Release(state);
+}
+
+GClosure *
+pygi_signal_closure_new (PyGObject *instance,
+                         GType g_type,
+                         const gchar *signal_name,
+                         PyObject *callback,
+                         PyObject *extra_args,
+                         PyObject *swap_data)
+{
+    GClosure *closure = NULL;
+    PyGISignalClosure *pygi_closure = NULL;
+    GISignalInfo *signal_info = NULL;
+
+    g_return_val_if_fail(callback != NULL, NULL);
+
+    signal_info = _pygi_lookup_signal_from_g_type (g_type, signal_name);
+    if (signal_info == NULL)
+        return NULL;
+
+    closure = g_closure_new_simple(sizeof(PyGISignalClosure), NULL);
+    g_closure_add_invalidate_notifier(closure, NULL, pygi_signal_closure_invalidate);
+    g_closure_set_marshal(closure, pygi_signal_closure_marshal);
+
+    pygi_closure = (PyGISignalClosure *)closure;
+
+    pygi_closure->signal_info = signal_info;
+    Py_INCREF(callback);
+    pygi_closure->pyg_closure.callback = callback;
+
+    if (extra_args != NULL && extra_args != Py_None) {
+        Py_INCREF(extra_args);
+        if (!PyTuple_Check(extra_args)) {
+            PyObject *tmp = PyTuple_New(1);
+            PyTuple_SetItem(tmp, 0, extra_args);
+            extra_args = tmp;
+        }
+        pygi_closure->pyg_closure.extra_args = extra_args;
+    }
+    if (swap_data) {
+        Py_INCREF(swap_data);
+        pygi_closure->pyg_closure.swap_data = swap_data;
+        closure->derivative_flag = TRUE;
+    }
+
+    return closure;
+}
diff --git a/gi/pygi-signal-closure.h b/gi/pygi-signal-closure.h
new file mode 100644 (file)
index 0000000..9ba8d6d
--- /dev/null
@@ -0,0 +1,50 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2011  Laszlo Pandy <lpandy@src.gnome.org>
+ *
+ * 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 __PYGI_SIGNAL_CLOSURE_H__
+#define __PYGI_SIGNAL_CLOSURE_H__
+
+#include <Python.h>
+#include <girepository.h>
+#include "pygobject-internal.h"
+
+G_BEGIN_DECLS
+
+/* Private */
+typedef struct _PyGISignalClosure
+{
+    PyGClosure pyg_closure;
+    GISignalInfo *signal_info;
+} PyGISignalClosure;
+
+GClosure *
+pygi_signal_closure_new (PyGObject *instance,
+                         GType g_type,
+                         const gchar *sig_name,
+                         PyObject *callback,
+                         PyObject *extra_args,
+                         PyObject *swap_data);
+
+G_END_DECLS
+
+#endif /* __PYGI_SIGNAL_CLOSURE_H__ */
diff --git a/gi/pygi-source.c b/gi/pygi-source.c
new file mode 100644 (file)
index 0000000..c6cdfda
--- /dev/null
@@ -0,0 +1,309 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 1998-2003  James Henstridge
+ * Copyright (C) 2005       Oracle
+ * Copyright (c) 2012       Canonical 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.
+ */
+
+#include "pygi-python-compat.h"
+#include "pygi-info.h"
+#include "pygi-boxed.h"
+#include "pygi-type.h"
+#include "pygi-basictype.h"
+#include "pygboxed.h"
+#include "pygi-source.h"
+
+typedef struct
+{
+    GSource source;
+    PyObject *obj;
+} PyGRealSource;
+
+static gboolean
+source_prepare(GSource *source, gint *timeout)
+{
+    PyGRealSource *pysource = (PyGRealSource *)source;
+    PyObject *t;
+    gboolean ret = FALSE;
+    gboolean got_err = TRUE;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+
+    t = PyObject_CallMethod(pysource->obj, "prepare", NULL);
+
+    if (t == NULL) {
+       goto bail;
+    } else if (!PyObject_IsTrue(t)) {
+       got_err = FALSE;
+       goto bail;
+    } else if (!PyTuple_Check(t)) {
+       PyErr_SetString(PyExc_TypeError,
+                       "source prepare function must return a tuple or False");
+       goto bail;
+    } else if (PyTuple_Size(t) != 2) {
+       PyErr_SetString(PyExc_TypeError,
+                       "source prepare function return tuple must be exactly "
+                       "2 elements long");
+       goto bail;
+    }
+
+    if (!pygi_gboolean_from_py (PyTuple_GET_ITEM(t, 0), &ret)) {
+        ret = FALSE;
+        goto bail;
+    }
+
+    if (!pygi_gint_from_py (PyTuple_GET_ITEM(t, 1), timeout))
+    {
+        ret = FALSE;
+        goto bail;
+    }
+
+    got_err = FALSE;
+
+bail:
+    if (got_err)
+       PyErr_Print();
+
+    Py_XDECREF(t);
+
+    PyGILState_Release(state);
+
+    return ret;
+}
+
+static gboolean
+source_check(GSource *source)
+{
+    PyGRealSource *pysource = (PyGRealSource *)source;
+    PyObject *t;
+    gboolean ret;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+
+    t = PyObject_CallMethod(pysource->obj, "check", NULL);
+
+    if (t == NULL) {
+       PyErr_Print();
+       ret = FALSE;
+    } else {
+       ret = PyObject_IsTrue(t);
+       Py_DECREF(t);
+    }
+
+    PyGILState_Release(state);
+
+    return ret;
+}
+
+static gboolean
+source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
+{
+    PyGRealSource *pysource = (PyGRealSource *)source;
+    PyObject *func, *args, *tuple, *t;
+    gboolean ret;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+
+    if (callback) {
+       tuple = user_data;
+
+       func = PyTuple_GetItem(tuple, 0);
+        args = PyTuple_GetItem(tuple, 1);
+    } else {
+       func = Py_None;
+       args = Py_None;
+    }
+
+    t = PyObject_CallMethod(pysource->obj, "dispatch", "OO", func, args);
+
+    if (t == NULL) {
+       PyErr_Print();
+       ret = FALSE;
+    } else {
+       ret = PyObject_IsTrue(t);
+       Py_DECREF(t);
+    }
+
+    PyGILState_Release(state);
+
+    return ret;
+}
+
+static void
+source_finalize(GSource *source)
+{
+    PyGRealSource *pysource = (PyGRealSource *)source;
+    PyObject *func, *t;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+
+    func = PyObject_GetAttrString(pysource->obj, "finalize");
+    if (func) {
+       t = PyObject_CallObject(func, NULL);
+       Py_DECREF(func);
+
+       if (t == NULL) {
+           PyErr_Print();
+       } else {
+           Py_DECREF(t);
+       }
+    } else {
+        PyErr_Clear ();
+    }
+
+    PyGILState_Release(state);
+}
+
+static GSourceFuncs pyg_source_funcs =
+{
+    source_prepare,
+    source_check,
+    source_dispatch,
+    source_finalize
+};
+
+/**
+ * _pyglib_destroy_notify:
+ * @user_data: a PyObject pointer.
+ *
+ * A function that can be used as a GDestroyNotify callback that will
+ * call Py_DECREF on the data.
+ */
+static void
+destroy_notify(gpointer user_data)
+{
+    PyObject *obj = (PyObject *)user_data;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+    Py_DECREF(obj);
+    PyGILState_Release(state);
+}
+
+static gboolean
+handler_marshal(gpointer user_data)
+{
+    PyObject *tuple, *ret;
+    gboolean res;
+    PyGILState_STATE state;
+
+    g_return_val_if_fail(user_data != NULL, FALSE);
+
+    state = PyGILState_Ensure();
+
+    tuple = (PyObject *)user_data;
+    ret = PyObject_CallObject(PyTuple_GetItem(tuple, 0),
+                             PyTuple_GetItem(tuple, 1));
+    if (!ret) {
+       PyErr_Print();
+       res = FALSE;
+    } else {
+       res = PyObject_IsTrue(ret);
+       Py_DECREF(ret);
+    }
+    
+    PyGILState_Release(state);
+
+    return res;
+}
+
+PyObject *
+pygi_source_set_callback (PyGObject *self_module, PyObject *args)
+{
+    PyObject *self, *first, *callback, *cbargs = NULL, *data;
+    Py_ssize_t len;
+
+    len = PyTuple_Size (args);
+    if (len < 2) {
+       PyErr_SetString(PyExc_TypeError,
+                       "set_callback requires at least 2 arguments");
+       return NULL;
+    }
+
+    first = PySequence_GetSlice(args, 0, 2);
+    if (!PyArg_ParseTuple(first, "OO:set_callback", &self, &callback)) {
+       Py_DECREF (first);
+       return NULL;
+    }
+    Py_DECREF(first);
+    
+    if (!pyg_boxed_check (self, G_TYPE_SOURCE)) {
+       PyErr_SetString(PyExc_TypeError, "first argument is not a GLib.Source");
+       return NULL;
+    }
+
+    if (!PyCallable_Check(callback)) {
+       PyErr_SetString(PyExc_TypeError, "second argument not callable");
+       return NULL;
+    }
+
+    cbargs = PySequence_GetSlice(args, 2, len);
+    if (cbargs == NULL)
+       return NULL;
+
+    data = Py_BuildValue("(ON)", callback, cbargs);
+    if (data == NULL)
+       return NULL;
+
+    g_source_set_callback(pyg_boxed_get (self, GSource),
+                         handler_marshal, data,
+                         destroy_notify);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+/**
+ * pygi_source_new:
+ *
+ * Wrap the un-bindable g_source_new() and provide wrapper callbacks in the
+ * GSourceFuncs which call back to Python.
+ *
+ * Returns NULL on error and sets an exception.
+ */
+PyObject*
+pygi_source_new (PyObject *self, PyObject *args)
+{
+    PyGRealSource *source;
+    PyObject *py_type, *boxed;
+
+    g_assert (args == NULL);
+
+    py_type = pygi_type_import_by_name ("GLib", "Source");
+    if (!py_type)
+        return NULL;
+
+    source = (PyGRealSource*) g_source_new (&pyg_source_funcs, sizeof (PyGRealSource));
+    /* g_source_new uses malloc, not slices */
+    boxed = pygi_boxed_new ( (PyTypeObject *) py_type, source, TRUE, 0);
+    Py_DECREF (py_type);
+    if (!boxed) {
+        g_source_unref ((GSource *)source);
+        return NULL;
+    }
+    source->obj = boxed;
+
+    return source->obj;
+}
diff --git a/gi/pygi-source.h b/gi/pygi-source.h
new file mode 100644 (file)
index 0000000..5d60c63
--- /dev/null
@@ -0,0 +1,31 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2012 Canonical 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.
+ */
+
+#ifndef __PYGI_SOURCE_H__
+#define __PYGI_SOURCE_H__
+
+PyObject *pygi_source_new (PyObject *self, PyObject *args);
+PyObject *pygi_source_set_callback (PyGObject *self, PyObject *args);
+
+#endif /* __PYGI_SOURCE_H__ */
+
diff --git a/gi/pygi-struct-marshal.c b/gi/pygi-struct-marshal.c
new file mode 100644 (file)
index 0000000..a7705a5
--- /dev/null
@@ -0,0 +1,614 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include <glib.h>
+
+#include "pygi-python-compat.h"
+#include "pygi-struct-marshal.h"
+#include "pygi-struct.h"
+#include "pygi-foreign.h"
+#include "pygi-value.h"
+#include "pygi-type.h"
+#include "pygi-boxed.h"
+#include "pygi-info.h"
+#include "pygpointer.h"
+#include "pygboxed.h"
+#include "pygi-type.h"
+
+/*
+ * _is_union_member - check to see if the py_arg is actually a member of the
+ * expected C union
+ */
+static gboolean
+_is_union_member (GIInterfaceInfo *interface_info, PyObject *py_arg) {
+    gint i;
+    gint n_fields;
+    GIUnionInfo *union_info;
+    GIInfoType info_type;
+    gboolean is_member = FALSE;
+
+    info_type = g_base_info_get_type (interface_info);
+
+    if (info_type != GI_INFO_TYPE_UNION)
+        return FALSE;
+
+    union_info = (GIUnionInfo *) interface_info;
+    n_fields = g_union_info_get_n_fields (union_info);
+
+    for (i = 0; i < n_fields; i++) {
+        GIFieldInfo *field_info;
+        GITypeInfo *field_type_info;
+
+        field_info = g_union_info_get_field (union_info, i);
+        field_type_info = g_field_info_get_type (field_info);
+
+        /* we can only check if the members are interfaces */
+        if (g_type_info_get_tag (field_type_info) == GI_TYPE_TAG_INTERFACE) {
+            GIInterfaceInfo *field_iface_info;
+            PyObject *py_type;
+
+            field_iface_info = g_type_info_get_interface (field_type_info);
+            py_type = pygi_type_import_by_gi_info ((GIBaseInfo *) field_iface_info);
+
+            if (py_type != NULL && PyObject_IsInstance (py_arg, py_type)) {
+                is_member = TRUE;
+            }
+
+            Py_XDECREF (py_type);
+            g_base_info_unref ( ( GIBaseInfo *) field_iface_info);
+        }
+
+        g_base_info_unref ( ( GIBaseInfo *) field_type_info);
+        g_base_info_unref ( ( GIBaseInfo *) field_info);
+
+        if (is_member)
+            break;
+    }
+
+    return is_member;
+}
+
+
+/*
+ * GValue from Python
+ */
+
+/* pygi_arg_gvalue_from_py_marshal:
+ * py_arg: (in):
+ * arg: (out):
+ * transfer:
+ * copy_reference: TRUE if arg should use the pointer reference held by py_arg
+ *                 when it is already holding a GValue vs. copying the value.
+ */
+gboolean
+pygi_arg_gvalue_from_py_marshal (PyObject *py_arg,
+                                 GIArgument *arg,
+                                 GITransfer transfer,
+                                 gboolean copy_reference) {
+    GValue *value;
+    GType object_type;
+
+    object_type = pyg_type_from_object_strict ( (PyObject *) Py_TYPE (py_arg), FALSE);
+    if (object_type == G_TYPE_INVALID) {
+        PyErr_SetString (PyExc_RuntimeError, "unable to retrieve object's GType");
+        return FALSE;
+    }
+
+    /* if already a gvalue, use that, else marshal into gvalue */
+    if (object_type == G_TYPE_VALUE) {
+        GValue *source_value = pyg_boxed_get (py_arg, GValue);
+        if (copy_reference) {
+            value = source_value;
+        } else {
+            value = g_slice_new0 (GValue);
+            g_value_init (value, G_VALUE_TYPE (source_value));
+            g_value_copy (source_value, value);
+        }
+    } else {
+        value = g_slice_new0 (GValue);
+        g_value_init (value, object_type);
+        if (pyg_value_from_pyobject_with_error (value, py_arg) < 0) {
+            g_slice_free (GValue, value);
+            return FALSE;
+        }
+    }
+
+    arg->v_pointer = value;
+    return TRUE;
+}
+
+void
+pygi_arg_gvalue_from_py_cleanup (PyGIInvokeState *state,
+                                 PyGIArgCache    *arg_cache,
+                                 PyObject        *py_arg,
+                                 gpointer         data,
+                                 gboolean         was_processed)
+{
+    /* Note py_arg can be NULL for hash table which is a bug. */
+    if (was_processed && py_arg != NULL) {
+        GType py_object_type =
+            pyg_type_from_object_strict ( (PyObject *) Py_TYPE (py_arg), FALSE);
+
+        /* When a GValue was not passed, it means the marshalers created a new
+         * one to pass in, clean this up.
+         */
+        if (py_object_type != G_TYPE_VALUE) {
+            g_value_unset ((GValue *) data);
+            g_slice_free (GValue, data);
+        }
+    }
+}
+
+/* pygi_arg_gclosure_from_py_marshal:
+ * py_arg: (in):
+ * arg: (out):
+ */
+static gboolean
+pygi_arg_gclosure_from_py_marshal (PyObject   *py_arg,
+                                   GIArgument *arg,
+                                   GITransfer  transfer)
+{
+    GClosure *closure;
+    GType object_gtype = pyg_type_from_object_strict (py_arg, FALSE);
+
+    if ( !(PyCallable_Check(py_arg) ||
+           g_type_is_a (object_gtype, G_TYPE_CLOSURE))) {
+        PyErr_Format (PyExc_TypeError, "Must be callable, not %s",
+                      Py_TYPE (py_arg)->tp_name);
+        return FALSE;
+    }
+
+    if (g_type_is_a (object_gtype, G_TYPE_CLOSURE)) {
+        closure = (GClosure *)pyg_boxed_get (py_arg, void);
+        /* Make sure we own a ref which is held until cleanup. */
+        if (closure != NULL) {
+            g_closure_ref (closure);
+        }
+    } else {
+        closure = pyg_closure_new (py_arg, NULL, NULL);
+        g_closure_ref (closure);
+        g_closure_sink (closure);
+    }
+
+    if (closure == NULL) {
+        PyErr_SetString (PyExc_RuntimeError, "PyObject conversion to GClosure failed");
+        return FALSE;
+    }
+
+    /* Add an additional ref when transfering everything to the callee. */
+    if (transfer == GI_TRANSFER_EVERYTHING) {
+        g_closure_ref (closure);
+    }
+
+    arg->v_pointer = closure;
+    return TRUE;
+}
+
+static void
+arg_gclosure_from_py_cleanup (PyGIInvokeState *state,
+                              PyGIArgCache    *arg_cache,
+                              PyObject        *py_arg,
+                              gpointer         cleanup_data,
+                              gboolean         was_processed)
+{
+    if (cleanup_data != NULL) {
+        g_closure_unref (cleanup_data);
+    }
+}
+
+/* pygi_arg_struct_from_py_marshal:
+ *
+ * Dispatcher to various sub marshalers
+ */
+gboolean
+pygi_arg_struct_from_py_marshal (PyObject *py_arg,
+                                 GIArgument *arg,
+                                 const gchar *arg_name,
+                                 GIBaseInfo *interface_info,
+                                 GType g_type,
+                                 PyObject *py_type,
+                                 GITransfer transfer,
+                                 gboolean copy_reference,
+                                 gboolean is_foreign,
+                                 gboolean is_pointer)
+{
+    gboolean is_union = FALSE;
+
+    if (py_arg == Py_None) {
+        arg->v_pointer = NULL;
+        return TRUE;
+    }
+
+    /* FIXME: handle this large if statement in the cache
+     *        and set the correct marshaller
+     */
+
+    if (g_type_is_a (g_type, G_TYPE_CLOSURE)) {
+        return pygi_arg_gclosure_from_py_marshal (py_arg, arg, transfer);
+    } else if (g_type_is_a (g_type, G_TYPE_VALUE)) {
+        return pygi_arg_gvalue_from_py_marshal(py_arg,
+                                               arg,
+                                               transfer,
+                                               copy_reference);
+    } else if (is_foreign) {
+        PyObject *success;
+        success = pygi_struct_foreign_convert_to_g_argument (py_arg,
+                                                             interface_info,
+                                                             transfer,
+                                                             arg);
+
+        return (success == Py_None);
+    } else if (!PyObject_IsInstance (py_arg, py_type)) {
+        /* first check to see if this is a member of the expected union */
+        is_union = _is_union_member (interface_info, py_arg);
+        if (!is_union) {
+            goto type_error;
+        }
+    }
+
+    if (g_type_is_a (g_type, G_TYPE_BOXED)) {
+        /* Additionally use pyg_type_from_object to pull the stashed __gtype__
+         * attribute off of the input argument for type checking. This is needed
+         * to work around type discrepancies in cases with aliased (typedef) types.
+         * e.g. GtkAllocation, GdkRectangle.
+         * See: https://bugzilla.gnomethere are .org/show_bug.cgi?id=707140
+         */
+        if (is_union || pyg_boxed_check (py_arg, g_type) ||
+                g_type_is_a (pyg_type_from_object (py_arg), g_type)) {
+            arg->v_pointer = pyg_boxed_get (py_arg, void);
+            if (transfer == GI_TRANSFER_EVERYTHING) {
+                arg->v_pointer = g_boxed_copy (g_type, arg->v_pointer);
+            }
+        } else {
+            goto type_error;
+        }
+
+    } else if (g_type_is_a (g_type, G_TYPE_POINTER) ||
+               g_type_is_a (g_type, G_TYPE_VARIANT) ||
+               g_type  == G_TYPE_NONE) {
+        g_warn_if_fail (g_type_is_a (g_type, G_TYPE_VARIANT) || !is_pointer || transfer == GI_TRANSFER_NOTHING);
+
+        if (g_type_is_a (g_type, G_TYPE_VARIANT) &&
+                pyg_type_from_object (py_arg) != G_TYPE_VARIANT) {
+            PyErr_SetString (PyExc_TypeError, "expected GLib.Variant");
+            return FALSE;
+        }
+        arg->v_pointer = pyg_pointer_get (py_arg, void);
+        if (transfer == GI_TRANSFER_EVERYTHING) {
+            g_variant_ref ((GVariant *)arg->v_pointer);
+        }
+
+    } else {
+        PyErr_Format (PyExc_NotImplementedError,
+                      "structure type '%s' is not supported yet",
+                      g_type_name(g_type));
+        return FALSE;
+    }
+    return TRUE;
+
+type_error:
+    {
+        gchar *type_name = _pygi_g_base_info_get_fullname (interface_info);
+        PyObject *module = PyObject_GetAttrString(py_arg, "__module__");
+
+        PyErr_Format (PyExc_TypeError, "argument %s: Expected %s, but got %s%s%s",
+                      arg_name ? arg_name : "self",
+                      type_name,
+                      module ? PYGLIB_PyUnicode_AsString(module) : "",
+                      module ? "." : "",
+                      Py_TYPE (py_arg)->tp_name);
+        if (module)
+            Py_DECREF (module);
+        g_free (type_name);
+        return FALSE;
+    }
+}
+
+static gboolean
+arg_struct_from_py_marshal_adapter (PyGIInvokeState   *state,
+                                    PyGICallableCache *callable_cache,
+                                    PyGIArgCache      *arg_cache,
+                                    PyObject          *py_arg,
+                                    GIArgument        *arg,
+                                    gpointer          *cleanup_data)
+{
+    PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)arg_cache;
+
+    gboolean res =  pygi_arg_struct_from_py_marshal (py_arg,
+                                                     arg,
+                                                     arg_cache->arg_name,
+                                                     iface_cache->interface_info,
+                                                     iface_cache->g_type,
+                                                     iface_cache->py_type,
+                                                     arg_cache->transfer,
+                                                     TRUE, /*copy_reference*/
+                                                     iface_cache->is_foreign,
+                                                     arg_cache->is_pointer);
+
+    /* Assume struct marshaling is always a pointer and assign cleanup_data
+     * here rather than passing it further down the chain.
+     */
+    *cleanup_data = arg->v_pointer;
+    return res;
+}
+
+static void
+arg_foreign_from_py_cleanup (PyGIInvokeState *state,
+                             PyGIArgCache    *arg_cache,
+                             PyObject        *py_arg,
+                             gpointer         data,
+                             gboolean         was_processed)
+{
+    if (state->failed && was_processed) {
+        pygi_struct_foreign_release (
+            ( (PyGIInterfaceCache *)arg_cache)->interface_info,
+            data);
+    }
+}
+
+static PyObject *
+pygi_arg_struct_to_py_marshaller (GIArgument *arg,
+                                  GIInterfaceInfo *interface_info,
+                                  GType g_type,
+                                  PyObject *py_type,
+                                  GITransfer transfer,
+                                  gboolean is_allocated,
+                                  gboolean is_foreign)
+{
+    PyObject *py_obj = NULL;
+
+    if (arg->v_pointer == NULL) {
+        Py_RETURN_NONE;
+    }
+
+    if (g_type_is_a (g_type, G_TYPE_VALUE)) {
+        py_obj = pyg_value_as_pyobject (arg->v_pointer, FALSE);
+    } else if (is_foreign) {
+        py_obj = pygi_struct_foreign_convert_from_g_argument (interface_info,
+                                                              transfer,
+                                                              arg->v_pointer);
+    } else if (g_type_is_a (g_type, G_TYPE_BOXED)) {
+        if (py_type) {
+            py_obj = pygi_boxed_new ((PyTypeObject *) py_type,
+                                     arg->v_pointer,
+                                     transfer == GI_TRANSFER_EVERYTHING || is_allocated,
+                                     is_allocated ?
+                                            g_struct_info_get_size(interface_info) : 0);
+        }
+    } else if (g_type_is_a (g_type, G_TYPE_POINTER)) {
+        if (py_type == NULL ||
+                !PyType_IsSubtype ((PyTypeObject *) py_type, &PyGIStruct_Type)) {
+            g_warn_if_fail (transfer == GI_TRANSFER_NOTHING);
+            py_obj = pyg_pointer_new (g_type, arg->v_pointer);
+        } else {
+            py_obj = pygi_struct_new ( (PyTypeObject *) py_type,
+                                      arg->v_pointer,
+                                      transfer == GI_TRANSFER_EVERYTHING);
+        }
+    } else if (g_type_is_a (g_type, G_TYPE_VARIANT)) {
+        /* Note: sink the variant (add a ref) only if we are not transfered ownership.
+         * GLib.Variant overrides __del__ which will then call "g_variant_unref" for
+         * cleanup in either case. */
+        if (py_type) {
+            if (transfer == GI_TRANSFER_NOTHING) {
+                g_variant_ref_sink (arg->v_pointer);
+            }
+            py_obj = pygi_struct_new ((PyTypeObject *) py_type,
+                                      arg->v_pointer,
+                                      FALSE);
+        }
+    } else if (g_type == G_TYPE_NONE) {
+        if (py_type) {
+            py_obj = pygi_struct_new ((PyTypeObject *) py_type,
+                                      arg->v_pointer,
+                                      transfer == GI_TRANSFER_EVERYTHING || is_allocated);
+        }
+    } else {
+        PyErr_Format (PyExc_NotImplementedError,
+                      "structure type '%s' is not supported yet",
+                      g_type_name (g_type));
+    }
+
+    return py_obj;
+}
+
+PyObject *
+pygi_arg_struct_to_py_marshal (GIArgument *arg,
+                               GIInterfaceInfo *interface_info,
+                               GType g_type,
+                               PyObject *py_type,
+                               GITransfer transfer,
+                               gboolean is_allocated,
+                               gboolean is_foreign)
+{
+    PyObject *ret = pygi_arg_struct_to_py_marshaller (arg, interface_info, g_type, py_type, transfer, is_allocated, is_foreign);
+
+    if (ret && PyObject_IsInstance (ret, (PyObject *) &PyGIBoxed_Type) && transfer == GI_TRANSFER_NOTHING)
+        pygi_boxed_copy_in_place ((PyGIBoxed *) ret);
+
+    return ret;
+};
+
+static PyObject *
+arg_struct_to_py_marshal_adapter (PyGIInvokeState   *state,
+                                  PyGICallableCache *callable_cache,
+                                  PyGIArgCache      *arg_cache,
+                                  GIArgument        *arg,
+                                  gpointer          *cleanup_data)
+{
+    PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)arg_cache;
+    PyObject *ret;
+
+    ret = pygi_arg_struct_to_py_marshaller (arg,
+                                            iface_cache->interface_info,
+                                            iface_cache->g_type,
+                                            iface_cache->py_type,
+                                            arg_cache->transfer,
+                                            arg_cache->is_caller_allocates,
+                                            iface_cache->is_foreign);
+
+    *cleanup_data = ret;
+
+    return ret;
+}
+
+static void
+arg_foreign_to_py_cleanup (PyGIInvokeState *state,
+                           PyGIArgCache    *arg_cache,
+                           gpointer         cleanup_data,
+                           gpointer         data,
+                           gboolean         was_processed)
+{
+    if (!was_processed && arg_cache->transfer == GI_TRANSFER_EVERYTHING) {
+        pygi_struct_foreign_release (
+            ( (PyGIInterfaceCache *)arg_cache)->interface_info,
+            data);
+    }
+}
+
+static void
+arg_boxed_to_py_cleanup (PyGIInvokeState *state,
+                           PyGIArgCache    *arg_cache,
+                           gpointer         cleanup_data,
+                           gpointer         data,
+                           gboolean         was_processed)
+{
+    if (arg_cache->transfer == GI_TRANSFER_NOTHING)
+        pygi_boxed_copy_in_place ((PyGIBoxed *) cleanup_data);
+}
+
+static gboolean
+arg_type_class_from_py_marshal (PyGIInvokeState   *state,
+                                PyGICallableCache *callable_cache,
+                                PyGIArgCache      *arg_cache,
+                                PyObject          *py_arg,
+                                GIArgument        *arg,
+                                gpointer          *cleanup_data)
+{
+    GType gtype = pyg_type_from_object (py_arg);
+
+    if (G_TYPE_IS_CLASSED (gtype)) {
+        arg->v_pointer = g_type_class_ref (gtype);
+        *cleanup_data = arg->v_pointer;
+        return TRUE;
+    } else {
+        PyErr_Format (PyExc_TypeError,
+                      "Unable to retrieve a GObject type class from \"%s\".",
+                      Py_TYPE(py_arg)->tp_name);
+        return FALSE;
+    }
+}
+
+static void
+arg_type_class_from_py_cleanup (PyGIInvokeState *state,
+                                PyGIArgCache    *arg_cache,
+                                PyObject        *py_arg,
+                                gpointer         data,
+                                gboolean         was_processed)
+{
+    if (was_processed) {
+        g_type_class_unref (data);
+    }
+}
+
+static void
+arg_struct_from_py_setup (PyGIArgCache     *arg_cache,
+                          GIInterfaceInfo  *iface_info,
+                          GITransfer        transfer)
+{
+    PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)arg_cache;
+
+    if (g_struct_info_is_gtype_struct ((GIStructInfo*)iface_info)) {
+        arg_cache->from_py_marshaller = arg_type_class_from_py_marshal;
+        /* Since we always add a ref in the marshalling, only unref the
+         * GTypeClass when we don't transfer ownership. */
+        if (transfer == GI_TRANSFER_NOTHING) {
+            arg_cache->from_py_cleanup = arg_type_class_from_py_cleanup;
+        }
+
+    } else {
+        arg_cache->from_py_marshaller = arg_struct_from_py_marshal_adapter;
+
+        if (g_type_is_a (iface_cache->g_type, G_TYPE_CLOSURE)) {
+            arg_cache->from_py_cleanup = arg_gclosure_from_py_cleanup;
+
+        } else if (iface_cache->g_type == G_TYPE_VALUE) {
+            arg_cache->from_py_cleanup = pygi_arg_gvalue_from_py_cleanup;
+
+        } else if (iface_cache->is_foreign) {
+            arg_cache->from_py_cleanup = arg_foreign_from_py_cleanup;
+        }
+    }
+}
+
+static void
+arg_struct_to_py_setup (PyGIArgCache     *arg_cache,
+                        GIInterfaceInfo  *iface_info,
+                        GITransfer        transfer)
+{
+    PyGIInterfaceCache *iface_cache = (PyGIInterfaceCache *)arg_cache;
+
+    if (arg_cache->to_py_marshaller == NULL) {
+        arg_cache->to_py_marshaller = arg_struct_to_py_marshal_adapter;
+    }
+
+    iface_cache->is_foreign = g_struct_info_is_foreign ( (GIStructInfo*)iface_info);
+
+    if (iface_cache->is_foreign)
+        arg_cache->to_py_cleanup = arg_foreign_to_py_cleanup;
+    else if (!g_type_is_a (iface_cache->g_type, G_TYPE_VALUE) &&
+             iface_cache->py_type &&
+             g_type_is_a (iface_cache->g_type, G_TYPE_BOXED))
+        arg_cache->to_py_cleanup = arg_boxed_to_py_cleanup;
+}
+
+PyGIArgCache *
+pygi_arg_struct_new_from_info (GITypeInfo      *type_info,
+                               GIArgInfo       *arg_info,
+                               GITransfer       transfer,
+                               PyGIDirection    direction,
+                               GIInterfaceInfo *iface_info)
+{
+    PyGIArgCache *cache = NULL;
+    PyGIInterfaceCache *iface_cache;
+
+    cache = pygi_arg_interface_new_from_info (type_info,
+                                              arg_info,
+                                              transfer,
+                                              direction,
+                                              iface_info);
+    if (cache == NULL)
+        return NULL;
+
+    iface_cache = (PyGIInterfaceCache *)cache;
+    iface_cache->is_foreign = (g_base_info_get_type ((GIBaseInfo *) iface_info) == GI_INFO_TYPE_STRUCT) &&
+                              (g_struct_info_is_foreign ((GIStructInfo*) iface_info));
+
+    if (direction & PYGI_DIRECTION_FROM_PYTHON) {
+        arg_struct_from_py_setup (cache, iface_info, transfer);
+    }
+
+    if (direction & PYGI_DIRECTION_TO_PYTHON) {
+        arg_struct_to_py_setup (cache, iface_info, transfer);
+    }
+
+    return cache;
+}
diff --git a/gi/pygi-struct-marshal.h b/gi/pygi-struct-marshal.h
new file mode 100644 (file)
index 0000000..b2846df
--- /dev/null
@@ -0,0 +1,69 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
+ * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_STRUCT_MARSHAL_H__
+#define __PYGI_STRUCT_MARSHAL_H__
+
+#include <girepository.h>
+#include "pygi-cache.h"
+
+G_BEGIN_DECLS
+
+PyGIArgCache *pygi_arg_struct_new_from_info  (GITypeInfo      *type_info,
+                                              GIArgInfo       *arg_info,   /* may be null */
+                                              GITransfer       transfer,
+                                              PyGIDirection    direction,
+                                              GIInterfaceInfo *iface_info);
+
+
+gboolean pygi_arg_gvalue_from_py_marshal     (PyObject        *py_arg, /*in*/
+                                              GIArgument      *arg,    /*out*/
+                                              GITransfer       transfer,
+                                              gboolean         is_allocated);
+
+gboolean pygi_arg_struct_from_py_marshal     (PyObject        *py_arg,
+                                              GIArgument      *arg,
+                                              const gchar     *arg_name,
+                                              GIBaseInfo      *interface_info,
+                                              GType            g_type,
+                                              PyObject        *py_type,
+                                              GITransfer       transfer,
+                                              gboolean         is_allocated,
+                                              gboolean         is_foreign,
+                                              gboolean         is_pointer);
+
+PyObject *pygi_arg_struct_to_py_marshal      (GIArgument      *arg,
+                                              GIInterfaceInfo *interface_info,
+                                              GType            g_type,
+                                              PyObject        *py_type,
+                                              GITransfer       transfer,
+                                              gboolean         is_allocated,
+                                              gboolean         is_foreign);
+
+/* Needed for hack in pygi-arg-garray.c */
+void pygi_arg_gvalue_from_py_cleanup         (PyGIInvokeState *state,
+                                              PyGIArgCache    *arg_cache,
+                                              PyObject        *py_arg,
+                                              gpointer         data,
+                                              gboolean         was_processed);
+
+G_END_DECLS
+
+#endif /*__PYGI_STRUCT_MARSHAL_H__*/
diff --git a/gi/pygi-struct.c b/gi/pygi-struct.c
new file mode 100644 (file)
index 0000000..4c05b7c
--- /dev/null
@@ -0,0 +1,254 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2009 Simon van der Linden <svdlinden@src.gnome.org>
+ *
+ *   pygi-struct.c: wrapper to handle non-registered structures.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pygi-struct.h"
+#include "pygi-foreign.h"
+#include "pygi-info.h"
+#include "pygi-type.h"
+#include "pygi-type.h"
+#include "pygpointer.h"
+#include "pygi-python-compat.h"
+
+#include <girepository.h>
+
+
+static GIBaseInfo *
+struct_get_info (PyTypeObject *type)
+{
+    PyObject *py_info;
+    GIBaseInfo *info = NULL;
+
+    py_info = PyObject_GetAttrString ((PyObject *)type, "__info__");
+    if (py_info == NULL) {
+        return NULL;
+    }
+    if (!PyObject_TypeCheck (py_info, &PyGIStructInfo_Type) &&
+            !PyObject_TypeCheck (py_info, &PyGIUnionInfo_Type)) {
+        PyErr_Format (PyExc_TypeError, "attribute '__info__' must be %s or %s, not %s",
+                      PyGIStructInfo_Type.tp_name,
+                      PyGIUnionInfo_Type.tp_name,
+                      Py_TYPE(py_info)->tp_name);
+        goto out;
+    }
+
+    info = ( (PyGIBaseInfo *) py_info)->info;
+    g_base_info_ref (info);
+
+out:
+    Py_DECREF (py_info);
+
+    return info;
+}
+
+static void
+struct_dealloc (PyGIStruct *self)
+{
+    GIBaseInfo *info;
+    PyObject *error_type, *error_value, *error_traceback;
+    gboolean have_error = !!PyErr_Occurred ();
+
+    if (have_error)
+        PyErr_Fetch (&error_type, &error_value, &error_traceback);
+
+    info = struct_get_info (Py_TYPE (self));
+
+    if (info != NULL && g_struct_info_is_foreign ( (GIStructInfo *) info)) {
+        pygi_struct_foreign_release (info, pyg_pointer_get_ptr (self));
+    } else if (self->free_on_dealloc) {
+        g_free (pyg_pointer_get_ptr (self));
+    }
+
+    if (info != NULL) {
+        g_base_info_unref (info);
+    }
+
+    if (have_error)
+        PyErr_Restore (error_type, error_value, error_traceback);
+
+    Py_TYPE (self)->tp_free ((PyObject *)self);
+}
+
+static PyObject *
+struct_new (PyTypeObject *type,
+            PyObject     *args,
+            PyObject     *kwargs)
+{
+    static char *kwlist[] = { NULL };
+
+    GIBaseInfo *info;
+    gsize size;
+    gpointer pointer;
+    PyObject *self = NULL;
+
+    if (!PyArg_ParseTupleAndKeywords (args, kwargs, "", kwlist)) {
+        return NULL;
+    }
+
+    info = struct_get_info ( type );
+    if (info == NULL) {
+        if (PyErr_ExceptionMatches (PyExc_AttributeError)) {
+            PyErr_Format (PyExc_TypeError, "missing introspection information");
+        }
+        return NULL;
+    }
+
+    size = g_struct_info_get_size ( (GIStructInfo *) info);
+    if (size == 0) {
+        PyErr_Format (PyExc_TypeError,
+            "struct cannot be created directly; try using a constructor, see: help(%s.%s)",
+            g_base_info_get_namespace (info),
+            g_base_info_get_name (info));
+        goto out;
+    }
+    pointer = g_try_malloc0 (size);
+    if (pointer == NULL) {
+        PyErr_NoMemory();
+        goto out;
+    }
+
+    self = pygi_struct_new (type, pointer, TRUE);
+    if (self == NULL) {
+        g_free (pointer);
+    }
+
+out:
+    g_base_info_unref (info);
+
+    return (PyObject *) self;
+}
+
+static int
+struct_init (PyObject *self,
+              PyObject *args,
+              PyObject *kwargs)
+{
+    /* Don't call PyGPointer's init, which raises an exception. */
+    return 0;
+}
+
+PYGLIB_DEFINE_TYPE("gi.Struct", PyGIStruct_Type, PyGIStruct);
+
+
+PyObject *
+pygi_struct_new_from_g_type (GType g_type,
+                             gpointer      pointer,
+                             gboolean      free_on_dealloc)
+{
+    PyGIStruct *self;
+    PyTypeObject *type;
+
+    type = (PyTypeObject *)pygi_type_import_by_g_type (g_type);
+
+    if (!type)
+        type = (PyTypeObject *)&PyGIStruct_Type; /* fallback */
+
+    if (!PyType_IsSubtype (type, &PyGIStruct_Type)) {
+        PyErr_SetString (PyExc_TypeError, "must be a subtype of gi.Struct");
+        return NULL;
+    }
+
+    self = (PyGIStruct *) type->tp_alloc (type, 0);
+    if (self == NULL) {
+        return NULL;
+    }
+
+    pyg_pointer_set_ptr (self, pointer);
+    ( (PyGPointer *) self)->gtype = g_type;
+    self->free_on_dealloc = free_on_dealloc;
+
+    return (PyObject *) self;
+}
+
+
+PyObject *
+pygi_struct_new (PyTypeObject *type,
+                 gpointer      pointer,
+                 gboolean      free_on_dealloc)
+{
+    PyGIStruct *self;
+    GType g_type;
+
+    if (!PyType_IsSubtype (type, &PyGIStruct_Type)) {
+        PyErr_SetString (PyExc_TypeError, "must be a subtype of gi.Struct");
+        return NULL;
+    }
+
+    self = (PyGIStruct *) type->tp_alloc (type, 0);
+    if (self == NULL) {
+        return NULL;
+    }
+
+    g_type = pyg_type_from_object ( (PyObject *) type);
+
+    pyg_pointer_set_ptr (self, pointer);
+    ( (PyGPointer *) self)->gtype = g_type;
+    self->free_on_dealloc = free_on_dealloc;
+
+    return (PyObject *) self;
+}
+
+static PyObject *
+struct_repr(PyGIStruct *self)
+{
+    PyObject* repr;
+    GIBaseInfo *info;
+    PyGPointer *pointer = (PyGPointer *)self;
+
+    info = struct_get_info (Py_TYPE (self));
+    if (info == NULL)
+        return NULL;
+
+    repr = PYGLIB_PyUnicode_FromFormat ("<%s.%s object at %p (%s at %p)>",
+                                        g_base_info_get_namespace (info),
+                                        g_base_info_get_name (info),
+                                        self, g_type_name (pointer->gtype),
+                                        pointer->pointer);
+
+    g_base_info_unref (info);
+
+    return repr;
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_struct_register_types (PyObject *m)
+{
+    Py_TYPE(&PyGIStruct_Type) = &PyType_Type;
+    g_assert (Py_TYPE (&PyGPointer_Type) != NULL);
+    PyGIStruct_Type.tp_base = &PyGPointer_Type;
+    PyGIStruct_Type.tp_new = (newfunc) struct_new;
+    PyGIStruct_Type.tp_init = (initproc) struct_init;
+    PyGIStruct_Type.tp_dealloc = (destructor) struct_dealloc;
+    PyGIStruct_Type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE);
+    PyGIStruct_Type.tp_repr = (reprfunc)struct_repr;
+
+    if (PyType_Ready (&PyGIStruct_Type) < 0)
+        return -1;
+    Py_INCREF ((PyObject *) &PyGIStruct_Type);
+    if (PyModule_AddObject (m, "Struct", (PyObject *) &PyGIStruct_Type) < 0) {
+        Py_DECREF ((PyObject *) &PyGIStruct_Type);
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/gi/pygi-struct.h b/gi/pygi-struct.h
new file mode 100644 (file)
index 0000000..27230a9
--- /dev/null
@@ -0,0 +1,49 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * Copyright (C) 2009 Simon van der Linden <svdlinden@src.gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_STRUCT_H__
+#define __PYGI_STRUCT_H__
+
+#include <Python.h>
+#include <pygobject-internal.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+    PyGPointer base;
+    gboolean free_on_dealloc;
+} PyGIStruct;
+
+extern PyTypeObject PyGIStruct_Type;
+
+PyObject *
+pygi_struct_new (PyTypeObject *type,
+                 gpointer      pointer,
+                 gboolean      free_on_dealloc);
+
+PyObject *
+pygi_struct_new_from_g_type (GType g_type,
+                             gpointer      pointer,
+                             gboolean      free_on_dealloc);
+
+int pygi_struct_register_types (PyObject *m);
+
+G_END_DECLS
+
+#endif /* __PYGI_STRUCT_H__ */
diff --git a/gi/pygi-type.c b/gi/pygi-type.c
new file mode 100644 (file)
index 0000000..3ca0fc8
--- /dev/null
@@ -0,0 +1,1394 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "pygobject-object.h"
+#include "pygboxed.h"
+#include "pygenum.h"
+#include "pygflags.h"
+#include "pygparamspec.h"
+#include "pygi-util.h"
+#include "pygpointer.h"
+#include "pyginterface.h"
+
+#include "pygi-type.h"
+#include "pygi-value.h"
+#include "pygi-basictype.h"
+
+PyObject *
+pygi_type_import_by_name (const char *namespace_,
+                          const char *name)
+{
+    gchar *module_name;
+    PyObject *py_module;
+    PyObject *py_object;
+
+    module_name = g_strconcat ("gi.repository.", namespace_, NULL);
+
+    py_module = pygi_import_module (module_name);
+
+    g_free (module_name);
+
+    if (py_module == NULL) {
+        return NULL;
+    }
+
+    py_object = PyObject_GetAttrString (py_module, name);
+
+    Py_DECREF (py_module);
+
+    return py_object;
+}
+
+PyObject *
+pygi_type_import_by_g_type (GType g_type)
+{
+    GIRepository *repository;
+    GIBaseInfo *info;
+    PyObject *type;
+
+    repository = g_irepository_get_default();
+
+    info = g_irepository_find_by_gtype (repository, g_type);
+    if (info == NULL) {
+        return NULL;
+    }
+
+    type = pygi_type_import_by_gi_info (info);
+    g_base_info_unref (info);
+
+    return type;
+}
+
+PyObject *
+pygi_type_import_by_gi_info (GIBaseInfo *info)
+{
+    return pygi_type_import_by_name (g_base_info_get_namespace (info),
+                                     g_base_info_get_name (info));
+}
+
+PyObject *
+pygi_type_get_from_g_type (GType g_type)
+{
+    PyObject *py_g_type;
+    PyObject *py_type;
+
+    py_g_type = pyg_type_wrapper_new (g_type);
+    if (py_g_type == NULL) {
+        return NULL;
+    }
+
+    py_type = PyObject_GetAttrString (py_g_type, "pytype");
+    if (py_type == Py_None) {
+        py_type = pygi_type_import_by_g_type (g_type);
+    }
+
+    Py_DECREF (py_g_type);
+
+    return py_type;
+}
+
+/* -------------- __gtype__ objects ---------------------------- */
+
+typedef struct {
+    PyObject_HEAD
+    GType type;
+} PyGTypeWrapper;
+
+PYGLIB_DEFINE_TYPE("gobject.GType", PyGTypeWrapper_Type, PyGTypeWrapper);
+
+static PyObject*
+generic_gsize_richcompare(gsize a, gsize b, int op)
+{
+    PyObject *res;
+
+    switch (op) {
+
+      case Py_EQ:
+        res = (a == b) ? Py_True : Py_False;
+        Py_INCREF(res);
+        break;
+
+      case Py_NE:
+        res = (a != b) ? Py_True : Py_False;
+        Py_INCREF(res);
+        break;
+
+
+      case Py_LT:
+        res = (a < b) ? Py_True : Py_False;
+        Py_INCREF(res);
+        break;
+
+      case Py_LE:
+        res = (a <= b) ? Py_True : Py_False;
+        Py_INCREF(res);
+        break;
+
+      case Py_GT:
+        res = (a > b) ? Py_True : Py_False;
+        Py_INCREF(res);
+        break;
+
+      case Py_GE:
+        res = (a >= b) ? Py_True : Py_False;
+        Py_INCREF(res);
+        break;
+
+      default:
+        res = Py_NotImplemented;
+        Py_INCREF(res);
+        break;
+    }
+
+    return res;
+}
+
+static PyObject*
+pyg_type_wrapper_richcompare(PyObject *self, PyObject *other, int op)
+{
+    if (Py_TYPE(self) == Py_TYPE(other) && Py_TYPE(self) == &PyGTypeWrapper_Type)
+        return generic_gsize_richcompare(((PyGTypeWrapper*)self)->type,
+                                        ((PyGTypeWrapper*)other)->type,
+                                        op);
+    else {
+        Py_INCREF(Py_NotImplemented);
+        return Py_NotImplemented;
+    }
+}
+
+static long
+pyg_type_wrapper_hash(PyGTypeWrapper *self)
+{
+    return (long)self->type;
+}
+
+static PyObject *
+pyg_type_wrapper_repr(PyGTypeWrapper *self)
+{
+    char buf[80];
+    const gchar *name = g_type_name(self->type);
+
+    g_snprintf(buf, sizeof(buf), "<GType %s (%lu)>",
+              name?name:"invalid", (unsigned long int) self->type);
+    return PYGLIB_PyUnicode_FromString(buf);
+}
+
+static void
+pyg_type_wrapper_dealloc(PyGTypeWrapper *self)
+{
+    PyObject_DEL(self);
+}
+
+static GQuark
+_pyg_type_key(GType type) {
+    GQuark key;
+
+    if (g_type_is_a(type, G_TYPE_INTERFACE)) {
+        key = pyginterface_type_key;
+    } else if (g_type_is_a(type, G_TYPE_ENUM)) {
+        key = pygenum_class_key;
+    } else if (g_type_is_a(type, G_TYPE_FLAGS)) {
+        key = pygflags_class_key;
+    } else if (g_type_is_a(type, G_TYPE_POINTER)) {
+        key = pygpointer_class_key;
+    } else if (g_type_is_a(type, G_TYPE_BOXED)) {
+        key = pygboxed_type_key;
+    } else {
+        key = pygobject_class_key;
+    }
+
+    return key;
+}
+
+static PyObject *
+_wrap_g_type_wrapper__get_pytype(PyGTypeWrapper *self, void *closure)
+{
+    GQuark key;
+    PyObject *py_type;
+
+    key = _pyg_type_key(self->type);
+
+    py_type = g_type_get_qdata(self->type, key);
+    if (!py_type)
+      py_type = Py_None;
+
+    Py_INCREF(py_type);
+    return py_type;
+}
+
+static int
+_wrap_g_type_wrapper__set_pytype(PyGTypeWrapper *self, PyObject* value, void *closure)
+{
+    GQuark key;
+    PyObject *py_type;
+
+    key = _pyg_type_key(self->type);
+
+    py_type = g_type_get_qdata(self->type, key);
+    Py_CLEAR(py_type);
+    if (value == Py_None)
+       g_type_set_qdata(self->type, key, NULL);
+    else if (PyType_Check(value)) {
+       Py_INCREF(value);
+       g_type_set_qdata(self->type, key, value);
+    } else {
+       PyErr_SetString(PyExc_TypeError, "Value must be None or a type object");
+       return -1;
+    }
+
+    return 0;
+}
+
+static PyObject *
+_wrap_g_type_wrapper__get_name(PyGTypeWrapper *self, void *closure)
+{
+   const char *name = g_type_name(self->type);
+   return PYGLIB_PyUnicode_FromString(name ? name : "invalid");
+}
+
+static PyObject *
+_wrap_g_type_wrapper__get_parent(PyGTypeWrapper *self, void *closure)
+{
+   return pyg_type_wrapper_new(g_type_parent(self->type));
+}
+
+static PyObject *
+_wrap_g_type_wrapper__get_fundamental(PyGTypeWrapper *self, void *closure)
+{
+   return pyg_type_wrapper_new(g_type_fundamental(self->type));
+}
+
+static PyObject *
+_wrap_g_type_wrapper__get_children(PyGTypeWrapper *self, void *closure)
+{
+  guint n_children, i;
+  GType *children;
+  PyObject *retval;
+
+  children = g_type_children(self->type, &n_children);
+
+  retval = PyList_New(n_children);
+  for (i = 0; i < n_children; i++)
+      PyList_SetItem(retval, i, pyg_type_wrapper_new(children[i]));
+  g_free(children);
+
+  return retval;
+}
+
+static PyObject *
+_wrap_g_type_wrapper__get_interfaces(PyGTypeWrapper *self, void *closure)
+{
+  guint n_interfaces, i;
+  GType *interfaces;
+  PyObject *retval;
+
+  interfaces = g_type_interfaces(self->type, &n_interfaces);
+
+  retval = PyList_New(n_interfaces);
+  for (i = 0; i < n_interfaces; i++)
+      PyList_SetItem(retval, i, pyg_type_wrapper_new(interfaces[i]));
+  g_free(interfaces);
+
+  return retval;
+}
+
+static PyObject *
+_wrap_g_type_wrapper__get_depth(PyGTypeWrapper *self, void *closure)
+{
+  return pygi_guint_to_py (g_type_depth (self->type));
+}
+
+static PyGetSetDef _PyGTypeWrapper_getsets[] = {
+    { "pytype", (getter)_wrap_g_type_wrapper__get_pytype, (setter)_wrap_g_type_wrapper__set_pytype },
+    { "name",  (getter)_wrap_g_type_wrapper__get_name, (setter)0 },
+    { "fundamental",  (getter)_wrap_g_type_wrapper__get_fundamental, (setter)0 },
+    { "parent",  (getter)_wrap_g_type_wrapper__get_parent, (setter)0 },
+    { "children",  (getter)_wrap_g_type_wrapper__get_children, (setter)0 },
+    { "interfaces",  (getter)_wrap_g_type_wrapper__get_interfaces, (setter)0 },
+    { "depth",  (getter)_wrap_g_type_wrapper__get_depth, (setter)0 },
+    { NULL, (getter)0, (setter)0 }
+};
+
+static PyObject*
+_wrap_g_type_is_interface(PyGTypeWrapper *self)
+{
+    return pygi_gboolean_to_py (G_TYPE_IS_INTERFACE (self->type));
+}
+
+static PyObject*
+_wrap_g_type_is_classed(PyGTypeWrapper *self)
+{
+    return pygi_gboolean_to_py (G_TYPE_IS_CLASSED (self->type));
+}
+
+static PyObject*
+_wrap_g_type_is_instantiatable(PyGTypeWrapper *self)
+{
+    return pygi_gboolean_to_py (G_TYPE_IS_INSTANTIATABLE(self->type));
+}
+
+static PyObject*
+_wrap_g_type_is_derivable(PyGTypeWrapper *self)
+{
+    return pygi_gboolean_to_py (G_TYPE_IS_DERIVABLE (self->type));
+}
+
+static PyObject*
+_wrap_g_type_is_deep_derivable(PyGTypeWrapper *self)
+{
+    return pygi_gboolean_to_py (G_TYPE_IS_DEEP_DERIVABLE (self->type));
+}
+
+static PyObject*
+_wrap_g_type_is_abstract(PyGTypeWrapper *self)
+{
+    return pygi_gboolean_to_py (G_TYPE_IS_ABSTRACT (self->type));
+}
+
+static PyObject*
+_wrap_g_type_is_value_abstract(PyGTypeWrapper *self)
+{
+    return pygi_gboolean_to_py (G_TYPE_IS_VALUE_ABSTRACT (self->type));
+}
+
+static PyObject*
+_wrap_g_type_is_value_type(PyGTypeWrapper *self)
+{
+    return pygi_gboolean_to_py (G_TYPE_IS_VALUE_TYPE (self->type));
+}
+
+static PyObject*
+_wrap_g_type_has_value_table(PyGTypeWrapper *self)
+{
+    return pygi_gboolean_to_py (G_TYPE_HAS_VALUE_TABLE (self->type));
+}
+
+static PyObject*
+_wrap_g_type_from_name(PyGTypeWrapper *_, PyObject *args)
+{
+    char *type_name;
+    GType type;
+
+    if (!PyArg_ParseTuple(args, "s:GType.from_name", &type_name))
+       return NULL;
+
+    type = g_type_from_name(type_name);
+    if (type == 0) {
+       PyErr_SetString(PyExc_RuntimeError, "unknown type name");
+       return NULL;
+    }
+
+    return pyg_type_wrapper_new(type);
+}
+
+static PyObject*
+_wrap_g_type_is_a(PyGTypeWrapper *self, PyObject *args)
+{
+    PyObject *gparent;
+    GType parent;
+
+    if (!PyArg_ParseTuple(args, "O:GType.is_a", &gparent))
+       return NULL;
+    else if ((parent = pyg_type_from_object(gparent)) == 0)
+       return NULL;
+
+    return pygi_gboolean_to_py (g_type_is_a (self->type, parent));
+}
+
+static PyMethodDef _PyGTypeWrapper_methods[] = {
+    { "is_interface", (PyCFunction)_wrap_g_type_is_interface, METH_NOARGS },
+    { "is_classed", (PyCFunction)_wrap_g_type_is_classed, METH_NOARGS },
+    { "is_instantiatable", (PyCFunction)_wrap_g_type_is_instantiatable, METH_NOARGS },
+    { "is_derivable", (PyCFunction)_wrap_g_type_is_derivable, METH_NOARGS },
+    { "is_deep_derivable", (PyCFunction)_wrap_g_type_is_deep_derivable, METH_NOARGS },
+    { "is_abstract", (PyCFunction)_wrap_g_type_is_abstract, METH_NOARGS },
+    { "is_value_abstract", (PyCFunction)_wrap_g_type_is_value_abstract, METH_NOARGS },
+    { "is_value_type", (PyCFunction)_wrap_g_type_is_value_type, METH_NOARGS },
+    { "has_value_table", (PyCFunction)_wrap_g_type_has_value_table, METH_NOARGS },
+    { "from_name", (PyCFunction)_wrap_g_type_from_name, METH_VARARGS | METH_STATIC },
+    { "is_a", (PyCFunction)_wrap_g_type_is_a, METH_VARARGS },
+    { NULL,  0, 0 }
+};
+
+static int
+pyg_type_wrapper_init(PyGTypeWrapper *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { "object", NULL };
+    PyObject *py_object;
+    GType type;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+                                    "O:GType.__init__",
+                                    kwlist, &py_object))
+        return -1;
+
+    if (!(type = pyg_type_from_object(py_object)))
+       return -1;
+
+    self->type = type;
+
+    return 0;
+}
+
+/**
+ * pyg_type_wrapper_new:
+ * type: a GType
+ *
+ * Creates a Python wrapper for a GType.
+ *
+ * Returns: the Python wrapper.
+ */
+PyObject *
+pyg_type_wrapper_new(GType type)
+{
+    PyGTypeWrapper *self;
+
+    g_assert (Py_TYPE (&PyGTypeWrapper_Type) != NULL);
+    self = (PyGTypeWrapper *)PyObject_NEW(PyGTypeWrapper,
+                                         &PyGTypeWrapper_Type);
+    if (self == NULL)
+       return NULL;
+
+    self->type = type;
+    return (PyObject *)self;
+}
+
+/**
+ * pyg_type_from_object_strict:
+ * obj: a Python object
+ * strict: if set to TRUE, raises an exception if it can't perform the
+ *         conversion
+ *
+ * converts a python object to a GType.  If strict is set, raises an 
+ * exception if it can't perform the conversion, otherwise returns
+ * PY_TYPE_OBJECT.
+ *
+ * Returns: the corresponding GType, or 0 on error.
+ */
+
+GType
+pyg_type_from_object_strict(PyObject *obj, gboolean strict)
+{
+    PyObject *gtype;
+    GType type;
+
+    /* NULL check */
+    if (!obj) {
+       PyErr_SetString(PyExc_TypeError, "can't get type from NULL object");
+       return 0;
+    }
+
+    /* map some standard types to primitive GTypes ... */
+    if (obj == Py_None)
+       return G_TYPE_NONE;
+    if (PyType_Check(obj)) {
+       PyTypeObject *tp = (PyTypeObject *)obj;
+
+       if (tp == &PYGLIB_PyLong_Type)
+           return G_TYPE_INT;
+       else if (tp == &PyBool_Type)
+           return G_TYPE_BOOLEAN;
+       else if (tp == &PyLong_Type)
+           return G_TYPE_LONG;
+       else if (tp == &PyFloat_Type)
+           return G_TYPE_DOUBLE;
+       else if (tp == &PYGLIB_PyUnicode_Type)
+           return G_TYPE_STRING;
+       else if (tp == &PyBaseObject_Type)
+           return PY_TYPE_OBJECT;
+    }
+
+    if (Py_TYPE(obj) == &PyGTypeWrapper_Type) {
+       return ((PyGTypeWrapper *)obj)->type;
+    }
+
+    /* handle strings */
+    if (PYGLIB_PyUnicode_Check(obj)) {
+       gchar *name = PYGLIB_PyUnicode_AsString(obj);
+
+       type = g_type_from_name(name);
+       if (type != 0) {
+           return type;
+       }
+    }
+
+    /* finally, look for a __gtype__ attribute on the object */
+    gtype = PyObject_GetAttrString(obj, "__gtype__");
+
+    if (gtype) {
+       if (Py_TYPE(gtype) == &PyGTypeWrapper_Type) {
+           type = ((PyGTypeWrapper *)gtype)->type;
+           Py_DECREF(gtype);
+           return type;
+       }
+       Py_DECREF(gtype);
+    }
+
+    PyErr_Clear();
+
+    /* Some API like those that take GValues can hold a python object as
+     * a pointer.  This is potentially dangerous becuase everything is 
+     * passed in as a PyObject so we can't actually type check it.  Only
+     * fallback to PY_TYPE_OBJECT if strict checking is disabled
+     */
+    if (!strict)
+        return PY_TYPE_OBJECT;
+
+    PyErr_SetString(PyExc_TypeError, "could not get typecode from object");
+    return 0;
+}
+
+/**
+ * pyg_type_from_object:
+ * obj: a Python object
+ *
+ * converts a python object to a GType.  Raises an exception if it
+ * can't perform the conversion.
+ *
+ * Returns: the corresponding GType, or 0 on error.
+ */
+GType
+pyg_type_from_object(PyObject *obj)
+{
+    /* Legacy call always defaults to strict type checking */
+    return pyg_type_from_object_strict(obj, TRUE);
+}
+
+/**
+ * pyg_enum_get_value:
+ * @enum_type: the GType of the flag.
+ * @obj: a Python object representing the flag value
+ * @val: a pointer to the location to store the integer representation of the flag.
+ *
+ * Converts a Python object to the integer equivalent.  The conversion
+ * will depend on the type of the Python object.  If the object is an
+ * integer, it is passed through directly.  If it is a string, it will
+ * be treated as a full or short enum name as defined in the GType.
+ *
+ * Returns: 0 on success or -1 on failure
+ */
+gint
+pyg_enum_get_value(GType enum_type, PyObject *obj, gint *val)
+{
+    GEnumClass *eclass = NULL;
+    gint res = -1;
+
+    g_return_val_if_fail(val != NULL, -1);
+    if (!obj) {
+       *val = 0;
+       res = 0;
+    } else if (PYGLIB_PyLong_Check(obj)) {
+       if (!pygi_gint_from_py (obj, val))
+           res = -1;
+       else
+           res = 0;
+
+       if (PyObject_TypeCheck(obj, &PyGEnum_Type) && ((PyGEnum *) obj)->gtype != enum_type) {
+           g_warning("expected enumeration type %s, but got %s instead",
+                     g_type_name(enum_type),
+                     g_type_name(((PyGEnum *) obj)->gtype));
+       }
+    /* Dumb code duplication, but probably not worth it to have yet another macro. */
+    } else if (PyLong_Check(obj)) {
+       if (!pygi_gint_from_py (obj, val))
+           res = -1;
+       else
+           res = 0;
+
+       if (PyObject_TypeCheck(obj, &PyGEnum_Type) && ((PyGEnum *) obj)->gtype != enum_type) {
+           g_warning("expected enumeration type %s, but got %s instead",
+                     g_type_name(enum_type),
+                     g_type_name(((PyGEnum *) obj)->gtype));
+       }
+    } else if (PYGLIB_PyUnicode_Check(obj)) {
+       GEnumValue *info;
+       char *str = PYGLIB_PyUnicode_AsString(obj);
+
+       if (enum_type != G_TYPE_NONE)
+           eclass = G_ENUM_CLASS(g_type_class_ref(enum_type));
+       else {
+           PyErr_SetString(PyExc_TypeError, "could not convert string to enum because there is no GType associated to look up the value");
+           res = -1;
+       }
+       info = g_enum_get_value_by_name(eclass, str);
+       g_type_class_unref(eclass);
+
+       if (!info)
+           info = g_enum_get_value_by_nick(eclass, str);
+       if (info) {
+           *val = info->value;
+           res = 0;
+       } else {
+           PyErr_SetString(PyExc_TypeError, "could not convert string");
+           res = -1;
+       }
+    } else {
+       PyErr_SetString(PyExc_TypeError,"enum values must be strings or ints");
+       res = -1;
+    }
+    return res;
+}
+
+/**
+ * pyg_flags_get_value:
+ * @flag_type: the GType of the flag.
+ * @obj: a Python object representing the flag value
+ * @val: a pointer to the location to store the integer representation of the flag.
+ *
+ * Converts a Python object to the integer equivalent.  The conversion
+ * will depend on the type of the Python object.  If the object is an
+ * integer, it is passed through directly.  If it is a string, it will
+ * be treated as a full or short flag name as defined in the GType.
+ * If it is a tuple, then the items are treated as strings and ORed
+ * together.
+ *
+ * Returns: 0 on success or -1 on failure
+ */
+gint
+pyg_flags_get_value(GType flag_type, PyObject *obj, guint *val)
+{
+    GFlagsClass *fclass = NULL;
+    gint res = -1;
+
+    g_return_val_if_fail(val != NULL, -1);
+    if (!obj) {
+       *val = 0;
+       res = 0;
+    } else if (PYGLIB_PyLong_Check(obj)) {
+       if (pygi_guint_from_py (obj, val))
+           res = 0;
+    } else if (PyLong_Check(obj)) {
+       if (pygi_guint_from_py (obj, val))
+           res = 0;
+    } else if (PYGLIB_PyUnicode_Check(obj)) {
+       GFlagsValue *info;
+       char *str = PYGLIB_PyUnicode_AsString(obj);
+
+       if (flag_type != G_TYPE_NONE)
+           fclass = G_FLAGS_CLASS(g_type_class_ref(flag_type));
+       else {
+           PyErr_SetString(PyExc_TypeError, "could not convert string to flag because there is no GType associated to look up the value");
+           res = -1;
+       }
+       info = g_flags_get_value_by_name(fclass, str);
+       g_type_class_unref(fclass);
+
+       if (!info)
+           info = g_flags_get_value_by_nick(fclass, str);
+       if (info) {
+           *val = info->value;
+           res = 0;
+       } else {
+           PyErr_SetString(PyExc_TypeError, "could not convert string");
+           res = -1;
+       }
+    } else if (PyTuple_Check(obj)) {
+       Py_ssize_t i, len;
+
+       len = PyTuple_Size(obj);
+       *val = 0;
+       res = 0;
+
+       if (flag_type != G_TYPE_NONE)
+           fclass = G_FLAGS_CLASS(g_type_class_ref(flag_type));
+       else {
+           PyErr_SetString(PyExc_TypeError, "could not convert string to flag because there is no GType associated to look up the value");
+           res = -1;
+       }
+
+       for (i = 0; i < len; i++) {
+           PyObject *item = PyTuple_GetItem(obj, i);
+           char *str = PYGLIB_PyUnicode_AsString(item);
+           GFlagsValue *info = g_flags_get_value_by_name(fclass, str);
+
+           if (!info)
+               info = g_flags_get_value_by_nick(fclass, str);
+           if (info) {
+               *val |= info->value;
+           } else {
+               PyErr_SetString(PyExc_TypeError, "could not convert string");
+               res = -1;
+               break;
+           }
+       }
+       g_type_class_unref(fclass);
+    } else {
+       PyErr_SetString(PyExc_TypeError,
+                       "flag values must be strings, ints, longs, or tuples");
+       res = -1;
+    }
+    return res;
+}
+
+static GQuark pyg_type_marshal_key = 0;
+static GQuark pyg_type_marshal_helper_key = 0;
+
+typedef enum _marshal_helper_data_e marshal_helper_data_e;
+enum _marshal_helper_data_e {
+    MARSHAL_HELPER_NONE = 0,
+    MARSHAL_HELPER_RETURN_NULL,
+    MARSHAL_HELPER_IMPORT_DONE,
+};
+
+PyGTypeMarshal *
+pyg_type_lookup(GType type)
+{
+    GType      ptype = type;
+    PyGTypeMarshal     *tm = NULL;
+    marshal_helper_data_e marshal_helper;
+
+    if (type == G_TYPE_INVALID)
+       return NULL;
+
+    marshal_helper = GPOINTER_TO_INT (
+       g_type_get_qdata(type, pyg_type_marshal_helper_key));
+
+    /* If we called this function before with @type and nothing was found,
+     * return NULL early to not spend time in the loop below */
+    if (marshal_helper == MARSHAL_HELPER_RETURN_NULL)
+       return NULL;
+
+    /* Otherwise do recursive type lookup */
+    do {
+       if (marshal_helper == MARSHAL_HELPER_IMPORT_DONE)
+           pygi_type_import_by_g_type (ptype);
+
+       if ((tm = g_type_get_qdata(ptype, pyg_type_marshal_key)) != NULL)
+           break;
+       ptype = g_type_parent(ptype);
+    } while (ptype);
+
+    if (marshal_helper == MARSHAL_HELPER_NONE) {
+       marshal_helper = (tm == NULL) ?
+           MARSHAL_HELPER_RETURN_NULL:
+           MARSHAL_HELPER_IMPORT_DONE;
+       g_type_set_qdata(type, pyg_type_marshal_helper_key,
+           GINT_TO_POINTER(marshal_helper));
+    }
+    return tm;
+}
+
+/**
+ * pyg_register_gtype_custom:
+ * @gtype: the GType for the new type
+ * @from_func: a function to convert GValues to Python objects
+ * @to_func: a function to convert Python objects to GValues
+ *
+ * In order to handle specific conversion of gboxed types or new
+ * fundamental types, you may use this function to register conversion
+ * handlers.
+ */
+
+void
+pyg_register_gtype_custom(GType gtype,
+                         fromvaluefunc from_func,
+                          tovaluefunc to_func)
+{
+    PyGTypeMarshal *tm;
+
+    if (!pyg_type_marshal_key) {
+       pyg_type_marshal_key = g_quark_from_static_string("PyGType::marshal");
+       pyg_type_marshal_helper_key = g_quark_from_static_string("PyGType::marshal-helper");
+    }
+
+    tm = g_new(PyGTypeMarshal, 1);
+    tm->fromvalue = from_func;
+    tm->tovalue = to_func;
+    g_type_set_qdata(gtype, pyg_type_marshal_key, tm);
+}
+
+/* -------------- PyGClosure ----------------- */
+
+static void
+pyg_closure_invalidate(gpointer data, GClosure *closure)
+{
+    PyGClosure *pc = (PyGClosure *)closure;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+    Py_XDECREF(pc->callback);
+    Py_XDECREF(pc->extra_args);
+    Py_XDECREF(pc->swap_data);
+    PyGILState_Release(state);
+
+    pc->callback = NULL;
+    pc->extra_args = NULL;
+    pc->swap_data = NULL;
+}
+
+static void
+pyg_closure_marshal(GClosure *closure,
+                   GValue *return_value,
+                   guint n_param_values,
+                   const GValue *param_values,
+                   gpointer invocation_hint,
+                   gpointer marshal_data)
+{
+    PyGILState_STATE state;
+    PyGClosure *pc = (PyGClosure *)closure;
+    PyObject *params, *ret;
+    guint i;
+
+    state = PyGILState_Ensure();
+
+    /* construct Python tuple for the parameter values */
+    params = PyTuple_New(n_param_values);
+    for (i = 0; i < n_param_values; i++) {
+       /* swap in a different initial data for connect_object() */
+       if (i == 0 && G_CCLOSURE_SWAP_DATA(closure)) {
+           g_return_if_fail(pc->swap_data != NULL);
+           Py_INCREF(pc->swap_data);
+           PyTuple_SetItem(params, 0, pc->swap_data);
+       } else {
+           PyObject *item = pyg_value_as_pyobject(&param_values[i], FALSE);
+
+           /* error condition */
+           if (!item) {
+            if (!PyErr_Occurred ())
+                PyErr_SetString (PyExc_TypeError,
+                                 "can't convert parameter to desired type");
+
+            if (pc->exception_handler)
+                pc->exception_handler (return_value, n_param_values, param_values);
+            else
+                PyErr_Print();
+
+            goto out;
+           }
+           PyTuple_SetItem(params, i, item);
+       }
+    }
+    /* params passed to function may have extra arguments */
+    if (pc->extra_args) {
+       PyObject *tuple = params;
+       params = PySequence_Concat(tuple, pc->extra_args);
+       Py_DECREF(tuple);
+    }
+    ret = PyObject_CallObject(pc->callback, params);
+    if (ret == NULL) {
+       if (pc->exception_handler)
+           pc->exception_handler(return_value, n_param_values, param_values);
+       else
+           PyErr_Print();
+       goto out;
+    }
+
+    if (G_IS_VALUE(return_value) && pyg_value_from_pyobject(return_value, ret) != 0) {
+       /* If we already have an exception set, use that, otherwise set a
+        * generic one */
+       if (!PyErr_Occurred())
+           PyErr_SetString(PyExc_TypeError,
+                            "can't convert return value to desired type");
+
+       if (pc->exception_handler)
+           pc->exception_handler(return_value, n_param_values, param_values);
+       else
+           PyErr_Print();
+    }
+    Py_DECREF(ret);
+
+ out:
+    Py_DECREF(params);
+    PyGILState_Release(state);
+}
+
+/**
+ * pyg_closure_new:
+ * callback: a Python callable object
+ * extra_args: a tuple of extra arguments, or None/NULL.
+ * swap_data: an alternative python object to pass first.
+ *
+ * Creates a GClosure wrapping a Python callable and optionally a set
+ * of additional function arguments.  This is needed to attach python
+ * handlers to signals, for instance.
+ *
+ * Returns: the new closure.
+ */
+GClosure *
+pyg_closure_new(PyObject *callback, PyObject *extra_args, PyObject *swap_data)
+{
+    GClosure *closure;
+
+    g_return_val_if_fail(callback != NULL, NULL);
+    closure = g_closure_new_simple(sizeof(PyGClosure), NULL);
+    g_closure_add_invalidate_notifier(closure, NULL, pyg_closure_invalidate);
+    g_closure_set_marshal(closure, pyg_closure_marshal);
+    Py_INCREF(callback);
+    ((PyGClosure *)closure)->callback = callback;
+    if (extra_args && extra_args != Py_None) {
+       Py_INCREF(extra_args);
+       if (!PyTuple_Check(extra_args)) {
+           PyObject *tmp = PyTuple_New(1);
+           PyTuple_SetItem(tmp, 0, extra_args);
+           extra_args = tmp;
+       }
+       ((PyGClosure *)closure)->extra_args = extra_args;
+    }
+    if (swap_data) {
+       Py_INCREF(swap_data);
+       ((PyGClosure *)closure)->swap_data = swap_data;
+       closure->derivative_flag = TRUE;
+    }
+    return closure;
+}
+
+/**
+ * pyg_closure_set_exception_handler:
+ * @closure: a closure created with pyg_closure_new()
+ * @handler: the handler to call when an exception occurs or NULL for none
+ *
+ * Sets the handler to call when an exception occurs during closure invocation.
+ * The handler is responsible for providing a proper return value to the
+ * closure invocation. If @handler is %NULL, the default handler will be used.
+ * The default handler prints the exception to stderr and doesn't touch the
+ * closure's return value.
+ */
+void
+pyg_closure_set_exception_handler(GClosure *closure,
+                                 PyClosureExceptionHandler handler)
+{
+    PyGClosure *pygclosure;
+
+    g_return_if_fail(closure != NULL);
+
+    pygclosure = (PyGClosure *)closure;
+    pygclosure->exception_handler = handler;
+}
+/* -------------- PySignalClassClosure ----------------- */
+/* a closure used for the `class closure' of a signal.  As this gets
+ * all the info from the first argument to the closure and the
+ * invocation hint, we can have a single closure that handles all
+ * class closure cases.  We call a method by the name of the signal
+ * with "do_" prepended.
+ *
+ *  We also remove the first argument from the * param list, as it is
+ *  the instance object, which is passed * implicitly to the method
+ *  object. */
+
+static void
+pyg_signal_class_closure_marshal(GClosure *closure,
+                                GValue *return_value,
+                                guint n_param_values,
+                                const GValue *param_values,
+                                gpointer invocation_hint,
+                                gpointer marshal_data)
+{
+    PyGILState_STATE state;
+    GObject *object;
+    PyObject *object_wrapper;
+    GSignalInvocationHint *hint = (GSignalInvocationHint *)invocation_hint;
+    gchar *method_name, *tmp;
+    PyObject *method;
+    PyObject *params, *ret;
+    Py_ssize_t py_len;
+    guint i, len;
+
+    state = PyGILState_Ensure();
+
+    g_return_if_fail(invocation_hint != NULL);
+    /* get the object passed as the first argument to the closure */
+    object = g_value_get_object(&param_values[0]);
+    g_return_if_fail(object != NULL && G_IS_OBJECT(object));
+
+    /* get the wrapper for this object */
+    object_wrapper = pygobject_new(object);
+    g_return_if_fail(object_wrapper != NULL);
+
+    /* construct method name for this class closure */
+    method_name = g_strconcat("do_", g_signal_name(hint->signal_id), NULL);
+
+    /* convert dashes to underscores.  For some reason, g_signal_name
+     * seems to convert all the underscores in the signal name to
+       dashes??? */
+    for (tmp = method_name; *tmp != '\0'; tmp++)
+       if (*tmp == '-') *tmp = '_';
+
+    method = PyObject_GetAttrString(object_wrapper, method_name);
+    g_free(method_name);
+
+    if (!method) {
+       PyErr_Clear();
+       Py_DECREF(object_wrapper);
+       PyGILState_Release(state);
+       return;
+    }
+    Py_DECREF(object_wrapper);
+
+    /* construct Python tuple for the parameter values; don't copy boxed values
+       initially because we'll check after the call to see if a copy is needed. */
+    params = PyTuple_New(n_param_values - 1);
+    for (i = 1; i < n_param_values; i++) {
+       PyObject *item = pyg_value_as_pyobject(&param_values[i], FALSE);
+
+       /* error condition */
+       if (!item) {
+           Py_DECREF(params);
+           PyGILState_Release(state);
+           return;
+       }
+       PyTuple_SetItem(params, i - 1, item);
+    }
+
+    ret = PyObject_CallObject(method, params);
+
+    /* Copy boxed values if others ref them, this needs to be done regardless of
+       exception status. */
+    py_len = PyTuple_Size(params);
+    len = (guint)py_len;
+    for (i = 0; i < len; i++) {
+       PyObject *item = PyTuple_GetItem(params, i);
+       if (item != NULL && PyObject_TypeCheck(item, &PyGBoxed_Type)
+           && Py_REFCNT (item) != 1) {
+           PyGBoxed* boxed_item = (PyGBoxed*)item;
+           if (!boxed_item->free_on_dealloc) {
+               gpointer boxed_ptr = pyg_boxed_get_ptr (boxed_item);
+               pyg_boxed_set_ptr (boxed_item, g_boxed_copy (boxed_item->gtype, boxed_ptr));
+               boxed_item->free_on_dealloc = TRUE;
+           }
+       }
+    }
+
+    if (ret == NULL) {
+       PyErr_Print();
+       Py_DECREF(method);
+       Py_DECREF(params);
+       PyGILState_Release(state);
+       return;
+    }
+    Py_DECREF(method);
+    Py_DECREF(params);
+    if (G_IS_VALUE(return_value))
+       pyg_value_from_pyobject(return_value, ret);
+    Py_DECREF(ret);
+    PyGILState_Release(state);
+}
+
+/**
+ * pyg_signal_class_closure_get:
+ *
+ * Returns the GClosure used for the class closure of signals.  When
+ * called, it will invoke the method do_signalname (for the signal
+ * "signalname").
+ *
+ * Returns: the closure.
+ */
+GClosure *
+pyg_signal_class_closure_get(void)
+{
+    static GClosure *closure;
+
+    if (closure == NULL) {
+       closure = g_closure_new_simple(sizeof(GClosure), NULL);
+       g_closure_set_marshal(closure, pyg_signal_class_closure_marshal);
+
+       g_closure_ref(closure);
+       g_closure_sink(closure);
+    }
+    return closure;
+}
+
+/* ----- __doc__ descriptor for GObject and GInterface ----- */
+
+static void
+object_doc_dealloc(PyObject *self)
+{
+    PyObject_FREE(self);
+}
+
+/* append information about signals of a particular gtype */
+static void
+add_signal_docs(GType gtype, GString *string)
+{
+    GTypeClass *class = NULL;
+    guint *signal_ids, n_ids = 0, i;
+
+    if (G_TYPE_IS_CLASSED(gtype))
+       class = g_type_class_ref(gtype);
+    signal_ids = g_signal_list_ids(gtype, &n_ids);
+
+    if (n_ids > 0) {
+       g_string_append_printf(string, "Signals from %s:\n",
+                              g_type_name(gtype));
+
+       for (i = 0; i < n_ids; i++) {
+           GSignalQuery query;
+           guint j;
+
+           g_signal_query(signal_ids[i], &query);
+
+           g_string_append(string, "  ");
+           g_string_append(string, query.signal_name);
+           g_string_append(string, " (");
+           for (j = 0; j < query.n_params; j++) {
+               g_string_append(string, g_type_name(query.param_types[j]));
+               if (j != query.n_params - 1)
+                   g_string_append(string, ", ");
+           }
+           g_string_append(string, ")");
+           if (query.return_type && query.return_type != G_TYPE_NONE) {
+               g_string_append(string, " -> ");
+               g_string_append(string, g_type_name(query.return_type));
+           }
+           g_string_append(string, "\n");
+       }
+       g_free(signal_ids);
+       g_string_append(string, "\n");
+    }
+    if (class)
+       g_type_class_unref(class);
+}
+
+static void
+add_property_docs(GType gtype, GString *string)
+{
+    GObjectClass *class;
+    GParamSpec **props;
+    guint n_props = 0, i;
+    gboolean has_prop = FALSE;
+    G_CONST_RETURN gchar *blurb=NULL;
+
+    class = g_type_class_ref(gtype);
+    props = g_object_class_list_properties(class, &n_props);
+
+    for (i = 0; i < n_props; i++) {
+       if (props[i]->owner_type != gtype)
+           continue; /* these are from a parent type */
+
+       /* print out the heading first */
+       if (!has_prop) {
+           g_string_append_printf(string, "Properties from %s:\n",
+                                  g_type_name(gtype));
+           has_prop = TRUE;
+       }
+       g_string_append_printf(string, "  %s -> %s: %s\n",
+                              g_param_spec_get_name(props[i]),
+                              g_type_name(props[i]->value_type),
+                              g_param_spec_get_nick(props[i]));
+
+       /* g_string_append_printf crashes on win32 if the third
+          argument is NULL. */
+       blurb=g_param_spec_get_blurb(props[i]);
+       if (blurb)
+           g_string_append_printf(string, "    %s\n",blurb);
+    }
+    g_free(props);
+    if (has_prop)
+       g_string_append(string, "\n");
+    g_type_class_unref(class);
+}
+
+static PyObject *
+object_doc_descr_get(PyObject *self, PyObject *obj, PyObject *type)
+{
+    GType gtype = 0;
+    GString *string;
+    PyObject *pystring;
+
+    if (obj && pygobject_check(obj, &PyGObject_Type)) {
+       gtype = G_OBJECT_TYPE(pygobject_get(obj));
+       if (!gtype)
+           PyErr_SetString(PyExc_RuntimeError, "could not get object type");
+    } else {
+       gtype = pyg_type_from_object(type);
+    }
+    if (!gtype)
+       return NULL;
+
+    string = g_string_new_len(NULL, 512);
+
+    if (g_type_is_a(gtype, G_TYPE_INTERFACE))
+       g_string_append_printf(string, "Interface %s\n\n", g_type_name(gtype));
+    else if (g_type_is_a(gtype, G_TYPE_OBJECT))
+       g_string_append_printf(string, "Object %s\n\n", g_type_name(gtype));
+    else
+       g_string_append_printf(string, "%s\n\n", g_type_name(gtype));
+
+    if (((PyTypeObject *) type)->tp_doc)
+        g_string_append_printf(string, "%s\n\n", ((PyTypeObject *) type)->tp_doc);
+
+    if (g_type_is_a(gtype, G_TYPE_OBJECT)) {
+       GType parent = G_TYPE_OBJECT;
+        GArray *parents = g_array_new(FALSE, FALSE, sizeof(GType));
+        int iparent;
+
+        while (parent) {
+            g_array_append_val(parents, parent);
+            parent = g_type_next_base(gtype, parent);
+        }
+
+        for (iparent = parents->len - 1; iparent >= 0; --iparent) {
+           GType *interfaces;
+           guint n_interfaces, i;
+
+            parent = g_array_index(parents, GType, iparent);
+           add_signal_docs(parent, string);
+           add_property_docs(parent, string);
+
+           /* add docs for implemented interfaces */
+           interfaces = g_type_interfaces(parent, &n_interfaces);
+           for (i = 0; i < n_interfaces; i++)
+               add_signal_docs(interfaces[i], string);
+           g_free(interfaces);
+       }
+        g_array_free(parents, TRUE);
+    }
+
+    pystring = PYGLIB_PyUnicode_FromStringAndSize(string->str, string->len);
+    g_string_free(string, TRUE);
+    return pystring;
+}
+
+PYGLIB_DEFINE_TYPE("gobject.GObject.__doc__", PyGObjectDoc_Type, PyObject);
+
+/**
+ * pyg_object_descr_doc_get:
+ *
+ * Returns an object intended to be the __doc__ attribute of GObject
+ * wrappers.  When read in the context of the object it will return
+ * some documentation about the signals and properties of the object.
+ *
+ * Returns: the descriptor.
+ */
+PyObject *
+pyg_object_descr_doc_get(void)
+{
+    static PyObject *doc_descr = NULL;
+
+    if (!doc_descr) {
+       Py_TYPE(&PyGObjectDoc_Type) = &PyType_Type;
+       if (PyType_Ready(&PyGObjectDoc_Type))
+           return NULL;
+
+       doc_descr = PyObject_NEW(PyObject, &PyGObjectDoc_Type);
+       if (doc_descr == NULL)
+           return NULL;
+    }
+    return doc_descr;
+}
+
+
+/**
+ * pyg_pyobj_to_unichar_conv:
+ *
+ * Converts PyObject value to a unichar and write result to memory
+ * pointed to by ptr.  Follows the calling convention of a ParseArgs
+ * converter (O& format specifier) so it may be used to convert function
+ * arguments.
+ *
+ * Returns: 1 if the conversion succeeds and 0 otherwise.  If the conversion
+ *          did not succeesd, a Python exception is raised
+ */
+int pyg_pyobj_to_unichar_conv(PyObject* py_obj, void* ptr)
+{
+    if (!pygi_gunichar_from_py (py_obj, ptr))
+        return 0;
+    return 1;
+}
+
+gboolean
+pyg_gtype_is_custom(GType gtype)
+{
+    return g_type_get_qdata (gtype, pygobject_custom_key) != NULL;
+}
+
+static PyObject *
+strv_from_gvalue(const GValue *value)
+{
+    gchar **argv;
+    PyObject  *py_argv;
+    gsize i;
+
+    argv = (gchar **) g_value_get_boxed (value);
+    py_argv = PyList_New (0);
+
+    for (i = 0; argv && argv[i]; i++) {
+        int res;
+        PyObject *item = pygi_utf8_to_py (argv[i]);
+        if (item == NULL) {
+            Py_DECREF (py_argv);
+            return NULL;
+        }
+        res = PyList_Append (py_argv, item);
+        Py_DECREF (item);
+        if (res == -1) {
+            Py_DECREF (py_argv);
+            return NULL;
+        }
+    }
+
+    return py_argv;
+}
+
+static int
+strv_to_gvalue(GValue *value, PyObject *obj)
+{
+    Py_ssize_t argc, i;
+    gchar **argv;
+
+    if (!(PyTuple_Check (obj) || PyList_Check (obj)))
+        return -1;
+
+    argc = PySequence_Length (obj);
+    argv = g_new (gchar *, argc + 1);
+    for (i = 0; i < argc; ++i) {
+        PyObject* item = PySequence_Fast_GET_ITEM (obj, i);
+        if (!pygi_utf8_from_py (item, &(argv[i])))
+            goto error;
+    }
+
+    argv[i] = NULL;
+    g_value_take_boxed (value, argv);
+    return 0;
+
+error:
+    for (i = i - 1; i >= 0; i--) {
+        g_free (argv[i]);
+    }
+    g_free (argv);
+    return -1;
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_type_register_types(PyObject *d)
+{
+    PyGTypeWrapper_Type.tp_dealloc = (destructor)pyg_type_wrapper_dealloc;
+    PyGTypeWrapper_Type.tp_richcompare = pyg_type_wrapper_richcompare;
+    PyGTypeWrapper_Type.tp_repr = (reprfunc)pyg_type_wrapper_repr;
+    PyGTypeWrapper_Type.tp_hash = (hashfunc)pyg_type_wrapper_hash;
+    PyGTypeWrapper_Type.tp_flags = Py_TPFLAGS_DEFAULT;
+    PyGTypeWrapper_Type.tp_methods = _PyGTypeWrapper_methods;
+    PyGTypeWrapper_Type.tp_getset = _PyGTypeWrapper_getsets;
+    PyGTypeWrapper_Type.tp_init = (initproc)pyg_type_wrapper_init;
+    PYGLIB_REGISTER_TYPE(d, PyGTypeWrapper_Type, "GType");
+
+    /* This type lazily registered in pyg_object_descr_doc_get */
+    PyGObjectDoc_Type.tp_dealloc = (destructor)object_doc_dealloc;
+    PyGObjectDoc_Type.tp_flags = Py_TPFLAGS_DEFAULT;
+    PyGObjectDoc_Type.tp_descr_get = (descrgetfunc)object_doc_descr_get;
+
+    pyg_register_gtype_custom (G_TYPE_STRV,
+                               strv_from_gvalue,
+                               strv_to_gvalue);
+
+    return 0;
+}
diff --git a/gi/pygi-type.h b/gi/pygi-type.h
new file mode 100644 (file)
index 0000000..94ddc85
--- /dev/null
@@ -0,0 +1,75 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *   pyginterface.c: wrapper for the gobject library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_TYPE_H__ 
+#define __PYGI_TYPE_H__
+
+#include <Python.h>
+#include <glib-object.h>
+#include <girepository.h>
+#include "pygobject-internal.h"
+
+#define PYGOBJECT_REGISTER_GTYPE(d, type, name, gtype)      \
+  {                                                         \
+    PyObject *o;                                           \
+    PYGLIB_REGISTER_TYPE(d, type, name);                    \
+    PyDict_SetItemString(type.tp_dict, "__gtype__",         \
+                        o=pyg_type_wrapper_new(gtype));    \
+    Py_DECREF(o);                                           \
+}
+
+extern PyTypeObject PyGTypeWrapper_Type;
+
+typedef PyObject *(* fromvaluefunc)(const GValue *value);
+typedef int (*tovaluefunc)(GValue *value, PyObject *obj);
+
+typedef struct {
+    fromvaluefunc fromvalue;
+    tovaluefunc tovalue;
+} PyGTypeMarshal;
+
+PyGTypeMarshal *pyg_type_lookup(GType type);
+
+gboolean pyg_gtype_is_custom (GType gtype);
+
+void pyg_register_gtype_custom(GType gtype,
+                               fromvaluefunc from_func,
+                               tovaluefunc to_func);
+
+int pygi_type_register_types(PyObject *d);
+
+PyObject *pyg_object_descr_doc_get(void);
+PyObject *pyg_type_wrapper_new (GType type);
+GType     pyg_type_from_object_strict (PyObject *obj, gboolean strict);
+GType     pyg_type_from_object (PyObject *obj);
+
+int pyg_pyobj_to_unichar_conv (PyObject* py_obj, void* ptr);
+
+GClosure *pyg_closure_new(PyObject *callback, PyObject *extra_args, PyObject *swap_data);
+GClosure *pyg_signal_class_closure_get(void);
+void      pyg_closure_set_exception_handler(GClosure *closure,
+                                            PyClosureExceptionHandler handler);
+
+PyObject *pygi_type_import_by_g_type (GType g_type);
+PyObject *pygi_type_import_by_name (const char *namespace_, const char *name);
+PyObject *pygi_type_import_by_gi_info (GIBaseInfo *info);
+PyObject *pygi_type_get_from_g_type (GType g_type);
+
+#endif /* __PYGI_TYPE_H__ */
diff --git a/gi/pygi-util.c b/gi/pygi-util.c
new file mode 100644 (file)
index 0000000..c02a53c
--- /dev/null
@@ -0,0 +1,175 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pygi-util.h"
+
+/**
+ * Like PyErr_Format, but supports the format syntax of
+ * PyUnicode_FromFormat also under Python 2.
+ * Note: Python 2 doesn't support %lld and %llo there.
+ */
+PyObject*
+pygi_pyerr_format (PyObject *exception, const char *format, ...)
+{
+    PyObject *text;
+    va_list argp;
+    va_start(argp, format);
+    text = PyUnicode_FromFormatV (format, argp);
+    va_end(argp);
+
+    if (text != NULL) {
+#if PY_MAJOR_VERSION < 3
+        PyObject *str;
+        str = PyUnicode_AsUTF8String (text);
+        Py_DECREF (text);
+        if (str) {
+            PyErr_SetObject (exception, str);
+            Py_DECREF (str);
+        }
+#else
+        PyErr_SetObject (exception, text);
+        Py_DECREF (text);
+#endif
+    }
+
+    return NULL;
+}
+
+gboolean
+pygi_guint_from_pyssize (Py_ssize_t pyval, guint *result)
+{
+    if (pyval < 0) {
+        PyErr_SetString (PyExc_ValueError, "< 0");
+        return FALSE;
+    } else if (G_MAXUINT < PY_SSIZE_T_MAX && pyval > (Py_ssize_t)G_MAXUINT) {
+        PyErr_SetString (PyExc_ValueError, "too large");
+        return FALSE;
+    }
+    *result = (guint)pyval;
+    return TRUE;
+}
+
+/* Better alternative to PyImport_ImportModule which tries to import from
+ * sys.modules first */
+PyObject *
+pygi_import_module (const char *name)
+{
+#if PY_VERSION_HEX < 0x03000000 && !defined(PYPY_VERSION)
+    /* see PyImport_ImportModuleNoBlock
+     * https://github.com/python/cpython/blob/2.7/Python/import.c#L2166-L2206 */
+    PyObject *result = PyImport_ImportModuleNoBlock(name);
+    if (result)
+        return result;
+
+    PyErr_Clear();
+#endif
+    return PyImport_ImportModule(name);
+}
+
+PyObject *
+pyg_integer_richcompare(PyObject *v, PyObject *w, int op)
+{
+    PyObject *result;
+    gboolean t;
+
+    switch (op) {
+    case Py_EQ: t = PYGLIB_PyLong_AS_LONG(v) == PYGLIB_PyLong_AS_LONG(w); break;
+    case Py_NE: t = PYGLIB_PyLong_AS_LONG(v) != PYGLIB_PyLong_AS_LONG(w); break;
+    case Py_LE: t = PYGLIB_PyLong_AS_LONG(v) <= PYGLIB_PyLong_AS_LONG(w); break;
+    case Py_GE: t = PYGLIB_PyLong_AS_LONG(v) >= PYGLIB_PyLong_AS_LONG(w); break;
+    case Py_LT: t = PYGLIB_PyLong_AS_LONG(v) <  PYGLIB_PyLong_AS_LONG(w); break;
+    case Py_GT: t = PYGLIB_PyLong_AS_LONG(v) >  PYGLIB_PyLong_AS_LONG(w); break;
+    default: g_assert_not_reached();
+    }
+
+    result = t ? Py_True : Py_False;
+    Py_INCREF(result);
+    return result;
+}
+
+PyObject*
+pyg_ptr_richcompare(void* a, void *b, int op)
+{
+    PyObject *res;
+
+    switch (op) {
+      case Py_EQ:
+        res = (a == b) ? Py_True : Py_False;
+        break;
+      case Py_NE:
+        res = (a != b) ? Py_True : Py_False;
+        break;
+      case Py_LT:
+        res = (a < b) ? Py_True : Py_False;
+        break;
+      case Py_LE:
+        res = (a <= b) ? Py_True : Py_False;
+        break;
+      case Py_GT:
+        res = (a > b) ? Py_True : Py_False;
+        break;
+      case Py_GE:
+        res = (a >= b) ? Py_True : Py_False;
+        break;
+      default:
+        res = Py_NotImplemented;
+        break;
+    }
+
+    Py_INCREF(res);
+    return res;
+}
+
+/**
+ * pyg_constant_strip_prefix:
+ * @name: the constant name.
+ * @strip_prefix: the prefix to strip.
+ *
+ * Advances the pointer @name by strlen(@strip_prefix) characters.  If
+ * the resulting name does not start with a letter or underscore, the
+ * @name pointer will be rewound.  This is to ensure that the
+ * resulting name is a valid identifier.  Hence the returned string is
+ * a pointer into the string @name.
+ *
+ * Returns: the stripped constant name.
+ */
+const gchar *
+pyg_constant_strip_prefix(const gchar *name, const gchar *strip_prefix)
+{
+    size_t prefix_len, i;
+
+    prefix_len = strlen(strip_prefix);
+
+    /* Check so name starts with strip_prefix, if it doesn't:
+     * return the rest of the part which doesn't match
+     */
+    for (i = 0; i < prefix_len; i++) {
+       if (name[i] != strip_prefix[i] && name[i] != '_') {
+           return &name[i];
+       }
+    }
+
+    /* strip off prefix from value name, while keeping it a valid
+     * identifier */
+    for (i = prefix_len + 1; i > 0; i--) {
+       if (g_ascii_isalpha(name[i - 1]) || name[i - 1] == '_') {
+           return &name[i - 1];
+       }
+    }
+    return name;
+}
diff --git a/gi/pygi-util.h b/gi/pygi-util.h
new file mode 100644 (file)
index 0000000..2f9847e
--- /dev/null
@@ -0,0 +1,62 @@
+#ifndef __PYGI_UTIL_H__
+#define __PYGI_UTIL_H__
+
+#include <Python.h>
+#include <glib.h>
+#include "pygobject-internal.h"
+#include "pygi-python-compat.h"
+
+G_BEGIN_DECLS
+
+PyObject * pyg_integer_richcompare(PyObject *v, PyObject *w, int op);
+PyObject * pyg_ptr_richcompare(void* a, void *b, int op);
+const gchar * pyg_constant_strip_prefix(const gchar *name, const gchar *strip_prefix);
+PyObject * pygi_import_module (const char *name);
+PyObject * pygi_pyerr_format (PyObject *exception, const char *format, ...);
+
+gboolean pygi_guint_from_pyssize (Py_ssize_t pyval, guint *result);
+
+#if PY_VERSION_HEX >= 0x03000000
+
+#define _PyGI_ERROR_PREFIX(format, ...) G_STMT_START { \
+    PyObject *py_error_prefix; \
+    py_error_prefix = PyUnicode_FromFormat(format, ## __VA_ARGS__); \
+    if (py_error_prefix != NULL) { \
+        PyObject *py_error_type, *py_error_value, *py_error_traceback; \
+        PyErr_Fetch(&py_error_type, &py_error_value, &py_error_traceback); \
+        if (PyUnicode_Check(py_error_value)) { \
+            PyObject *new; \
+            new = PyUnicode_Concat(py_error_prefix, py_error_value); \
+            Py_DECREF(py_error_value); \
+            if (new != NULL) { \
+                py_error_value = new; \
+            } \
+        } \
+        PyErr_Restore(py_error_type, py_error_value, py_error_traceback); \
+        Py_DECREF(py_error_prefix); \
+    } \
+} G_STMT_END
+
+#else
+
+#define _PyGI_ERROR_PREFIX(format, ...) G_STMT_START { \
+    PyObject *py_error_prefix; \
+    py_error_prefix = PyString_FromFormat(format, ## __VA_ARGS__); \
+    if (py_error_prefix != NULL) { \
+        PyObject *py_error_type, *py_error_value, *py_error_traceback; \
+        PyErr_Fetch(&py_error_type, &py_error_value, &py_error_traceback); \
+        if (PyString_Check(py_error_value)) { \
+            PyString_ConcatAndDel(&py_error_prefix, py_error_value); \
+            if (py_error_prefix != NULL) { \
+                py_error_value = py_error_prefix; \
+            } \
+        } \
+        PyErr_Restore(py_error_type, py_error_value, py_error_traceback); \
+    } \
+} G_STMT_END
+
+#endif
+
+G_END_DECLS
+
+#endif /* __PYGI_UTIL_H__ */
diff --git a/gi/pygi-value.c b/gi/pygi-value.c
new file mode 100644 (file)
index 0000000..9366934
--- /dev/null
@@ -0,0 +1,909 @@
+
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include "pygi-value.h"
+#include "pygi-struct.h"
+#include "pygi-python-compat.h"
+#include "pygi-basictype.h"
+#include "pygobject-object.h"
+#include "pygi-type.h"
+#include "pygenum.h"
+#include "pygpointer.h"
+#include "pygboxed.h"
+#include "pygflags.h"
+#include "pygparamspec.h"
+
+
+GIArgument
+_pygi_argument_from_g_value(const GValue *value,
+                            GITypeInfo *type_info)
+{
+    GIArgument arg = { 0, };
+
+    GITypeTag type_tag = g_type_info_get_tag (type_info);
+
+    /* For the long handling: long can be equivalent to
+       int32 or int64, depending on the architecture, but
+       gi doesn't tell us (and same for ulong)
+    */
+    switch (type_tag) {
+        case GI_TYPE_TAG_BOOLEAN:
+            arg.v_boolean = g_value_get_boolean (value);
+            break;
+        case GI_TYPE_TAG_INT8:
+            arg.v_int8 = g_value_get_schar (value);
+            break;
+        case GI_TYPE_TAG_INT16:
+        case GI_TYPE_TAG_INT32:
+           if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_LONG))
+               arg.v_int32 = (gint32)g_value_get_long (value);
+           else
+               arg.v_int32 = (gint32)g_value_get_int (value);
+            break;
+        case GI_TYPE_TAG_INT64:
+           if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_LONG))
+               arg.v_int64 = g_value_get_long (value);
+           else
+               arg.v_int64 = g_value_get_int64 (value);
+            break;
+        case GI_TYPE_TAG_UINT8:
+            arg.v_uint8 = g_value_get_uchar (value);
+            break;
+        case GI_TYPE_TAG_UINT16:
+        case GI_TYPE_TAG_UINT32:
+           if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_ULONG))
+               arg.v_uint32 = (guint32)g_value_get_ulong (value);
+           else
+               arg.v_uint32 = (guint32)g_value_get_uint (value);
+            break;
+        case GI_TYPE_TAG_UINT64:
+           if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_ULONG))
+               arg.v_uint64 = g_value_get_ulong (value);
+           else
+               arg.v_uint64 = g_value_get_uint64 (value);
+            break;
+        case GI_TYPE_TAG_UNICHAR:
+            arg.v_uint32 = g_value_get_schar (value);
+            break;
+        case GI_TYPE_TAG_FLOAT:
+            arg.v_float = g_value_get_float (value);
+            break;
+        case GI_TYPE_TAG_DOUBLE:
+            arg.v_double = g_value_get_double (value);
+            break;
+        case GI_TYPE_TAG_GTYPE:
+            arg.v_size = g_value_get_gtype (value);
+            break;
+        case GI_TYPE_TAG_UTF8:
+        case GI_TYPE_TAG_FILENAME:
+            /* Callers are responsible for ensuring the GValue stays alive
+             * long enough for the string to be copied. */
+            arg.v_string = (char *)g_value_get_string (value);
+            break;
+        case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
+        case GI_TYPE_TAG_ARRAY:
+        case GI_TYPE_TAG_GHASH:
+            if (G_VALUE_HOLDS_BOXED (value))
+                arg.v_pointer = g_value_get_boxed (value);
+            else
+                /* e. g. GSettings::change-event */
+                arg.v_pointer = g_value_get_pointer (value);
+            break;
+        case GI_TYPE_TAG_INTERFACE:
+        {
+            GIBaseInfo *info;
+            GIInfoType info_type;
+
+            info = g_type_info_get_interface (type_info);
+            info_type = g_base_info_get_type (info);
+
+            g_base_info_unref (info);
+
+            switch (info_type) {
+                case GI_INFO_TYPE_FLAGS:
+                    arg.v_uint = g_value_get_flags (value);
+                    break;
+                case GI_INFO_TYPE_ENUM:
+                    arg.v_int = g_value_get_enum (value);
+                    break;
+                case GI_INFO_TYPE_INTERFACE:
+                case GI_INFO_TYPE_OBJECT:
+                    if (G_VALUE_HOLDS_PARAM (value))
+                      arg.v_pointer = g_value_get_param (value);
+                    else
+                      arg.v_pointer = g_value_get_object (value);
+                    break;
+                case GI_INFO_TYPE_BOXED:
+                case GI_INFO_TYPE_STRUCT:
+                case GI_INFO_TYPE_UNION:
+                    if (G_VALUE_HOLDS (value, G_TYPE_BOXED)) {
+                        arg.v_pointer = g_value_get_boxed (value);
+                    } else if (G_VALUE_HOLDS (value, G_TYPE_VARIANT)) {
+                        arg.v_pointer = g_value_get_variant (value);
+                    } else if (G_VALUE_HOLDS (value, G_TYPE_POINTER)) {
+                        arg.v_pointer = g_value_get_pointer (value);
+                    } else {
+                        PyErr_Format (PyExc_NotImplementedError,
+                                      "Converting GValue's of type '%s' is not implemented.",
+                                      g_type_name (G_VALUE_TYPE (value)));
+                    }
+                    break;
+                default:
+                    PyErr_Format (PyExc_NotImplementedError,
+                                  "Converting GValue's of type '%s' is not implemented.",
+                                  g_info_type_to_string (info_type));
+                    break;
+            }
+            break;
+        }
+        case GI_TYPE_TAG_ERROR:
+            arg.v_pointer = g_value_get_boxed (value);
+            break;
+        case GI_TYPE_TAG_VOID:
+            arg.v_pointer = g_value_get_pointer (value);
+            break;
+        default:
+            break;
+    }
+
+    return arg;
+}
+
+
+/* Ignore g_value_array deprecations. Although they are deprecated,
+ * we still need to support the marshaling of them in PyGObject.
+ */
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
+static int
+pyg_value_array_from_pyobject(GValue *value,
+                              PyObject *obj,
+                              const GParamSpecValueArray *pspec)
+{
+    Py_ssize_t seq_len;
+    GValueArray *value_array;
+    guint len, i;
+
+    seq_len = PySequence_Length(obj);
+    if (seq_len == -1) {
+        PyErr_Clear();
+        return -1;
+    }
+    len = (guint)seq_len;
+
+    if (pspec && pspec->fixed_n_elements > 0 && len != pspec->fixed_n_elements)
+        return -1;
+
+    value_array = g_value_array_new(len);
+
+    for (i = 0; i < len; ++i) {
+        PyObject *item = PySequence_GetItem(obj, i);
+        GType type;
+
+        if (! item) {
+            PyErr_Clear();
+            g_value_array_free(value_array);
+            return -1;
+        }
+
+        if (pspec && pspec->element_spec)
+            type = G_PARAM_SPEC_VALUE_TYPE(pspec->element_spec);
+        else if (item == Py_None)
+            type = G_TYPE_POINTER; /* store None as NULL */
+        else {
+            type = pyg_type_from_object((PyObject*)Py_TYPE(item));
+            if (! type) {
+                PyErr_Clear();
+                g_value_array_free(value_array);
+                Py_DECREF(item);
+                return -1;
+            }
+        }
+
+        if (type == G_TYPE_VALUE) {
+            const GValue * item_value = pyg_boxed_get(item, GValue);
+            g_value_array_append(value_array, item_value);
+        } else {
+            GValue item_value = { 0, };
+            int status;
+
+            g_value_init(&item_value, type);
+            status = (pspec && pspec->element_spec)
+                ? pyg_param_gvalue_from_pyobject(&item_value, item, pspec->element_spec)
+                : pyg_value_from_pyobject(&item_value, item);
+            Py_DECREF(item);
+
+            if (status == -1) {
+                g_value_array_free(value_array);
+                g_value_unset(&item_value);
+                return -1;
+            }
+            g_value_array_append(value_array, &item_value);
+            g_value_unset(&item_value);
+        }
+    }
+
+    g_value_take_boxed(value, value_array);
+    return 0;
+}
+
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+static int
+pyg_array_from_pyobject(GValue *value,
+                        PyObject *obj)
+{
+    Py_ssize_t len, i;
+    GArray *array;
+
+    len = PySequence_Length(obj);
+    if (len == -1) {
+        PyErr_Clear();
+        return -1;
+    }
+
+    array = g_array_new(FALSE, TRUE, sizeof(GValue));
+
+    for (i = 0; i < len; ++i) {
+        PyObject *item = PySequence_GetItem(obj, i);
+        GType type;
+        GValue item_value = { 0, };
+        int status;
+
+        if (! item) {
+            PyErr_Clear();
+            g_array_free(array, FALSE);
+            return -1;
+        }
+
+        if (item == Py_None)
+            type = G_TYPE_POINTER; /* store None as NULL */
+        else {
+            type = pyg_type_from_object((PyObject*)Py_TYPE(item));
+            if (! type) {
+                PyErr_Clear();
+                g_array_free(array, FALSE);
+                Py_DECREF(item);
+                return -1;
+            }
+        }
+
+        g_value_init(&item_value, type);
+        status = pyg_value_from_pyobject(&item_value, item);
+        Py_DECREF(item);
+
+        if (status == -1) {
+            g_array_free(array, FALSE);
+            g_value_unset(&item_value);
+            return -1;
+        }
+
+        g_array_append_val(array, item_value);
+    }
+
+    g_value_take_boxed(value, array);
+    return 0;
+}
+
+/**
+ * pyg_value_from_pyobject_with_error:
+ * @value: the GValue object to store the converted value in.
+ * @obj: the Python object to convert.
+ *
+ * This function converts a Python object and stores the result in a
+ * GValue.  The GValue must be initialised in advance with
+ * g_value_init().  If the Python object can't be converted to the
+ * type of the GValue, then an error is returned.
+ *
+ * Returns: 0 on success, -1 on error.
+ */
+int
+pyg_value_from_pyobject_with_error(GValue *value, PyObject *obj)
+{
+    GType value_type = G_VALUE_TYPE(value);
+
+    switch (G_TYPE_FUNDAMENTAL(value_type)) {
+    case G_TYPE_INTERFACE:
+        /* we only handle interface types that have a GObject prereq */
+        if (g_type_is_a(value_type, G_TYPE_OBJECT)) {
+            if (obj == Py_None)
+                g_value_set_object(value, NULL);
+            else {
+                if (!PyObject_TypeCheck(obj, &PyGObject_Type)) {
+                    PyErr_SetString(PyExc_TypeError, "GObject is required");
+                    return -1;
+                }
+                if (!G_TYPE_CHECK_INSTANCE_TYPE(pygobject_get(obj),
+                        value_type)) {
+                    PyErr_SetString(PyExc_TypeError, "Invalid GObject type for assignment");
+                    return -1;
+                }
+                g_value_set_object(value, pygobject_get(obj));
+            }
+        } else {
+            PyErr_SetString(PyExc_TypeError, "Unsupported conversion");
+            return -1;
+        }
+        break;
+    case G_TYPE_CHAR:
+    {
+        gint8 temp;
+        if (pygi_gschar_from_py (obj, &temp)) {
+            g_value_set_schar (value, temp);
+            return 0;
+        } else
+            return -1;
+    }
+    case G_TYPE_UCHAR:
+    {
+        guchar temp;
+        if (pygi_guchar_from_py (obj, &temp)) {
+            g_value_set_uchar (value, temp);
+            return 0;
+        } else
+            return -1;
+    }
+    case G_TYPE_BOOLEAN:
+    {
+        gboolean temp;
+        if (pygi_gboolean_from_py (obj, &temp)) {
+            g_value_set_boolean (value, temp);
+            return 0;
+        } else
+            return -1;
+    }
+    case G_TYPE_INT:
+    {
+        gint temp;
+        if (pygi_gint_from_py (obj, &temp)) {
+            g_value_set_int (value, temp);
+            return 0;
+        } else
+            return -1;
+    }
+    case G_TYPE_UINT:
+    {
+        guint temp;
+        if (pygi_guint_from_py (obj, &temp)) {
+            g_value_set_uint (value, temp);
+            return 0;
+        } else
+            return -1;
+    }
+    case G_TYPE_LONG:
+    {
+        glong temp;
+        if (pygi_glong_from_py (obj, &temp)) {
+            g_value_set_long (value, temp);
+            return 0;
+        } else
+            return -1;
+    }
+    case G_TYPE_ULONG:
+    {
+        gulong temp;
+        if (pygi_gulong_from_py (obj, &temp)) {
+            g_value_set_ulong (value, temp);
+            return 0;
+        } else
+            return -1;
+    }
+    case G_TYPE_INT64:
+    {
+        gint64 temp;
+        if (pygi_gint64_from_py (obj, &temp)) {
+            g_value_set_int64 (value, temp);
+            return 0;
+        } else
+            return -1;
+    }
+    case G_TYPE_UINT64:
+    {
+        guint64 temp;
+        if (pygi_guint64_from_py (obj, &temp)) {
+            g_value_set_uint64 (value, temp);
+            return 0;
+        } else
+            return -1;
+    }
+    case G_TYPE_ENUM:
+    {
+        gint val = 0;
+        if (pyg_enum_get_value(G_VALUE_TYPE(value), obj, &val) < 0) {
+            return -1;
+        }
+        g_value_set_enum(value, val);
+    }
+    break;
+    case G_TYPE_FLAGS:
+    {
+        guint val = 0;
+        if (pyg_flags_get_value(G_VALUE_TYPE(value), obj, &val) < 0) {
+            return -1;
+        }
+        g_value_set_flags(value, val);
+        return 0;
+    }
+    break;
+    case G_TYPE_FLOAT:
+    {
+        gfloat temp;
+        if (pygi_gfloat_from_py (obj, &temp)) {
+            g_value_set_float (value, temp);
+            return 0;
+        } else
+            return -1;
+    }
+    case G_TYPE_DOUBLE:
+    {
+        gdouble temp;
+        if (pygi_gdouble_from_py (obj, &temp)) {
+            g_value_set_double (value, temp);
+            return 0;
+        } else
+            return -1;
+    }
+    case G_TYPE_STRING:
+    {
+        gchar *temp;
+        if (pygi_utf8_from_py (obj, &temp)) {
+            g_value_take_string (value, temp);
+            return 0;
+        } else {
+            /* also allows setting anything implementing __str__ */
+            PyObject* str;
+            PyErr_Clear ();
+            str = PyObject_Str (obj);
+            if (str == NULL)
+                return -1;
+            if (pygi_utf8_from_py (str, &temp)) {
+                Py_DECREF (str);
+                g_value_take_string (value, temp);
+                return 0;
+            }
+            Py_DECREF (str);
+            return -1;
+        }
+    }
+    case G_TYPE_POINTER:
+        if (obj == Py_None)
+            g_value_set_pointer(value, NULL);
+        else if (PyObject_TypeCheck(obj, &PyGPointer_Type) &&
+                G_VALUE_HOLDS(value, ((PyGPointer *)obj)->gtype))
+            g_value_set_pointer(value, pyg_pointer_get(obj, gpointer));
+        else if (PyCapsule_CheckExact (obj))
+            g_value_set_pointer(value, PyCapsule_GetPointer (obj, NULL));
+        else if (G_VALUE_HOLDS_GTYPE (value))
+            g_value_set_gtype (value, pyg_type_from_object (obj));
+        else {
+            PyErr_SetString(PyExc_TypeError, "Expected pointer");
+            return -1;
+        }
+        break;
+    case G_TYPE_BOXED: {
+        PyGTypeMarshal *bm;
+        gboolean holds_value_array;
+
+        G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+        holds_value_array = G_VALUE_HOLDS(value, G_TYPE_VALUE_ARRAY);
+        G_GNUC_END_IGNORE_DEPRECATIONS
+
+        if (obj == Py_None)
+            g_value_set_boxed(value, NULL);
+        else if (G_VALUE_HOLDS(value, PY_TYPE_OBJECT))
+            g_value_set_boxed(value, obj);
+        else if (PyObject_TypeCheck(obj, &PyGBoxed_Type) &&
+                G_VALUE_HOLDS(value, ((PyGBoxed *)obj)->gtype))
+            g_value_set_boxed(value, pyg_boxed_get(obj, gpointer));
+        else if (G_VALUE_HOLDS(value, G_TYPE_VALUE)) {
+            GType type;
+            GValue *n_value;
+
+            type = pyg_type_from_object((PyObject*)Py_TYPE(obj));
+            if (G_UNLIKELY (! type)) {
+                return -1;
+            }
+            n_value = g_new0 (GValue, 1);
+            g_value_init (n_value, type);
+            g_value_take_boxed (value, n_value);
+            return pyg_value_from_pyobject_with_error (n_value, obj);
+        }
+        else if (PySequence_Check(obj) && holds_value_array)
+            return pyg_value_array_from_pyobject(value, obj, NULL);
+
+        else if (PySequence_Check(obj) &&
+                G_VALUE_HOLDS(value, G_TYPE_ARRAY))
+            return pyg_array_from_pyobject(value, obj);
+        else if (PYGLIB_PyUnicode_Check(obj) &&
+                G_VALUE_HOLDS(value, G_TYPE_GSTRING)) {
+            GString *string;
+            char *buffer;
+            Py_ssize_t len;
+            if (PYGLIB_PyUnicode_AsStringAndSize(obj, &buffer, &len))
+                return -1;
+            string = g_string_new_len(buffer, len);
+            g_value_set_boxed(value, string);
+            g_string_free (string, TRUE);
+            break;
+        }
+        else if ((bm = pyg_type_lookup(G_VALUE_TYPE(value))) != NULL)
+            return bm->tovalue(value, obj);
+        else if (PyCapsule_CheckExact (obj))
+            g_value_set_boxed(value, PyCapsule_GetPointer (obj, NULL));
+        else {
+            PyErr_SetString(PyExc_TypeError, "Expected Boxed");
+            return -1;
+        }
+        break;
+    }
+    case G_TYPE_PARAM:
+        /* we need to support both the wrapped _gi.GParamSpec and the GI
+         * GObject.ParamSpec */
+        if (G_IS_PARAM_SPEC (pygobject_get (obj)))
+            g_value_set_param(value, G_PARAM_SPEC (pygobject_get (obj)));
+        else if (pyg_param_spec_check (obj))
+            g_value_set_param(value, PyCapsule_GetPointer (obj, NULL));
+        else {
+            PyErr_SetString(PyExc_TypeError, "Expected ParamSpec");
+            return -1;
+        }
+        break;
+    case G_TYPE_OBJECT:
+        if (obj == Py_None) {
+            g_value_set_object(value, NULL);
+        } else if (PyObject_TypeCheck(obj, &PyGObject_Type) &&
+                G_TYPE_CHECK_INSTANCE_TYPE(pygobject_get(obj),
+                        G_VALUE_TYPE(value))) {
+            g_value_set_object(value, pygobject_get(obj));
+        } else {
+            PyErr_SetString(PyExc_TypeError, "Expected GObject");
+            return -1;
+        }
+        break;
+    case G_TYPE_VARIANT:
+    {
+        if (obj == Py_None)
+            g_value_set_variant(value, NULL);
+        else if (pyg_type_from_object_strict(obj, FALSE) == G_TYPE_VARIANT)
+            g_value_set_variant(value, pyg_boxed_get(obj, GVariant));
+        else {
+            PyErr_SetString(PyExc_TypeError, "Expected Variant");
+            return -1;
+        }
+        break;
+    }
+    default:
+    {
+        PyGTypeMarshal *bm;
+        if ((bm = pyg_type_lookup(G_VALUE_TYPE(value))) != NULL) {
+            return bm->tovalue(value, obj);
+        } else {
+            PyErr_SetString(PyExc_TypeError, "Unknown value type");
+            return -1;
+        }
+        break;
+    }
+    }
+
+    /* If an error occurred, unset the GValue but don't clear the Python error. */
+    if (PyErr_Occurred()) {
+        g_value_unset(value);
+        return -1;
+    }
+
+    return 0;
+}
+
+/**
+ * pyg_value_from_pyobject:
+ * @value: the GValue object to store the converted value in.
+ * @obj: the Python object to convert.
+ *
+ * Same basic function as pyg_value_from_pyobject_with_error but clears
+ * any Python errors before returning.
+ *
+ * Returns: 0 on success, -1 on error.
+ */
+int
+pyg_value_from_pyobject(GValue *value, PyObject *obj)
+{
+    int res = pyg_value_from_pyobject_with_error (value, obj);
+
+    if (PyErr_Occurred()) {
+        PyErr_Clear();
+        return -1;
+    }
+    return res;
+}
+
+/**
+ * pygi_value_to_py_basic_type:
+ * @value: the GValue object.
+ * @handled: (out): TRUE if the return value is defined
+ *
+ * This function creates/returns a Python wrapper object that
+ * represents the GValue passed as an argument limited to supporting basic types
+ * like ints, bools, and strings.
+ *
+ * Returns: a PyObject representing the value.
+ */
+PyObject *
+pygi_value_to_py_basic_type (const GValue *value, GType fundamental, gboolean *handled)
+{
+    *handled = TRUE;
+    switch (fundamental) {
+        case G_TYPE_CHAR:
+            return PYGLIB_PyLong_FromLong (g_value_get_schar (value));
+        case G_TYPE_UCHAR:
+            return PYGLIB_PyLong_FromLong (g_value_get_uchar (value));
+        case G_TYPE_BOOLEAN:
+            return pygi_gboolean_to_py (g_value_get_boolean (value));
+        case G_TYPE_INT:
+            return pygi_gint_to_py (g_value_get_int (value));
+        case G_TYPE_UINT:
+            return pygi_guint_to_py (g_value_get_uint (value));
+        case G_TYPE_LONG:
+            return pygi_glong_to_py (g_value_get_long(value));
+        case G_TYPE_ULONG:
+            return pygi_gulong_to_py (g_value_get_ulong (value));
+        case G_TYPE_INT64:
+            return pygi_gint64_to_py (g_value_get_int64 (value));
+        case G_TYPE_UINT64:
+            return pygi_guint64_to_py (g_value_get_uint64 (value));
+        case G_TYPE_ENUM:
+            return pyg_enum_from_gtype (G_VALUE_TYPE (value),
+                                        g_value_get_enum (value));
+        case G_TYPE_FLAGS:
+            return pyg_flags_from_gtype (G_VALUE_TYPE (value),
+                                         g_value_get_flags (value));
+        case G_TYPE_FLOAT:
+            return pygi_gfloat_to_py (g_value_get_float (value));
+        case G_TYPE_DOUBLE:
+            return pygi_gdouble_to_py (g_value_get_double (value));
+        case G_TYPE_STRING:
+            return pygi_utf8_to_py (g_value_get_string (value));
+        default:
+            *handled = FALSE;
+            return NULL;
+    }
+}
+
+/**
+ * value_to_py_structured_type:
+ * @value: the GValue object.
+ * @copy_boxed: true if boxed values should be copied.
+ *
+ * This function creates/returns a Python wrapper object that
+ * represents the GValue passed as an argument.
+ *
+ * Returns: a PyObject representing the value or NULL and sets an error;
+ */
+static PyObject *
+value_to_py_structured_type (const GValue *value, GType fundamental, gboolean copy_boxed)
+{
+    const gchar *type_name;
+
+    switch (fundamental) {
+    case G_TYPE_INTERFACE:
+        if (g_type_is_a(G_VALUE_TYPE(value), G_TYPE_OBJECT))
+            return pygobject_new(g_value_get_object(value));
+        else
+            break;
+
+    case G_TYPE_POINTER:
+        if (G_VALUE_HOLDS_GTYPE (value))
+            return pyg_type_wrapper_new (g_value_get_gtype (value));
+        else
+            return pyg_pointer_new(G_VALUE_TYPE(value),
+                    g_value_get_pointer(value));
+    case G_TYPE_BOXED: {
+        PyGTypeMarshal *bm;
+        gboolean holds_value_array;
+
+        G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+        holds_value_array = G_VALUE_HOLDS(value, G_TYPE_VALUE_ARRAY);
+        G_GNUC_END_IGNORE_DEPRECATIONS
+
+        if (G_VALUE_HOLDS(value, PY_TYPE_OBJECT)) {
+            PyObject *ret = (PyObject *)g_value_dup_boxed(value);
+            if (ret == NULL) {
+                Py_INCREF(Py_None);
+                return Py_None;
+            }
+            return ret;
+        } else if (G_VALUE_HOLDS(value, G_TYPE_VALUE)) {
+            GValue *n_value = g_value_get_boxed (value);
+            return pyg_value_as_pyobject(n_value, copy_boxed);
+        } else if (holds_value_array) {
+            GValueArray *array = (GValueArray *) g_value_get_boxed(value);
+            Py_ssize_t n_values = array ? array->n_values : 0;
+            PyObject *ret = PyList_New(n_values);
+            int i;
+            for (i = 0; i < n_values; ++i)
+                PyList_SET_ITEM(ret, i, pyg_value_as_pyobject
+                        (array->values + i, copy_boxed));
+            return ret;
+        } else if (G_VALUE_HOLDS(value, G_TYPE_GSTRING)) {
+            GString *string = (GString *) g_value_get_boxed(value);
+            PyObject *ret = PYGLIB_PyUnicode_FromStringAndSize(string->str, string->len);
+            return ret;
+        }
+        bm = pyg_type_lookup(G_VALUE_TYPE(value));
+        if (bm) {
+            return bm->fromvalue(value);
+        } else {
+            if (copy_boxed)
+                return pygi_gboxed_new(G_VALUE_TYPE(value),
+                        g_value_get_boxed(value), TRUE, TRUE);
+            else
+                return pygi_gboxed_new(G_VALUE_TYPE(value),
+                        g_value_get_boxed(value),FALSE,FALSE);
+        }
+    }
+    case G_TYPE_PARAM:
+        return pyg_param_spec_new(g_value_get_param(value));
+    case G_TYPE_OBJECT:
+        return pygobject_new(g_value_get_object(value));
+    case G_TYPE_VARIANT:
+    {
+        GVariant *v = g_value_get_variant(value);
+        if (v == NULL) {
+            Py_INCREF(Py_None);
+            return Py_None;
+        }
+        return pygi_struct_new_from_g_type (G_TYPE_VARIANT, g_variant_ref(v), FALSE);
+    }
+    default:
+    {
+        PyGTypeMarshal *bm;
+        if ((bm = pyg_type_lookup(G_VALUE_TYPE(value))))
+            return bm->fromvalue(value);
+        break;
+    }
+    }
+
+    type_name = g_type_name (G_VALUE_TYPE (value));
+    if (type_name == NULL) {
+        type_name = "(null)";
+    }
+    PyErr_Format (PyExc_TypeError, "unknown type %s", type_name);
+    return NULL;
+}
+
+
+/**
+ * pyg_value_as_pyobject:
+ * @value: the GValue object.
+ * @copy_boxed: true if boxed values should be copied.
+ *
+ * This function creates/returns a Python wrapper object that
+ * represents the GValue passed as an argument.
+ *
+ * Returns: a PyObject representing the value or %NULL and sets an exception.
+ */
+PyObject *
+pyg_value_as_pyobject (const GValue *value, gboolean copy_boxed)
+{
+    PyObject *pyobj;
+    gboolean handled;
+    GType fundamental = G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (value));
+
+    /* HACK: special case char and uchar to return PyBytes intstead of integers
+     * in the general case. Property access will skip this by calling
+     * pygi_value_to_py_basic_type() directly.
+     * See: https://bugzilla.gnome.org/show_bug.cgi?id=733893 */
+    if (fundamental == G_TYPE_CHAR) {
+        gint8 val = g_value_get_schar(value);
+        return PYGLIB_PyUnicode_FromStringAndSize ((char *)&val, 1);
+    } else if (fundamental == G_TYPE_UCHAR) {
+        guint8 val = g_value_get_uchar(value);
+        return PYGLIB_PyBytes_FromStringAndSize ((char *)&val, 1);
+    }
+
+    pyobj = pygi_value_to_py_basic_type (value, fundamental, &handled);
+    if (handled)
+        return pyobj;
+
+    pyobj = value_to_py_structured_type (value, fundamental, copy_boxed);
+    return pyobj;
+}
+
+
+int
+pyg_param_gvalue_from_pyobject(GValue* value,
+                               PyObject* py_obj,
+                              const GParamSpec* pspec)
+{
+    if (G_IS_PARAM_SPEC_UNICHAR(pspec)) {
+       gunichar u;
+
+       if (!pyg_pyobj_to_unichar_conv(py_obj, &u)) {
+           PyErr_Clear();
+           return -1;
+       }
+        g_value_set_uint(value, u);
+       return 0;
+    }
+    else if (G_IS_PARAM_SPEC_VALUE_ARRAY(pspec))
+       return pyg_value_array_from_pyobject(value, py_obj,
+                                            G_PARAM_SPEC_VALUE_ARRAY(pspec));
+    else {
+       return pyg_value_from_pyobject(value, py_obj);
+    }
+}
+
+PyObject*
+pyg_param_gvalue_as_pyobject(const GValue* gvalue,
+                             gboolean copy_boxed,
+                             const GParamSpec* pspec)
+{
+    if (G_IS_PARAM_SPEC_UNICHAR(pspec)) {
+        gunichar u;
+        gchar *encoded;
+        PyObject *retval;
+
+        u = g_value_get_uint (gvalue);
+        encoded = g_ucs4_to_utf8 (&u, 1, NULL, NULL, NULL);
+        if (encoded == NULL) {
+            PyErr_SetString (PyExc_ValueError, "Failed to decode");
+            return NULL;
+        }
+        retval = PyUnicode_FromString (encoded);
+        g_free (encoded);
+        return retval;
+    }
+    else {
+        return pyg_value_as_pyobject(gvalue, copy_boxed);
+    }
+}
+
+PyObject *
+pyg__gvalue_get(PyObject *module, PyObject *pygvalue)
+{
+    if (!pyg_boxed_check (pygvalue, G_TYPE_VALUE)) {
+        PyErr_SetString (PyExc_TypeError, "Expected GValue argument.");
+        return NULL;
+    }
+
+    return pyg_value_as_pyobject (pyg_boxed_get(pygvalue, GValue),
+                                  /*copy_boxed=*/ TRUE);
+}
+
+PyObject *
+pyg__gvalue_set(PyObject *module, PyObject *args)
+{
+    PyObject *pygvalue;
+    PyObject *pyobject;
+
+    if (!PyArg_ParseTuple (args, "OO:_gi._gvalue_set",
+                           &pygvalue, &pyobject))
+        return NULL;
+
+    if (!pyg_boxed_check (pygvalue, G_TYPE_VALUE)) {
+        PyErr_SetString (PyExc_TypeError, "Expected GValue argument.");
+        return NULL;
+    }
+
+    if (pyg_value_from_pyobject_with_error (pyg_boxed_get (pygvalue, GValue),
+                                            pyobject) == -1)
+        return NULL;
+
+    Py_RETURN_NONE;
+}
diff --git a/gi/pygi-value.h b/gi/pygi-value.h
new file mode 100644 (file)
index 0000000..6450112
--- /dev/null
@@ -0,0 +1,51 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGI_VALUE_H__
+#define __PYGI_VALUE_H__
+
+#include <Python.h>
+#include <glib-object.h>
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+GIArgument _pygi_argument_from_g_value(const GValue *value,
+                                       GITypeInfo *type_info);
+
+int       pyg_value_from_pyobject(GValue *value, PyObject *obj);
+int       pyg_value_from_pyobject_with_error(GValue *value, PyObject *obj);
+PyObject *pyg_value_as_pyobject(const GValue *value, gboolean copy_boxed);
+int       pyg_param_gvalue_from_pyobject(GValue* value,
+                                         PyObject* py_obj,
+                                         const GParamSpec* pspec);
+PyObject *pyg_param_gvalue_as_pyobject(const GValue* gvalue,
+                                       gboolean copy_boxed,
+                                       const GParamSpec* pspec);
+PyObject *pyg_strv_from_gvalue(const GValue *value);
+int       pyg_strv_to_gvalue(GValue *value, PyObject *obj);
+
+PyObject *pygi_value_to_py_basic_type      (const GValue *value,
+                                            GType fundamental,
+                                            gboolean *handled);
+
+PyObject *pyg__gvalue_get(PyObject *module, PyObject *pygvalue);
+PyObject *pyg__gvalue_set(PyObject *module, PyObject *args);
+
+G_END_DECLS
+
+#endif /* __PYGI_VALUE_H__ */
diff --git a/gi/pyginterface.c b/gi/pyginterface.c
new file mode 100644 (file)
index 0000000..a80b40f
--- /dev/null
@@ -0,0 +1,126 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *   pyginterface.c: wrapper for the gobject library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <Python.h>
+#include <glib-object.h>
+
+#include "pygi-python-compat.h"
+#include "pyginterface.h"
+#include "pygi-type.h"
+
+GQuark pyginterface_type_key;
+GQuark pyginterface_info_key;
+
+PYGLIB_DEFINE_TYPE("gobject.GInterface", PyGInterface_Type, PyObject)
+
+static int
+pyg_interface_init(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    gchar buf[512];
+
+    if (!PyArg_ParseTuple(args, ":GInterface.__init__"))
+       return -1;
+
+    g_snprintf(buf, sizeof(buf), "%s can not be constructed",
+              Py_TYPE(self)->tp_name);
+    PyErr_SetString(PyExc_NotImplementedError, buf);
+    return -1;
+}
+
+static void
+pyg_interface_free(PyObject *op)
+{
+    PyObject_FREE(op);
+}
+
+/**
+ * pyg_register_interface:
+ * @dict: a module dictionary.
+ * @class_name: the class name for the wrapper class.
+ * @gtype: the GType of the interface.
+ * @type: the wrapper class for the interface.
+ *
+ * Registers a Python class as the wrapper for a GInterface.  As a
+ * convenience it will also place a reference to the wrapper class in
+ * the provided module dictionary.
+ */
+void
+pyg_register_interface(PyObject *dict, const gchar *class_name,
+                       GType gtype, PyTypeObject *type)
+{
+    PyObject *o;
+
+    Py_TYPE(type) = &PyType_Type;
+    g_assert (Py_TYPE (&PyGInterface_Type) != NULL);
+    type->tp_base = &PyGInterface_Type;
+
+    if (PyType_Ready(type) < 0) {
+        g_warning("could not ready `%s'", type->tp_name);
+        return;
+    }
+
+    if (gtype) {
+        o = pyg_type_wrapper_new(gtype);
+        PyDict_SetItemString(type->tp_dict, "__gtype__", o);
+        Py_DECREF(o);
+    }
+
+    g_type_set_qdata(gtype, pyginterface_type_key, type);
+    
+    PyDict_SetItemString(dict, (char *)class_name, (PyObject *)type);
+    
+}
+
+void
+pyg_register_interface_info(GType gtype, const GInterfaceInfo *info)
+{
+    g_type_set_qdata(gtype, pyginterface_info_key, (gpointer) info);
+}
+
+const GInterfaceInfo *
+pyg_lookup_interface_info(GType gtype)
+{
+    return g_type_get_qdata(gtype, pyginterface_info_key);
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_interface_register_types(PyObject *d)
+{
+  pyginterface_type_key = g_quark_from_static_string("PyGInterface::type");
+  pyginterface_info_key = g_quark_from_static_string("PyGInterface::info");
+
+  PyGInterface_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+  PyGInterface_Type.tp_init = (initproc)pyg_interface_init;
+  PyGInterface_Type.tp_free = (freefunc)pyg_interface_free;
+
+  PYGOBJECT_REGISTER_GTYPE(d, PyGInterface_Type, "GInterface", G_TYPE_INTERFACE)
+
+  PyDict_SetItemString(PyGInterface_Type.tp_dict, "__doc__",
+                      pyg_object_descr_doc_get());
+  PyDict_SetItemString(PyGInterface_Type.tp_dict, "__gdoc__",
+                      pyg_object_descr_doc_get());
+
+  return 0;
+}
diff --git a/gi/pyginterface.h b/gi/pyginterface.h
new file mode 100644 (file)
index 0000000..d5819ce
--- /dev/null
@@ -0,0 +1,38 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *   pyginterface.c: wrapper for the gobject library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGOBJECT_INTERFACE_H__ 
+#define __PYGOBJECT_INTERFACE_H__
+
+extern GQuark pyginterface_type_key;
+extern GQuark pyginterface_info_key;
+
+extern PyTypeObject PyGInterface_Type;
+
+void pyg_register_interface(PyObject *dict,
+                           const gchar *class_name,
+                           GType gtype,
+                           PyTypeObject *type);
+const GInterfaceInfo * pyg_lookup_interface_info(GType gtype);
+void pyg_register_interface_info(GType gtype, const
+                                GInterfaceInfo *info);
+int pygi_interface_register_types(PyObject *d);
+
+#endif /* __PYGOBJECT_INTERFACE_H__ */
diff --git a/gi/pygobject-internal.h b/gi/pygobject-internal.h
new file mode 100644 (file)
index 0000000..2cd82c5
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef _PYGOBJECT_INTERNAL_H_
+#define _PYGOBJECT_INTERNAL_H_
+
+#define _INSIDE_PYGOBJECT_
+#include "pygobject.h"
+
+#endif /*_PYGOBJECT_INTERNAL_H_*/
diff --git a/gi/pygobject-object.c b/gi/pygobject-object.c
new file mode 100644 (file)
index 0000000..dbf46e1
--- /dev/null
@@ -0,0 +1,2536 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *
+ *   pygobject.c: wrapper for the GObject type.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "pygobject-object.h"
+#include "pyginterface.h"
+#include "pygparamspec.h"
+#include "pygi-type.h"
+#include "pygboxed.h"
+#include "gimodule.h"
+
+#include "pygi-util.h"
+#include "pygi-value.h"
+#include "pygi-type.h"
+#include "pygi-property.h"
+#include "pygi-signal-closure.h"
+#include "pygi-basictype.h"
+
+extern PyObject *PyGIDeprecationWarning;
+
+static void pygobject_dealloc(PyGObject *self);
+static int  pygobject_traverse(PyGObject *self, visitproc visit, void *arg);
+static PyObject * pyg_type_get_bases(GType gtype);
+static inline int pygobject_clear(PyGObject *self);
+static PyObject * pygobject_weak_ref_new(GObject *obj, PyObject *callback, PyObject *user_data);
+static void pygobject_inherit_slots(PyTypeObject *type, PyObject *bases,
+                                   gboolean check_for_present);
+static void pygobject_find_slot_for(PyTypeObject *type, PyObject *bases, int slot_offset,
+                                   gboolean check_for_present);
+GType PY_TYPE_OBJECT = 0;
+GQuark pygobject_custom_key;
+GQuark pygobject_class_key;
+GQuark pygobject_class_init_key;
+GQuark pygobject_wrapper_key;
+GQuark pygobject_has_updated_constructor_key;
+GQuark pygobject_instance_data_key;
+
+/* PyPy doesn't support tp_dictoffset, so we have to work around it */
+#ifndef PYPY_VERSION
+#define PYGI_OBJECT_USE_CUSTOM_DICT
+#endif
+
+GClosure *
+gclosure_from_pyfunc(PyGObject *object, PyObject *func)
+{
+    GSList *l;
+    PyGObjectData *inst_data;
+    inst_data = pyg_object_peek_inst_data(object->obj);
+    if (inst_data) {
+        for (l = inst_data->closures; l; l = l->next) {
+            PyGClosure *pyclosure = l->data;
+            int res = PyObject_RichCompareBool(pyclosure->callback, func, Py_EQ);
+            if (res == -1) {
+                PyErr_Clear(); /* Is there anything else to do? */
+            } else if (res) {
+                return (GClosure*)pyclosure;
+            }
+        }
+    }
+    return NULL;
+}
+
+/* Copied from glib. gobject uses hyphens in property names, but in Python
+ * we can only represent hyphens as underscores. Convert underscores to
+ * hyphens for glib compatibility. */
+static void
+canonicalize_key (gchar *key)
+{
+    gchar *p;
+
+    for (p = key; *p != 0; p++)
+    {
+        gchar c = *p;
+
+        if (c != '-' &&
+            (c < '0' || c > '9') &&
+            (c < 'A' || c > 'Z') &&
+            (c < 'a' || c > 'z'))
+                *p = '-';
+    }
+}
+
+/* -------------- class <-> wrapper manipulation --------------- */
+
+static void
+pygobject_data_free(PyGObjectData *data)
+{
+    /* This function may be called after the python interpreter has already
+     * been shut down. If this happens, we cannot do any python calls, so just
+     * free the memory. */
+    PyGILState_STATE state = 0;
+    PyThreadState *_save = NULL;
+    gboolean state_saved;
+    GSList *closures, *tmp;
+
+    state_saved = Py_IsInitialized();
+    if (state_saved) {
+       state = PyGILState_Ensure();
+       Py_DECREF(data->type);
+       /* We cannot use Py_BEGIN_ALLOW_THREADS here because this is inside
+        * a branch. */
+       Py_UNBLOCK_THREADS; /* Modifies _save */
+    }
+
+    tmp = closures = data->closures;
+#ifndef NDEBUG
+    data->closures = NULL;
+    data->type = NULL;
+#endif
+    while (tmp) {
+       GClosure *closure = tmp->data;
+          /* we get next item first, because the current link gets
+           * invalidated by pygobject_unwatch_closure */
+       tmp = tmp->next;
+       g_closure_invalidate(closure);
+    }
+    if (data->closures != NULL)
+       g_warning("invalidated all closures, but data->closures != NULL !");
+
+    g_free(data);
+
+    if (state_saved && Py_IsInitialized ()) {
+       Py_BLOCK_THREADS; /* Restores _save */
+       PyGILState_Release(state);
+    }
+}
+
+static inline PyGObjectData *
+pygobject_data_new(void)
+{
+    PyGObjectData *data;
+    data = g_new0(PyGObjectData, 1);
+    return data;
+}
+
+static inline PyGObjectData *
+pygobject_get_inst_data(PyGObject *self)
+{
+    PyGObjectData *inst_data;
+
+    if (G_UNLIKELY(!self->obj))
+        return NULL;
+    inst_data = g_object_get_qdata(self->obj, pygobject_instance_data_key);
+    if (inst_data == NULL)
+    {
+        inst_data = pygobject_data_new();
+
+        inst_data->type = Py_TYPE(self);
+        Py_INCREF((PyObject *) inst_data->type);
+
+        g_object_set_qdata_full(self->obj, pygobject_instance_data_key,
+                                inst_data, (GDestroyNotify) pygobject_data_free);
+    }
+    return inst_data;
+}
+
+
+PyTypeObject *PyGObject_MetaType = NULL;
+
+/**
+ * pygobject_sink:
+ * @obj: a GObject
+ * 
+ * As Python handles reference counting for us, the "floating
+ * reference" code in GTK is not all that useful.  In fact, it can
+ * cause leaks.  This function should be called to remove the floating
+ * references on objects on construction.
+ **/
+void
+pygobject_sink(GObject *obj)
+{
+    /* The default behaviour for GInitiallyUnowned subclasses is to call ref_sink().
+     * - if the object is new and owned by someone else, its ref has been sunk and
+     *   we need to keep the one from that someone and add our own "fresh ref"
+     * - if the object is not and owned by nobody, its ref is floating and we need
+     *   to transform it into a regular ref.
+     */
+    if (G_IS_INITIALLY_UNOWNED(obj)) {
+        g_object_ref_sink(obj);
+    }
+}
+
+typedef struct {
+    PyObject_HEAD
+    GParamSpec **props;
+    guint n_props;
+    guint index;
+} PyGPropsIter;
+
+PYGLIB_DEFINE_TYPE("gi._gi.GPropsIter", PyGPropsIter_Type, PyGPropsIter);
+
+static void
+pyg_props_iter_dealloc(PyGPropsIter *self)
+{
+    g_free(self->props);
+    PyObject_Del((PyObject*) self);
+}
+
+static PyObject*
+pygobject_props_iter_next(PyGPropsIter *iter)
+{
+    if (iter->index < iter->n_props)
+        return pyg_param_spec_new(iter->props[iter->index++]);
+    else {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+}
+
+typedef struct {
+    PyObject_HEAD
+    /* a reference to the object containing the properties */
+    PyGObject *pygobject;
+    GType      gtype;
+} PyGProps;
+
+static void
+PyGProps_dealloc(PyGProps* self)
+{
+    PyGObject *tmp;
+
+    PyObject_GC_UnTrack((PyObject*)self);
+
+    tmp = self->pygobject;
+    self->pygobject = NULL;
+    Py_XDECREF(tmp);
+
+    PyObject_GC_Del((PyObject*)self);
+}
+
+static PyObject*
+build_parameter_list(GObjectClass *class)
+{
+    GParamSpec **props;
+    guint n_props = 0, i;
+    PyObject *prop_str;
+    PyObject *props_list;
+
+    props = g_object_class_list_properties(class, &n_props);
+    props_list = PyList_New(n_props);
+    for (i = 0; i < n_props; i++) {
+       char *name;
+       name = g_strdup(g_param_spec_get_name(props[i]));
+       /* hyphens cannot belong in identifiers */
+       g_strdelimit(name, "-", '_');
+       prop_str = PYGLIB_PyUnicode_FromString(name);
+       
+       PyList_SetItem(props_list, i, prop_str);
+       g_free(name);
+    }
+
+    if (props)
+        g_free(props);
+    
+    return props_list;
+}
+
+static PyObject*
+PyGProps_getattro(PyGProps *self, PyObject *attr)
+{
+    char *attr_name, *property_name;
+    GObjectClass *class;
+    GParamSpec *pspec;
+
+    attr_name = PYGLIB_PyUnicode_AsString(attr);
+    if (!attr_name) {
+        PyErr_Clear();
+        return PyObject_GenericGetAttr((PyObject *)self, attr);
+    }
+
+    class = g_type_class_ref(self->gtype);
+
+    /* g_object_class_find_property recurses through the class hierarchy,
+     * so the resulting pspec tells us the owner_type that owns the property
+     * we're dealing with. */
+    property_name = g_strdup(attr_name);
+    canonicalize_key(property_name);
+    pspec = g_object_class_find_property(class, property_name);
+    g_free(property_name);
+    g_type_class_unref(class);
+
+    if (!pspec) {
+       return PyObject_GenericGetAttr((PyObject *)self, attr);
+    }
+
+    if (!self->pygobject) {
+        /* If we're doing it without an instance, return a GParamSpec */
+        return pyg_param_spec_new(pspec);
+    }
+
+    return pygi_get_property_value (self->pygobject, pspec);
+}
+
+static gboolean
+set_property_from_pspec(GObject *obj,
+                       GParamSpec *pspec,
+                       PyObject *pvalue)
+{
+    GValue value = { 0, };
+
+    if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) {
+       PyErr_Format(PyExc_TypeError,
+                    "property '%s' can only be set in constructor",
+                    pspec->name);
+       return FALSE;
+    }  
+
+    if (!(pspec->flags & G_PARAM_WRITABLE)) {
+       PyErr_Format(PyExc_TypeError,
+                    "property '%s' is not writable", pspec->name);
+       return FALSE;
+    }  
+
+    g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec));
+    if (pyg_param_gvalue_from_pyobject(&value, pvalue, pspec) < 0) {
+        PyObject *pvalue_str = PyObject_Repr(pvalue);
+       PyErr_Format(PyExc_TypeError,
+                    "could not convert %s to type '%s' when setting property '%s.%s'",
+                    PYGLIB_PyUnicode_AsString(pvalue_str),
+                    g_type_name(G_PARAM_SPEC_VALUE_TYPE(pspec)),
+                    G_OBJECT_TYPE_NAME(obj),
+                    pspec->name);
+       Py_DECREF(pvalue_str);
+       return FALSE;
+    }
+
+    Py_BEGIN_ALLOW_THREADS;
+    g_object_set_property(obj, pspec->name, &value);
+    g_value_unset(&value);
+    Py_END_ALLOW_THREADS;
+
+    return TRUE;
+}
+
+PYGLIB_DEFINE_TYPE("gi._gi.GProps", PyGProps_Type, PyGProps);
+
+static int
+PyGProps_setattro(PyGProps *self, PyObject *attr, PyObject *pvalue)
+{
+    GParamSpec *pspec;
+    char *attr_name, *property_name;
+    GObject *obj;
+    int ret = -1;
+    
+    if (pvalue == NULL) {
+       PyErr_SetString(PyExc_TypeError, "properties cannot be "
+                       "deleted");
+       return -1;
+    }
+
+    attr_name = PYGLIB_PyUnicode_AsString(attr);
+    if (!attr_name) {
+        PyErr_Clear();
+        return PyObject_GenericSetAttr((PyObject *)self, attr, pvalue);
+    }
+
+    if (!self->pygobject) {
+        PyErr_SetString(PyExc_TypeError,
+                       "cannot set GOject properties without an instance");
+        return -1;
+    }
+
+    obj = self->pygobject->obj;
+
+    property_name = g_strdup(attr_name);
+    canonicalize_key(property_name);
+
+    /* g_object_class_find_property recurses through the class hierarchy,
+     * so the resulting pspec tells us the owner_type that owns the property
+     * we're dealing with. */
+    pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(obj),
+                                         property_name);
+    g_free(property_name);
+    if (!pspec) {
+       return PyObject_GenericSetAttr((PyObject *)self, attr, pvalue);
+    }
+    if (!pyg_gtype_is_custom (pspec->owner_type)) {
+        /* This GType is not implemented in Python: see if we can set the
+         * property via gi. */
+        ret = pygi_set_property_value (self->pygobject, pspec, pvalue);
+        if (ret == 0)
+            return 0;
+        else if (ret == -1 && PyErr_Occurred())
+            return -1;
+    }
+
+    /* This GType is implemented in Python, or we failed to set it via gi:
+     * do a straightforward set. */
+    if (!set_property_from_pspec(obj, pspec, pvalue))
+       return -1;
+                                 
+    return 0;
+}
+
+static int
+pygobject_props_traverse(PyGProps *self, visitproc visit, void *arg)
+{
+    if (self->pygobject && visit((PyObject *) self->pygobject, arg) < 0)
+        return -1;
+    return 0;
+}
+
+static PyObject*
+pygobject_props_get_iter(PyGProps *self)
+{
+    PyGPropsIter *iter;
+    GObjectClass *class;
+
+    iter = PyObject_NEW(PyGPropsIter, &PyGPropsIter_Type);
+    class = g_type_class_ref(self->gtype);
+    iter->props = g_object_class_list_properties(class, &iter->n_props);
+    iter->index = 0;
+    g_type_class_unref(class);
+    return (PyObject *) iter;
+}
+
+static PyObject*
+pygobject_props_dir(PyGProps *self)
+{
+    PyObject *ret;
+    GObjectClass *class;
+
+    class = g_type_class_ref (self->gtype);
+    ret = build_parameter_list (class);
+    g_type_class_unref (class);
+
+    return ret;
+}
+
+static PyMethodDef pygobject_props_methods[] = {
+    { "__dir__", (PyCFunction)pygobject_props_dir, METH_NOARGS},
+    { NULL, NULL, 0}
+};
+
+
+static Py_ssize_t
+PyGProps_length(PyGProps *self)
+{
+    GObjectClass *class;
+    GParamSpec **props;
+    guint n_props;
+    
+    class = g_type_class_ref(self->gtype);
+    props = g_object_class_list_properties(class, &n_props);
+    g_type_class_unref(class);
+    g_free(props);
+
+    return (Py_ssize_t)n_props;
+}
+
+static PySequenceMethods _PyGProps_as_sequence = {
+    (lenfunc) PyGProps_length,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0
+};
+
+PYGLIB_DEFINE_TYPE("gi._gi.GPropsDescr", PyGPropsDescr_Type, PyObject);
+
+static PyObject *
+pyg_props_descr_descr_get(PyObject *self, PyObject *obj, PyObject *type)
+{
+    PyGProps *gprops;
+
+    gprops = PyObject_GC_New(PyGProps, &PyGProps_Type);
+    if (obj == NULL || obj == Py_None) {
+        gprops->pygobject = NULL;
+        gprops->gtype = pyg_type_from_object(type);
+    } else {
+        if (!PyObject_IsInstance(obj, (PyObject *) &PyGObject_Type)) {
+            PyErr_SetString(PyExc_TypeError, "cannot use GObject property"
+                            " descriptor on non-GObject instances");
+            return NULL;
+        }
+        Py_INCREF(obj);
+        gprops->pygobject = (PyGObject *) obj;
+        gprops->gtype = pyg_type_from_object(obj);
+    }
+    return (PyObject *) gprops;
+}
+
+/**
+ * pygobject_register_class:
+ * @dict: the module dictionary.  A reference to the type will be stored here.
+ * @type_name: not used ?
+ * @gtype: the GType of the GObject subclass.
+ * @type: the Python type object for this wrapper.
+ * @static_bases: a tuple of Python type objects that are the bases of
+ * this type
+ *
+ * This function is used to register a Python type as the wrapper for
+ * a particular GObject subclass.  It will also insert a reference to
+ * the wrapper class into the module dictionary passed as a reference,
+ * which simplifies initialisation.
+ */
+void
+pygobject_register_class(PyObject *dict, const gchar *type_name,
+                        GType gtype, PyTypeObject *type,
+                        PyObject *static_bases)
+{
+    PyObject *o;
+    const char *class_name, *s;
+    PyObject *runtime_bases;
+    PyObject *bases_list, *bases, *mod_name;
+    int i;
+    
+    class_name = type->tp_name;
+    s = strrchr(class_name, '.');
+    if (s != NULL)
+       class_name = s + 1;
+
+    runtime_bases = pyg_type_get_bases(gtype);
+    if (static_bases) {
+        PyTypeObject *py_parent_type = (PyTypeObject *) PyTuple_GET_ITEM(static_bases, 0);
+        bases_list = PySequence_List(static_bases);
+          /* we start at index 1 because we want to skip the primary
+           * base, otherwise we might get MRO conflict */
+        for (i = 1; i < PyTuple_GET_SIZE(runtime_bases); ++i)
+        {
+            PyObject *base = PyTuple_GET_ITEM(runtime_bases, i);
+            int contains = PySequence_Contains(bases_list, base);
+            if (contains < 0)
+                PyErr_Print();
+            else if (!contains) {
+                if (!PySequence_Contains(py_parent_type->tp_mro, base)) {
+#if 0
+                    g_message("Adding missing base %s to type %s",
+                              ((PyTypeObject *)base)->tp_name, type->tp_name);
+#endif
+                    PyList_Append(bases_list, base);
+                }
+            }
+        }
+        bases = PySequence_Tuple(bases_list);
+        Py_DECREF(bases_list);
+        Py_DECREF(runtime_bases);
+    } else
+        bases = runtime_bases;
+
+    Py_TYPE(type) = PyGObject_MetaType;
+    type->tp_bases = bases;
+    if (G_LIKELY(bases)) {
+        type->tp_base = (PyTypeObject *)PyTuple_GetItem(bases, 0);
+        Py_INCREF(type->tp_base);
+    }
+
+    pygobject_inherit_slots(type, bases, TRUE);
+
+    if (PyType_Ready(type) < 0) {
+       g_warning ("couldn't make the type `%s' ready", type->tp_name);
+       return;
+    }
+
+    /* Set type.__module__ to the name of the module,
+     * otherwise it'll default to 'gobject', see #376099
+     */
+    s = strrchr(type->tp_name, '.');
+    if (s != NULL) {
+       mod_name = PYGLIB_PyUnicode_FromStringAndSize(type->tp_name, (int)(s - type->tp_name));
+       PyDict_SetItemString(type->tp_dict, "__module__", mod_name);
+       Py_DECREF(mod_name);
+    }
+    
+    if (gtype) {
+       o = pyg_type_wrapper_new(gtype);
+       PyDict_SetItemString(type->tp_dict, "__gtype__", o);
+       Py_DECREF(o);
+
+       /* stash a pointer to the python class with the GType */
+       Py_INCREF(type);
+       g_type_set_qdata(gtype, pygobject_class_key, type);
+    }
+
+    /* set up __doc__ descriptor on type */
+    PyDict_SetItemString(type->tp_dict, "__doc__",
+                        pyg_object_descr_doc_get());
+
+    PyDict_SetItemString(dict, (char *)class_name, (PyObject *)type);
+}
+
+static void
+pyg_toggle_notify (gpointer data, GObject *object, gboolean is_last_ref)
+{
+    PyGObject *self;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+
+    /* Avoid thread safety problems by using qdata for wrapper retrieval
+     * instead of the user data argument.
+     * See: https://bugzilla.gnome.org/show_bug.cgi?id=709223
+     */
+    self = (PyGObject *)g_object_get_qdata (object, pygobject_wrapper_key);
+    if (self) {
+        if (is_last_ref)
+            Py_DECREF(self);
+        else
+            Py_INCREF(self);
+    }
+
+    PyGILState_Release(state);
+}
+
+static inline gboolean
+pygobject_toggle_ref_is_required (PyGObject *self)
+{
+#ifdef PYGI_OBJECT_USE_CUSTOM_DICT
+    return self->inst_dict != NULL;
+#else
+    PyObject *dict;
+    gboolean result;
+    dict = PyObject_GetAttrString ((PyObject *)self, "__dict__");
+    if (!dict) {
+        PyErr_Clear ();
+        return FALSE;
+    }
+    result = PyDict_Size (dict) != 0;
+    Py_DECREF (dict);
+    return result;
+#endif
+}
+
+static inline gboolean
+pygobject_toggle_ref_is_active (PyGObject *self)
+{
+    return self->private_flags.flags & PYGOBJECT_USING_TOGGLE_REF;
+}
+
+  /* Called when the inst_dict is first created; switches the 
+     reference counting strategy to start using toggle ref to keep the
+     wrapper alive while the GObject lives.  In contrast, while
+     inst_dict was NULL the python wrapper is allowed to die at
+     will and is recreated on demand. */
+static inline void
+pygobject_toggle_ref_ensure (PyGObject *self)
+{
+    if (pygobject_toggle_ref_is_active (self))
+        return;
+
+    if (!pygobject_toggle_ref_is_required (self))
+        return;
+
+    if (self->obj == NULL)
+        return;
+
+    g_assert(self->obj->ref_count >= 1);
+    self->private_flags.flags |= PYGOBJECT_USING_TOGGLE_REF;
+      /* Note that add_toggle_ref will never immediately call back into 
+         pyg_toggle_notify */
+    Py_INCREF((PyObject *) self);
+    g_object_add_toggle_ref(self->obj, pyg_toggle_notify, NULL);
+    g_object_unref(self->obj);
+}
+
+/* Called when an custom gobject is initalized via g_object_new instead of
+   its constructor.  The next time the wrapper is access via 
+   pygobject_new_full it will sink the floating reference instead of
+   adding a new reference and causing a leak */
+void
+pygobject_ref_float(PyGObject *self)
+{
+    /* should only be floated once */
+    g_assert(!(self->private_flags.flags & PYGOBJECT_IS_FLOATING_REF));
+    
+    self->private_flags.flags |= PYGOBJECT_IS_FLOATING_REF;
+}
+
+/* Called by gobject_new_full, if the floating flag is set remove it, otherwise
+   ref the pyobject */
+void
+pygobject_ref_sink(PyGObject *self)
+{
+    if (self->private_flags.flags & PYGOBJECT_IS_FLOATING_REF)
+        self->private_flags.flags &= ~PYGOBJECT_IS_FLOATING_REF;
+    else
+        Py_INCREF ( (PyObject *) self);
+}
+
+/**
+ * pygobject_register_wrapper:
+ * @self: the wrapper instance
+ *
+ * In the constructor of PyGTK wrappers, this function should be
+ * called after setting the obj member.  It will tie the wrapper
+ * instance to the GObject so that the same wrapper instance will
+ * always be used for this GObject instance.
+ */
+void
+pygobject_register_wrapper(PyObject *self)
+{
+    PyGObject *gself;
+
+    g_return_if_fail(self != NULL);
+    g_return_if_fail(PyObject_TypeCheck(self, &PyGObject_Type));
+
+    gself = (PyGObject *)self;
+
+    g_assert(gself->obj->ref_count >= 1);
+      /* save wrapper pointer so we can access it later */
+    g_object_set_qdata_full(gself->obj, pygobject_wrapper_key, gself, NULL);
+
+    pygobject_toggle_ref_ensure (gself);
+}
+
+static PyObject *
+pyg_type_get_bases(GType gtype)
+{
+    GType *interfaces, parent_type, interface_type;
+    guint n_interfaces;
+    PyTypeObject *py_parent_type, *py_interface_type;
+    PyObject *bases;
+    guint i;
+    
+    if (G_UNLIKELY(gtype == G_TYPE_OBJECT))
+        return NULL;
+
+    /* Lookup the parent type */
+    parent_type = g_type_parent(gtype);
+    py_parent_type = pygobject_lookup_class(parent_type);
+    interfaces = g_type_interfaces(gtype, &n_interfaces);
+    bases = PyTuple_New(n_interfaces + 1);
+    /* We will always put the parent at the first position in bases */
+    Py_INCREF(py_parent_type); /* PyTuple_SetItem steals a reference */
+    PyTuple_SetItem(bases, 0, (PyObject *) py_parent_type);
+
+    /* And traverse interfaces */
+    if (n_interfaces) {
+       for (i = 0; i < n_interfaces; i++) {
+           interface_type = interfaces[i];
+           py_interface_type = pygobject_lookup_class(interface_type);
+            Py_INCREF(py_interface_type); /* PyTuple_SetItem steals a reference */
+           PyTuple_SetItem(bases, i + 1, (PyObject *) py_interface_type);
+       }
+    }
+    g_free(interfaces);
+    return bases;
+}
+
+/**
+ * pygobject_new_with_interfaces
+ * @gtype: the GType of the GObject subclass.
+ *
+ * Creates a new PyTypeObject from the given GType with interfaces attached in
+ * bases.
+ *
+ * Returns: a PyTypeObject for the new type or NULL if it couldn't be created
+ */
+static PyTypeObject *
+pygobject_new_with_interfaces(GType gtype)
+{
+    PyGILState_STATE state;
+    PyObject *o;
+    PyTypeObject *type;
+    PyObject *dict;
+    PyTypeObject *py_parent_type;
+    PyObject *bases;
+
+    state = PyGILState_Ensure();
+
+    bases = pyg_type_get_bases(gtype);
+    py_parent_type = (PyTypeObject *) PyTuple_GetItem(bases, 0);
+
+    dict = PyDict_New();
+    
+    o = pyg_type_wrapper_new(gtype);
+    PyDict_SetItemString(dict, "__gtype__", o);
+    Py_DECREF(o);
+
+    /* set up __doc__ descriptor on type */
+    PyDict_SetItemString(dict, "__doc__", pyg_object_descr_doc_get());
+
+    /* Something special to point out that it's not accessible through
+     * gi.repository */
+    o = PYGLIB_PyUnicode_FromString ("__gi__");
+    PyDict_SetItemString (dict, "__module__", o);
+    Py_DECREF (o);
+
+    type = (PyTypeObject*)PyObject_CallFunction((PyObject *) Py_TYPE(py_parent_type),
+                                                "sNN", g_type_name (gtype), bases, dict);
+
+    if (type == NULL) {
+       PyErr_Print();
+        PyGILState_Release(state);
+       return NULL;
+    }
+
+      /* Workaround python tp_(get|set)attr slot inheritance bug.
+       * Fixes bug #144135. */
+    if (!type->tp_getattr && py_parent_type->tp_getattr) {
+        type->tp_getattro = NULL;
+        type->tp_getattr = py_parent_type->tp_getattr;
+    }
+    if (!type->tp_setattr && py_parent_type->tp_setattr) {
+        type->tp_setattro = NULL;
+        type->tp_setattr = py_parent_type->tp_setattr;
+    }
+      /* override more python stupid hacks behind our back */
+    type->tp_dealloc = py_parent_type->tp_dealloc;
+    type->tp_alloc = py_parent_type->tp_alloc;
+    type->tp_free = py_parent_type->tp_free;
+    type->tp_traverse = py_parent_type->tp_traverse;
+    type->tp_clear = py_parent_type->tp_clear;
+
+    pygobject_inherit_slots(type, bases, FALSE);
+
+    if (PyType_Ready(type) < 0) {
+       g_warning ("couldn't make the type `%s' ready", type->tp_name);
+        PyGILState_Release(state);
+       return NULL;
+    }
+
+    /* stash a pointer to the python class with the GType */
+    Py_INCREF(type);
+    g_type_set_qdata(gtype, pygobject_class_key, type);
+
+    PyGILState_Release(state);
+
+    return type;
+}
+
+/* Pick appropriate value for given slot (at slot_offset inside
+ * PyTypeObject structure).  It must be a pointer, e.g. a pointer to a
+ * function.  We use the following heuristic:
+ *
+ * - Scan all types listed as bases of the type.
+ * - If for exactly one base type slot value is non-NULL and
+ *   different from that of 'object' and 'GObject', set current type
+ *   slot into that value.
+ * - Otherwise (if there is more than one such base type or none at
+ *   all) don't touch it and live with Python default.
+ *
+ * The intention here is to propagate slot from custom wrappers to
+ * wrappers created at runtime when appropriate.  We prefer to be on
+ * the safe side, so if there is potential collision (more than one
+ * custom slot value), we discard custom overrides altogether.
+ *
+ * When registering type with pygobject_register_class(), i.e. a type
+ * that has been manually created (likely with Codegen help),
+ * `check_for_present' should be set to TRUE.  In this case, the
+ * function will never overwrite any non-NULL slots already present in
+ * the type.  If `check_for_present' is FALSE, such non-NULL slots are
+ * though to be set by Python interpreter and so will be overwritten
+ * if heuristic above says so.
+ */
+static void
+pygobject_inherit_slots(PyTypeObject *type, PyObject *bases, gboolean check_for_present)
+{
+    static int slot_offsets[] = { offsetof(PyTypeObject, tp_richcompare),
+#if PY_VERSION_HEX < 0x03000000
+                                  offsetof(PyTypeObject, tp_compare),
+#endif
+                                  offsetof(PyTypeObject, tp_richcompare),
+                                  offsetof(PyTypeObject, tp_hash),
+                                  offsetof(PyTypeObject, tp_iter),
+                                  offsetof(PyTypeObject, tp_repr),
+                                  offsetof(PyTypeObject, tp_str),
+                                  offsetof(PyTypeObject, tp_print) };
+    gsize i;
+
+    /* Happens when registering gobject.GObject itself, at least. */
+    if (!bases)
+       return;
+
+    for (i = 0; i < G_N_ELEMENTS(slot_offsets); ++i)
+       pygobject_find_slot_for(type, bases, slot_offsets[i], check_for_present);
+}
+
+static void
+pygobject_find_slot_for(PyTypeObject *type, PyObject *bases, int slot_offset,
+                       gboolean check_for_present)
+{
+#define TYPE_SLOT(type)  (* (void **)  (void *) (((char *) (type)) + slot_offset))
+
+    void *found_slot = NULL;
+    Py_ssize_t num_bases = PyTuple_Size(bases);
+    Py_ssize_t i;
+
+    if (check_for_present && TYPE_SLOT(type) != NULL) {
+       /* We are requested to check if there is any custom slot value
+        * in this type already and there actually is.  Don't
+        * overwrite it.
+        */
+       return;
+    }
+
+    for (i = 0; i < num_bases; ++i) {
+       PyTypeObject *base_type = (PyTypeObject *) PyTuple_GetItem(bases, i);
+       void *slot = TYPE_SLOT(base_type);
+
+       if (slot == NULL)
+           continue;
+       if (slot == TYPE_SLOT(&PyGObject_Type) ||
+           slot == TYPE_SLOT(&PyBaseObject_Type))
+           continue;
+
+       if (found_slot != NULL && found_slot != slot) {
+           /* We have a conflict: more than one base use different
+            * custom slots.  To be on the safe side, we bail out.
+            */
+           return;
+       }
+
+       found_slot = slot;
+    }
+
+    /* Only perform the final assignment if at least one base has a
+     * custom value.  Otherwise just leave this type's slot untouched.
+     */
+    if (found_slot != NULL)
+       TYPE_SLOT(type) = found_slot;
+
+#undef TYPE_SLOT
+}
+
+/**
+ * pygobject_lookup_class:
+ * @gtype: the GType of the GObject subclass.
+ *
+ * This function looks up the wrapper class used to represent
+ * instances of a GObject represented by @gtype.  If no wrapper class
+ * or interface has been registered for the given GType, then a new
+ * type will be created.
+ *
+ * Does not set an exception when NULL is returned.
+ *
+ * Returns: The wrapper class for the GObject or NULL if the
+ *          GType has no registered type and a new type couldn't be created
+ */
+PyTypeObject *
+pygobject_lookup_class(GType gtype)
+{
+    PyTypeObject *py_type;
+
+    if (gtype == G_TYPE_INTERFACE)
+        return &PyGInterface_Type;
+    
+    py_type = g_type_get_qdata(gtype, pygobject_class_key);
+    if (py_type == NULL) {
+        py_type = g_type_get_qdata(gtype, pyginterface_type_key);
+
+        if (py_type == NULL) {
+            py_type = (PyTypeObject *)pygi_type_import_by_g_type(gtype);
+            PyErr_Clear ();
+        }
+
+        if (py_type == NULL) {
+            py_type = pygobject_new_with_interfaces(gtype);
+            PyErr_Clear ();
+            g_type_set_qdata(gtype, pyginterface_type_key, py_type);
+        }
+    }
+    
+    return py_type;
+}
+
+/**
+ * pygobject_new_full:
+ * @obj: a GObject instance.
+ * @steal: whether to steal a ref from the GObject or add (sink) a new one.
+ * @g_class: the GObjectClass
+ *
+ * This function gets a reference to a wrapper for the given GObject
+ * instance.  If a wrapper has already been created, a new reference
+ * to that wrapper will be returned.  Otherwise, a wrapper instance
+ * will be created.
+ *
+ * Returns: a reference to the wrapper for the GObject.
+ */
+PyObject *
+pygobject_new_full(GObject *obj, gboolean steal, gpointer g_class)
+{
+    PyGObject *self;
+
+    if (obj == NULL) {
+        Py_RETURN_NONE;
+    }
+
+    /* If the GObject already has a PyObject wrapper stashed in its qdata, re-use it.
+     */
+    self = (PyGObject *)g_object_get_qdata(obj, pygobject_wrapper_key);
+    if (self != NULL) {
+        /* Note the use of "pygobject_ref_sink" here only deals with PyObject
+         * wrapper ref counts and has nothing to do with GObject.
+         */
+        pygobject_ref_sink(self);
+
+        /* If steal is true, we also want to decref the incoming GObjects which
+         * already have a Python wrapper because the wrapper is already holding a
+         * strong reference.
+         */
+        if (steal)
+            g_object_unref (obj);
+
+    } else {
+       /* create wrapper */
+        PyGObjectData *inst_data = pyg_object_peek_inst_data(obj);
+       PyTypeObject *tp;
+        if (inst_data)
+            tp = inst_data->type;
+        else {
+            if (g_class)
+                tp = pygobject_lookup_class(G_OBJECT_CLASS_TYPE(g_class));
+            else
+                tp = pygobject_lookup_class(G_OBJECT_TYPE(obj));
+        }
+        g_assert(tp != NULL);
+        
+        /* need to bump type refcount if created with
+           pygobject_new_with_interfaces(). fixes bug #141042 */
+        if (tp->tp_flags & Py_TPFLAGS_HEAPTYPE)
+            Py_INCREF(tp);
+       self = PyObject_GC_New(PyGObject, tp);
+       if (self == NULL)
+           return NULL;
+        self->inst_dict = NULL;
+       self->weakreflist = NULL;
+       self->private_flags.flags = 0;
+       self->obj = obj;
+
+        /* If we are not stealing a ref or the object is floating,
+         * add a regular ref or sink the object. */
+        if (g_object_is_floating (obj))
+            self->private_flags.flags |= PYGOBJECT_GOBJECT_WAS_FLOATING;
+        if (!steal || self->private_flags.flags & PYGOBJECT_GOBJECT_WAS_FLOATING)
+            g_object_ref_sink (obj);
+
+        pygobject_register_wrapper((PyObject *)self);
+       PyObject_GC_Track((PyObject *)self);
+    }
+
+    return (PyObject *)self;
+}
+
+
+PyObject *
+pygobject_new(GObject *obj)
+{
+    return pygobject_new_full(obj,
+                              /*steal=*/FALSE,
+                              NULL);
+}
+
+static void
+pygobject_unwatch_closure(gpointer data, GClosure *closure)
+{
+    PyGObjectData *inst_data = data;
+
+    /* Despite no Python API is called the list inst_data->closures
+     * must be protected by GIL as it is used by GC in
+     * pygobject_traverse */
+    PyGILState_STATE state = PyGILState_Ensure();
+    inst_data->closures = g_slist_remove (inst_data->closures, closure);
+    PyGILState_Release(state);
+}
+
+/**
+ * pygobject_watch_closure:
+ * @self: a GObject wrapper instance
+ * @closure: a GClosure to watch
+ *
+ * Adds a closure to the list of watched closures for the wrapper.
+ * The closure must be one returned by pyg_closure_new().  When the
+ * cycle GC traverses the wrapper instance, it will enumerate the
+ * references to Python objects stored in watched closures.  If the
+ * cycle GC tells the wrapper to clear itself, the watched closures
+ * will be invalidated.
+ */
+void
+pygobject_watch_closure(PyObject *self, GClosure *closure)
+{
+    PyGObject *gself;
+    PyGObjectData *data;
+
+    g_return_if_fail(self != NULL);
+    g_return_if_fail(PyObject_TypeCheck(self, &PyGObject_Type));
+    g_return_if_fail(closure != NULL);
+
+    gself = (PyGObject *)self;
+    data = pygobject_get_inst_data(gself);
+    g_return_if_fail(data != NULL);
+    g_return_if_fail(g_slist_find(data->closures, closure) == NULL);
+    data->closures = g_slist_prepend(data->closures, closure);
+    g_closure_add_invalidate_notifier(closure, data, pygobject_unwatch_closure);
+}
+
+
+/* -------------- PyGObject behaviour ----------------- */
+
+PYGLIB_DEFINE_TYPE("gi._gi.GObject", PyGObject_Type, PyGObject);
+
+static void
+pygobject_dealloc(PyGObject *self)
+{
+    /* Untrack must be done first. This is because followup calls such as
+     * ClearWeakRefs could call into Python and cause new allocations to
+     * happen, which could in turn could trigger the garbage collector,
+     * which would then get confused as it is tracking this half-deallocated
+     * object. */
+    PyObject_GC_UnTrack((PyObject *)self);
+
+    if (self->weakreflist != NULL)
+        PyObject_ClearWeakRefs((PyObject *)self);
+
+      /* this forces inst_data->type to be updated, which could prove
+       * important if a new wrapper has to be created and it is of a
+       * unregistered type */
+    pygobject_get_inst_data(self);
+    pygobject_clear(self);
+    /* the following causes problems with subclassed types */
+    /* Py_TYPE(self)->tp_free((PyObject *)self); */
+    PyObject_GC_Del(self);
+}
+
+static PyObject*
+pygobject_richcompare(PyObject *self, PyObject *other, int op)
+{
+    int isinst;
+
+    isinst = PyObject_IsInstance(self, (PyObject*)&PyGObject_Type);
+    if (isinst == -1)
+        return NULL;
+    if (!isinst) {
+        Py_INCREF(Py_NotImplemented);
+        return Py_NotImplemented;
+    }
+    isinst = PyObject_IsInstance(other, (PyObject*)&PyGObject_Type);
+    if (isinst == -1)
+        return NULL;
+    if (!isinst) {
+        Py_INCREF(Py_NotImplemented);
+        return Py_NotImplemented;
+    }
+
+    return pyg_ptr_richcompare(((PyGObject*)self)->obj,
+                               ((PyGObject*)other)->obj,
+                               op);
+}
+
+static PYGLIB_Py_hash_t
+pygobject_hash(PyGObject *self)
+{
+    return PYGLIB_Py_hash_t_FromVoidPtr (self->obj);
+}
+
+static PyObject *
+pygobject_repr(PyGObject *self)
+{
+    PyObject *module, *repr;
+    gchar *module_str, *namespace;
+
+    module = PyObject_GetAttrString ((PyObject *)self, "__module__");
+    if (module == NULL)
+        return NULL;
+
+    if (!PYGLIB_PyUnicode_Check (module)) {
+        Py_DECREF (module);
+        return NULL;
+    }
+
+    module_str = PYGLIB_PyUnicode_AsString (module);
+    namespace = g_strrstr (module_str, ".");
+    if (namespace == NULL) {
+        namespace = module_str;
+    } else {
+        namespace += 1;
+    }
+
+    repr = PYGLIB_PyUnicode_FromFormat ("<%s.%s object at %p (%s at %p)>",
+                                        namespace, Py_TYPE (self)->tp_name, self,
+                                        self->obj ? G_OBJECT_TYPE_NAME (self->obj) : "uninitialized",
+                                        self->obj);
+    Py_DECREF (module);
+    return repr;
+}
+
+
+static int
+pygobject_traverse(PyGObject *self, visitproc visit, void *arg)
+{
+    int ret = 0;
+    GSList *tmp;
+    PyGObjectData *data = pygobject_get_inst_data(self);
+
+    if (self->inst_dict) ret = visit(self->inst_dict, arg);
+    if (ret != 0) return ret;
+
+    /* Only let the GC track the closures when tp_clear() would free them.
+     * https://bugzilla.gnome.org/show_bug.cgi?id=731501
+     */
+    if (data && self->obj->ref_count == 1) {
+        for (tmp = data->closures; tmp != NULL; tmp = tmp->next) {
+            PyGClosure *closure = tmp->data;
+
+            if (closure->callback) ret = visit(closure->callback, arg);
+            if (ret != 0) return ret;
+
+            if (closure->extra_args) ret = visit(closure->extra_args, arg);
+            if (ret != 0) return ret;
+
+            if (closure->swap_data) ret = visit(closure->swap_data, arg);
+            if (ret != 0) return ret;
+        }
+    }
+    return ret;
+}
+
+static inline int
+pygobject_clear(PyGObject *self)
+{
+    if (self->obj) {
+        g_object_set_qdata_full(self->obj, pygobject_wrapper_key, NULL, NULL);
+        if (pygobject_toggle_ref_is_active (self)) {
+            g_object_remove_toggle_ref(self->obj, pyg_toggle_notify, NULL);
+            self->private_flags.flags &= ~PYGOBJECT_USING_TOGGLE_REF;
+        } else {
+            Py_BEGIN_ALLOW_THREADS;
+            g_object_unref(self->obj);
+            Py_END_ALLOW_THREADS;
+        }
+        self->obj = NULL;
+    }
+    Py_CLEAR(self->inst_dict);
+    return 0;
+}
+
+static void
+pygobject_free(PyObject *op)
+{
+    PyObject_GC_Del(op);
+}
+
+gboolean
+pygobject_prepare_construct_properties(GObjectClass *class, PyObject *kwargs,
+                                       guint *n_params, GParameter **params)
+{
+    *n_params = 0;
+    *params = NULL;
+
+    if (kwargs) {
+        Py_ssize_t pos = 0;
+        PyObject *key;
+        PyObject *value;
+
+        *params = g_new0(GParameter, PyDict_Size(kwargs));
+        while (PyDict_Next(kwargs, &pos, &key, &value)) {
+            GParamSpec *pspec;
+            GParameter *param = &(*params)[*n_params];
+            const gchar *key_str = PYGLIB_PyUnicode_AsString(key);
+
+            pspec = g_object_class_find_property(class, key_str);
+            if (!pspec) {
+                PyErr_Format(PyExc_TypeError,
+                             "gobject `%s' doesn't support property `%s'",
+                             G_OBJECT_CLASS_NAME(class), key_str);
+                return FALSE;
+            }
+            g_value_init(&param->value, G_PARAM_SPEC_VALUE_TYPE(pspec));
+            if (pyg_param_gvalue_from_pyobject(&param->value, value, pspec) < 0) {
+                PyErr_Format(PyExc_TypeError,
+                             "could not convert value for property `%s' from %s to %s",
+                             key_str, Py_TYPE(value)->tp_name,
+                             g_type_name(G_PARAM_SPEC_VALUE_TYPE(pspec)));
+                return FALSE;
+            }
+            param->name = g_strdup(key_str);
+            ++(*n_params);
+        }
+    }
+    return TRUE;
+}
+
+/* ---------------- PyGObject methods ----------------- */
+
+static int
+pygobject_init(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+    GType object_type;
+    guint n_params = 0, i;
+    GParameter *params = NULL;
+    GObjectClass *class;
+
+    /* Only do GObject creation and property setting if the GObject hasn't
+     * already been created. The case where self->obj already exists can occur
+     * when C constructors are called directly (Gtk.Button.new_with_label)
+     * and we are simply wrapping the result with a PyGObject.
+     * In these cases we want to ignore any keyword arguments passed along
+     * to __init__ and simply return.
+     *
+     * See: https://bugzilla.gnome.org/show_bug.cgi?id=705810
+     */
+    if (self->obj != NULL)
+        return 0;
+
+    if (!PyArg_ParseTuple(args, ":GObject.__init__", NULL))
+       return -1;
+
+    object_type = pyg_type_from_object((PyObject *)self);
+    if (!object_type)
+       return -1;
+
+    if (G_TYPE_IS_ABSTRACT(object_type)) {
+       PyErr_Format(PyExc_TypeError, "cannot create instance of abstract "
+                    "(non-instantiable) type `%s'", g_type_name(object_type));
+       return -1;
+    }
+
+    if ((class = g_type_class_ref (object_type)) == NULL) {
+       PyErr_SetString(PyExc_TypeError,
+                       "could not get a reference to type class");
+       return -1;
+    }
+
+    if (!pygobject_prepare_construct_properties (class, kwargs, &n_params, &params))
+        goto cleanup;
+
+    if (pygobject_constructv(self, n_params, params))
+       PyErr_SetString(PyExc_RuntimeError, "could not create object");
+
+ cleanup:
+    for (i = 0; i < n_params; i++) {
+       g_free((gchar *) params[i].name);
+       g_value_unset(&params[i].value);
+    }
+    g_free(params);
+    g_type_class_unref(class);
+    
+    return (self->obj) ? 0 : -1;
+}
+
+#define CHECK_GOBJECT(self) \
+    if (!G_IS_OBJECT(self->obj)) {                                           \
+       PyErr_Format(PyExc_TypeError,                                        \
+                     "object at %p of type %s is not initialized",          \
+                     self, Py_TYPE(self)->tp_name);                         \
+       return NULL;                                                         \
+    }
+
+static PyObject *
+pygobject_get_property (PyGObject *self, PyObject *args)
+{
+    gchar *param_name;
+
+    if (!PyArg_ParseTuple (args, "s:GObject.get_property", &param_name)) {
+        return NULL;
+    }
+
+    CHECK_GOBJECT(self);
+
+    return pygi_get_property_value_by_name (self, param_name);
+}
+
+static PyObject *
+pygobject_get_properties(PyGObject *self, PyObject *args)
+{
+    Py_ssize_t len, i;
+    PyObject *tuple;
+
+    if ((len = PyTuple_Size(args)) < 1) {
+        PyErr_SetString(PyExc_TypeError, "requires at least one argument");
+        return NULL;
+    }
+
+    tuple = PyTuple_New(len);
+    for (i = 0; i < len; i++) {
+        PyObject *py_property = PyTuple_GetItem(args, i);
+        gchar *property_name;
+        PyObject *item;
+
+        if (!PYGLIB_PyUnicode_Check(py_property)) {
+            PyErr_SetString(PyExc_TypeError,
+                            "Expected string argument for property.");
+            goto fail;
+        }
+
+        property_name = PYGLIB_PyUnicode_AsString(py_property);
+        item = pygi_get_property_value_by_name (self, property_name);
+        PyTuple_SetItem (tuple, i, item);
+    }
+
+    return tuple;
+
+fail:
+    Py_DECREF (tuple);
+    return NULL;
+}
+
+static PyObject *
+pygobject_set_property(PyGObject *self, PyObject *args)
+{
+    gchar *param_name;
+    GParamSpec *pspec;
+    PyObject *pvalue;
+    int ret = -1;
+
+    if (!PyArg_ParseTuple(args, "sO:GObject.set_property", &param_name,
+                         &pvalue))
+       return NULL;
+    
+    CHECK_GOBJECT(self);
+    
+    pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(self->obj),
+                                        param_name);
+    if (!pspec) {
+       PyErr_Format(PyExc_TypeError,
+                    "object of type `%s' does not have property `%s'",
+                    g_type_name(G_OBJECT_TYPE(self->obj)), param_name);
+       return NULL;
+    }
+    
+    ret = pygi_set_property_value (self, pspec, pvalue);
+    if (ret == 0)
+       goto done;
+    else if (PyErr_Occurred())
+        return  NULL;
+
+    if (!set_property_from_pspec(self->obj, pspec, pvalue))
+       return NULL;
+
+done:
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+pygobject_set_properties(PyGObject *self, PyObject *args, PyObject *kwargs)
+{    
+    GObjectClass    *class;
+    Py_ssize_t      pos;
+    PyObject        *value;
+    PyObject        *key;
+    PyObject        *result = NULL;
+
+    CHECK_GOBJECT(self);
+
+    class = G_OBJECT_GET_CLASS(self->obj);
+    
+    g_object_freeze_notify (G_OBJECT(self->obj));
+    pos = 0;
+
+    while (kwargs && PyDict_Next (kwargs, &pos, &key, &value)) {
+       gchar *key_str = PYGLIB_PyUnicode_AsString(key);
+       GParamSpec *pspec;
+       int ret = -1;
+
+       pspec = g_object_class_find_property(class, key_str);
+       if (!pspec) {
+           gchar buf[512];
+
+           g_snprintf(buf, sizeof(buf),
+                      "object `%s' doesn't support property `%s'",
+                      g_type_name(G_OBJECT_TYPE(self->obj)), key_str);
+           PyErr_SetString(PyExc_TypeError, buf);
+           goto exit;
+       }
+
+        ret = pygi_set_property_value (self, pspec, value);
+        if (ret != 0) {
+            /* Non-zero return code means that either an error occured ...*/
+            if (PyErr_Occurred())
+                goto exit;
+
+            /* ... or the property couldn't be found , so let's try the default
+             * call. */
+            if (!set_property_from_pspec(G_OBJECT(self->obj), pspec, value))
+                goto exit;
+        }
+    }
+
+    result = Py_None;
+
+ exit:
+    g_object_thaw_notify (G_OBJECT(self->obj));
+    Py_XINCREF(result);
+    return result;
+}
+
+/* custom closure for gobject bindings */
+static void
+pygbinding_closure_invalidate(gpointer data, GClosure *closure)
+{
+    PyGClosure *pc = (PyGClosure *)closure;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+    Py_XDECREF(pc->callback);
+    Py_XDECREF(pc->extra_args);
+    PyGILState_Release(state);
+
+    pc->callback = NULL;
+    pc->extra_args = NULL;
+}
+
+static void
+pygbinding_marshal (GClosure     *closure,
+                    GValue       *return_value,
+                    guint         n_param_values,
+                    const GValue *param_values,
+                    gpointer      invocation_hint,
+                    gpointer      marshal_data)
+{
+    PyGILState_STATE state;
+    PyGClosure *pc = (PyGClosure *)closure;
+    PyObject *params, *ret;
+    GValue *out_value;
+
+    state = PyGILState_Ensure();
+
+    /* construct Python tuple for the parameter values */
+    params = PyTuple_New(2);
+    PyTuple_SetItem (params, 0, pyg_value_as_pyobject(&param_values[0], FALSE));
+    PyTuple_SetItem (params, 1, pyg_value_as_pyobject(&param_values[1], FALSE));
+
+    /* params passed to function may have extra arguments */
+    if (pc->extra_args) {
+        PyObject *tuple = params;
+        params = PySequence_Concat(tuple, pc->extra_args);
+        Py_DECREF(tuple);
+    }
+    ret = PyObject_CallObject(pc->callback, params);
+    if (!ret) {
+        PyErr_Print ();
+        goto out;
+    } else if (ret == Py_None) {
+        g_value_set_boolean (return_value, FALSE);
+        goto out;
+    }
+
+    out_value = g_value_get_boxed (&param_values[2]);
+    if (pyg_value_from_pyobject (out_value, ret) != 0) {
+        PyErr_SetString (PyExc_ValueError, "can't convert value");
+        PyErr_Print ();
+        g_value_set_boolean (return_value, FALSE);
+    } else {
+        g_value_set_boolean (return_value, TRUE);
+    }
+
+    Py_DECREF(ret);
+
+out:
+    Py_DECREF(params);
+    PyGILState_Release(state);
+}
+
+static GClosure *
+pygbinding_closure_new (PyObject *callback, PyObject *extra_args)
+{
+    GClosure *closure;
+
+    g_return_val_if_fail(callback != NULL, NULL);
+    closure = g_closure_new_simple(sizeof(PyGClosure), NULL);
+    g_closure_add_invalidate_notifier(closure, NULL, pygbinding_closure_invalidate);
+    g_closure_set_marshal(closure, pygbinding_marshal);
+    Py_INCREF(callback);
+    ((PyGClosure *)closure)->callback = callback;
+    if (extra_args && extra_args != Py_None) {
+        Py_INCREF(extra_args);
+        if (!PyTuple_Check(extra_args)) {
+            PyObject *tmp = PyTuple_New(1);
+            PyTuple_SetItem(tmp, 0, extra_args);
+            extra_args = tmp;
+        }
+        ((PyGClosure *)closure)->extra_args = extra_args;
+    }
+    return closure;
+}
+
+static PyObject *
+pygobject_bind_property(PyGObject *self, PyObject *args)
+{
+       gchar *source_name, *target_name;
+       gchar *source_canon, *target_canon;
+       PyObject *target, *source_repr, *target_repr;
+       PyObject *transform_to, *transform_from, *user_data = NULL;
+       GBinding *binding;
+       GBindingFlags flags = G_BINDING_DEFAULT;
+       GClosure *to_closure = NULL, *from_closure = NULL;
+
+       transform_from = NULL;
+       transform_to = NULL;
+
+       if (!PyArg_ParseTuple(args, "sOs|iOOO:GObject.bind_property",
+                             &source_name, &target, &target_name, &flags,
+                             &transform_to, &transform_from, &user_data))
+               return NULL;
+
+       CHECK_GOBJECT(self);
+       if (!PyObject_TypeCheck(target, &PyGObject_Type)) {
+               PyErr_SetString(PyExc_TypeError, "Second argument must be a GObject");
+               return NULL;
+       }
+
+       if (transform_to && transform_to != Py_None) {
+               if (!PyCallable_Check (transform_to)) {
+                       PyErr_SetString (PyExc_TypeError,
+                                        "transform_to must be callable or None");
+                       return NULL;
+               }
+               to_closure = pygbinding_closure_new (transform_to, user_data);
+       }
+
+       if (transform_from && transform_from != Py_None) {
+               if (!PyCallable_Check (transform_from)) {
+                       PyErr_SetString (PyExc_TypeError,
+                                        "transform_from must be callable or None");
+                       return NULL;
+               }
+               from_closure = pygbinding_closure_new (transform_from, user_data);
+       }
+
+       /* Canonicalize underscores to hyphens. Note the results must be freed. */
+       source_canon = g_strdelimit(g_strdup(source_name), "_", '-');
+       target_canon = g_strdelimit(g_strdup(target_name), "_", '-');
+
+       binding = g_object_bind_property_with_closures (G_OBJECT(self->obj), source_canon,
+                                                       pygobject_get(target), target_canon,
+                                                       flags, to_closure, from_closure);
+       g_free(source_canon);
+       g_free(target_canon);
+       source_canon = target_canon = NULL;
+
+       if (binding == NULL) {
+               source_repr = PyObject_Repr((PyObject*)self);
+               target_repr = PyObject_Repr(target);
+               PyErr_Format(PyExc_TypeError, "Cannot create binding from %s.%s to %s.%s",
+                            PYGLIB_PyUnicode_AsString(source_repr), source_name,
+                            PYGLIB_PyUnicode_AsString(target_repr), target_name);
+               Py_DECREF(source_repr);
+               Py_DECREF(target_repr);
+               return NULL;
+       }
+
+       return pygobject_new (G_OBJECT (binding));
+}
+
+static PyObject *
+connect_helper(PyGObject *self, gchar *name, PyObject *callback, PyObject *extra_args, PyObject *object, gboolean after)
+{
+    guint sigid;
+    GQuark detail = 0;
+    GClosure *closure = NULL;
+    gulong handlerid;
+    GSignalQuery query_info;
+
+    if (!g_signal_parse_name(name, G_OBJECT_TYPE(self->obj),
+                            &sigid, &detail, TRUE)) {
+       PyObject *repr = PyObject_Repr((PyObject*)self);
+       PyErr_Format(PyExc_TypeError, "%s: unknown signal name: %s",
+                    PYGLIB_PyUnicode_AsString(repr),
+                    name);
+       Py_DECREF(repr);
+       return NULL;
+    }
+
+    if (object && !PyObject_TypeCheck (object, &PyGObject_Type)) {
+        if (PyErr_WarnEx (PyGIDeprecationWarning,
+                          "Using non GObject arguments for connect_object() is deprecated, use: "
+                          "connect_data(signal, callback, data, connect_flags=GObject.ConnectFlags.SWAPPED)",
+                          1)) {
+            return NULL;
+        }
+    }
+
+    g_signal_query (sigid, &query_info);
+    if (!pyg_gtype_is_custom (query_info.itype)) {
+        /* The signal is implemented by a non-Python class, probably
+         * something in the gi repository. */
+        closure = pygi_signal_closure_new (self, query_info.itype,
+                                           query_info.signal_name, callback,
+                                           extra_args, object);
+    }
+
+    if (!closure) {
+        /* The signal is either implemented at the Python level, or it comes
+         * from a foreign class that we don't have introspection data for. */
+        closure = pyg_closure_new (callback, extra_args, object);
+    }
+
+    pygobject_watch_closure((PyObject *)self, closure);
+    handlerid = g_signal_connect_closure_by_id(self->obj, sigid, detail,
+                                              closure, after);
+    return pygi_gulong_to_py (handlerid);
+}
+
+static PyObject *
+pygobject_connect(PyGObject *self, PyObject *args)
+{
+    PyObject *first, *callback, *extra_args, *ret;
+    gchar *name;
+    Py_ssize_t len;
+
+    len = PyTuple_Size(args);
+    if (len < 2) {
+       PyErr_SetString(PyExc_TypeError,
+                       "GObject.connect requires at least 2 arguments");
+       return NULL;
+    }
+    first = PySequence_GetSlice(args, 0, 2);
+    if (!PyArg_ParseTuple(first, "sO:GObject.connect", &name, &callback)) {
+       Py_DECREF(first);
+       return NULL;
+    }
+    Py_DECREF(first);
+    if (!PyCallable_Check(callback)) {
+       PyErr_SetString(PyExc_TypeError, "second argument must be callable");
+       return NULL;
+    }
+    
+    CHECK_GOBJECT(self);
+    
+    extra_args = PySequence_GetSlice(args, 2, len);
+    if (extra_args == NULL)
+       return NULL;
+
+    ret = connect_helper(self, name, callback, extra_args, NULL, FALSE);
+    Py_DECREF(extra_args);
+    return ret;
+}
+
+static PyObject *
+pygobject_connect_after(PyGObject *self, PyObject *args)
+{
+    PyObject *first, *callback, *extra_args, *ret;
+    gchar *name;
+    Py_ssize_t len;
+
+    len = PyTuple_Size(args);
+    if (len < 2) {
+       PyErr_SetString(PyExc_TypeError,
+                       "GObject.connect_after requires at least 2 arguments");
+       return NULL;
+    }
+    first = PySequence_GetSlice(args, 0, 2);
+    if (!PyArg_ParseTuple(first, "sO:GObject.connect_after",
+                         &name, &callback)) {
+       Py_DECREF(first);
+       return NULL;
+    }
+    Py_DECREF(first);
+    if (!PyCallable_Check(callback)) {
+       PyErr_SetString(PyExc_TypeError, "second argument must be callable");
+       return NULL;
+    }
+    
+    CHECK_GOBJECT(self);
+    
+    extra_args = PySequence_GetSlice(args, 2, len);
+    if (extra_args == NULL)
+       return NULL;
+
+    ret = connect_helper(self, name, callback, extra_args, NULL, TRUE);
+    Py_DECREF(extra_args);
+    return ret;
+}
+
+static PyObject *
+pygobject_connect_object(PyGObject *self, PyObject *args)
+{
+    PyObject *first, *callback, *extra_args, *object, *ret;
+    gchar *name;
+    Py_ssize_t len;
+
+    len = PyTuple_Size(args);
+    if (len < 3) {
+       PyErr_SetString(PyExc_TypeError,
+               "GObject.connect_object requires at least 3 arguments");
+       return NULL;
+    }
+    first = PySequence_GetSlice(args, 0, 3);
+    if (!PyArg_ParseTuple(first, "sOO:GObject.connect_object",
+                         &name, &callback, &object)) {
+       Py_DECREF(first);
+       return NULL;
+    }
+    Py_DECREF(first);
+    if (!PyCallable_Check(callback)) {
+       PyErr_SetString(PyExc_TypeError, "second argument must be callable");
+       return NULL;
+    }
+    
+    CHECK_GOBJECT(self);
+    
+    extra_args = PySequence_GetSlice(args, 3, len);
+    if (extra_args == NULL)
+       return NULL;
+
+    ret = connect_helper(self, name, callback, extra_args, object, FALSE);
+    Py_DECREF(extra_args);
+    return ret;
+}
+
+static PyObject *
+pygobject_connect_object_after(PyGObject *self, PyObject *args)
+{
+    PyObject *first, *callback, *extra_args, *object, *ret;
+    gchar *name;
+    Py_ssize_t len;
+
+    len = PyTuple_Size(args);
+    if (len < 3) {
+       PyErr_SetString(PyExc_TypeError,
+               "GObject.connect_object_after requires at least 3 arguments");
+       return NULL;
+    }
+    first = PySequence_GetSlice(args, 0, 3);
+    if (!PyArg_ParseTuple(first, "sOO:GObject.connect_object_after",
+                         &name, &callback, &object)) {
+       Py_DECREF(first);
+       return NULL;
+    }
+    Py_DECREF(first);
+    if (!PyCallable_Check(callback)) {
+       PyErr_SetString(PyExc_TypeError, "second argument must be callable");
+       return NULL;
+    }
+    
+    CHECK_GOBJECT(self);
+    
+    extra_args = PySequence_GetSlice(args, 3, len);
+    if (extra_args == NULL)
+       return NULL;
+
+    ret = connect_helper(self, name, callback, extra_args, object, TRUE);
+    Py_DECREF(extra_args);
+    return ret;
+}
+
+static PyObject *
+pygobject_emit(PyGObject *self, PyObject *args)
+{
+    guint signal_id, i, j;
+    Py_ssize_t len;
+    GQuark detail;
+    PyObject *first, *py_ret, *repr = NULL;
+    gchar *name;
+    GSignalQuery query;
+    GValue *params, ret = { 0, };
+    
+    len = PyTuple_Size(args);
+    if (len < 1) {
+       PyErr_SetString(PyExc_TypeError,"GObject.emit needs at least one arg");
+       return NULL;
+    }
+    first = PySequence_GetSlice(args, 0, 1);
+    if (!PyArg_ParseTuple(first, "s:GObject.emit", &name)) {
+       Py_DECREF(first);
+       return NULL;
+    }
+    Py_DECREF(first);
+    
+    CHECK_GOBJECT(self);
+    
+    if (!g_signal_parse_name(name, G_OBJECT_TYPE(self->obj),
+                            &signal_id, &detail, TRUE)) {
+       repr = PyObject_Repr((PyObject*)self);
+       PyErr_Format(PyExc_TypeError, "%s: unknown signal name: %s",
+                    PYGLIB_PyUnicode_AsString(repr),
+                    name);
+       Py_DECREF(repr);
+       return NULL;
+    }
+    g_signal_query(signal_id, &query);
+    if ((gsize)len != query.n_params + 1) {
+       gchar buf[128];
+
+       g_snprintf(buf, sizeof(buf),
+                  "%d parameters needed for signal %s; %ld given",
+                  query.n_params, name, (long int) (len - 1));
+       PyErr_SetString(PyExc_TypeError, buf);
+       return NULL;
+    }
+
+    params = g_new0(GValue, query.n_params + 1);
+    g_value_init(&params[0], G_OBJECT_TYPE(self->obj));
+    g_value_set_object(&params[0], G_OBJECT(self->obj));
+
+    for (i = 0; i < query.n_params; i++)
+       g_value_init(&params[i + 1],
+                    query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE);
+    for (i = 0; i < query.n_params; i++) {
+       PyObject *item = PyTuple_GetItem(args, i+1);
+
+       if (pyg_value_from_pyobject(&params[i+1], item) < 0) {
+           gchar buf[128];
+           g_snprintf(buf, sizeof(buf),
+                      "could not convert type %s to %s required for parameter %d",
+                      Py_TYPE(item)->tp_name,
+                       G_VALUE_TYPE_NAME(&params[i+1]), i);
+           PyErr_SetString(PyExc_TypeError, buf);
+
+           for (j = 0; j <= i; j++)
+               g_value_unset(&params[j]);
+
+           g_free(params);
+           return NULL;
+       }
+    }    
+
+    if (query.return_type != G_TYPE_NONE)
+       g_value_init(&ret, query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE);
+    
+    Py_BEGIN_ALLOW_THREADS;
+    g_signal_emitv(params, signal_id, detail, &ret);
+    Py_END_ALLOW_THREADS;
+
+    for (i = 0; i < query.n_params + 1; i++)
+       g_value_unset(&params[i]);
+    
+    g_free(params);
+    if ((query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE) != G_TYPE_NONE) {
+       py_ret = pyg_value_as_pyobject(&ret, TRUE);
+       g_value_unset(&ret);
+    } else {
+       Py_INCREF(Py_None);
+       py_ret = Py_None;
+    }
+
+    return py_ret;
+}
+
+static PyObject *
+pygobject_chain_from_overridden(PyGObject *self, PyObject *args)
+{
+    GSignalInvocationHint *ihint;
+    guint signal_id, i;
+    Py_ssize_t len;
+    PyObject *py_ret;
+    const gchar *name;
+    GSignalQuery query;
+    GValue *params, ret = { 0, };
+    
+    CHECK_GOBJECT(self);
+    
+    ihint = g_signal_get_invocation_hint(self->obj);
+    if (!ihint) {
+       PyErr_SetString(PyExc_TypeError, "could not find signal invocation "
+                       "information for this object.");
+       return NULL;
+    }
+
+    signal_id = ihint->signal_id;
+    name = g_signal_name(signal_id);
+
+    len = PyTuple_Size(args);
+    if (signal_id == 0) {
+       PyErr_SetString(PyExc_TypeError, "unknown signal name");
+       return NULL;
+    }
+    g_signal_query(signal_id, &query);
+    if (len < 0 || (gsize)len != query.n_params) {
+       gchar buf[128];
+
+       g_snprintf(buf, sizeof(buf),
+                  "%d parameters needed for signal %s; %ld given",
+                  query.n_params, name, (long int) len);
+       PyErr_SetString(PyExc_TypeError, buf);
+       return NULL;
+    }
+    params = g_new0(GValue, query.n_params + 1);
+    g_value_init(&params[0], G_OBJECT_TYPE(self->obj));
+    g_value_set_object(&params[0], G_OBJECT(self->obj));
+
+    for (i = 0; i < query.n_params; i++)
+       g_value_init(&params[i + 1],
+                    query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE);
+    for (i = 0; i < query.n_params; i++) {
+       PyObject *item = PyTuple_GetItem(args, i);
+
+       if (pyg_boxed_check(item, (query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE))) {
+           g_value_set_static_boxed(&params[i+1], pyg_boxed_get(item, void));
+       }
+       else if (pyg_value_from_pyobject(&params[i+1], item) < 0) {
+           gchar buf[128];
+
+           g_snprintf(buf, sizeof(buf),
+                      "could not convert type %s to %s required for parameter %d",
+                      Py_TYPE(item)->tp_name,
+                      g_type_name(G_VALUE_TYPE(&params[i+1])), i);
+           PyErr_SetString(PyExc_TypeError, buf);
+           for (i = 0; i < query.n_params + 1; i++)
+               g_value_unset(&params[i]);
+           g_free(params);
+           return NULL;
+       }
+    }
+    if (query.return_type != G_TYPE_NONE)
+       g_value_init(&ret, query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE);
+    g_signal_chain_from_overridden(params, &ret);
+    for (i = 0; i < query.n_params + 1; i++)
+       g_value_unset(&params[i]);
+    g_free(params);
+    if (query.return_type != G_TYPE_NONE) {
+       py_ret = pyg_value_as_pyobject(&ret, TRUE);
+       g_value_unset(&ret);
+    } else {
+       Py_INCREF(Py_None);
+       py_ret = Py_None;
+    }
+    return py_ret;
+}
+
+
+static PyObject *
+pygobject_weak_ref(PyGObject *self, PyObject *args)
+{
+    Py_ssize_t len;
+    PyObject *callback = NULL, *user_data = NULL;
+    PyObject *retval;
+
+    CHECK_GOBJECT(self);
+
+    if ((len = PySequence_Length(args)) >= 1) {
+        callback = PySequence_ITEM(args, 0);
+        user_data = PySequence_GetSlice(args, 1, len);
+    }
+    retval = pygobject_weak_ref_new(self->obj, callback, user_data);
+    Py_XDECREF(callback);
+    Py_XDECREF(user_data);
+    return retval;
+}
+
+
+static PyObject *
+pygobject_copy(PyGObject *self)
+{
+    PyErr_SetString(PyExc_TypeError,
+                   "GObject descendants' instances are non-copyable");
+    return NULL;
+}
+
+static PyObject *
+pygobject_deepcopy(PyGObject *self, PyObject *args)
+{
+    PyErr_SetString(PyExc_TypeError,
+                   "GObject descendants' instances are non-copyable");
+    return NULL;
+}
+
+
+static PyObject *
+pygobject_disconnect_by_func(PyGObject *self, PyObject *args)
+{
+    PyObject *pyfunc = NULL, *repr = NULL;
+    GClosure *closure = NULL;
+    guint retval;
+    
+    CHECK_GOBJECT(self);
+
+    if (!PyArg_ParseTuple(args, "O:GObject.disconnect_by_func", &pyfunc))
+       return NULL;
+
+    if (!PyCallable_Check(pyfunc)) {
+       PyErr_SetString(PyExc_TypeError, "first argument must be callable");
+       return NULL;
+    }
+
+    closure = gclosure_from_pyfunc(self, pyfunc);
+    if (!closure) {
+       repr = PyObject_Repr((PyObject*)pyfunc);
+       PyErr_Format(PyExc_TypeError, "nothing connected to %s",
+                    PYGLIB_PyUnicode_AsString(repr));
+       Py_DECREF(repr);
+       return NULL;
+    }
+    
+    retval = g_signal_handlers_disconnect_matched(self->obj,
+                                                 G_SIGNAL_MATCH_CLOSURE,
+                                                 0, 0,
+                                                 closure,
+                                                 NULL, NULL);
+    return pygi_guint_to_py (retval);
+}
+
+static PyObject *
+pygobject_handler_block_by_func(PyGObject *self, PyObject *args)
+{
+    PyObject *pyfunc = NULL, *repr = NULL;
+    GClosure *closure = NULL;
+    guint retval;
+    
+    CHECK_GOBJECT(self);
+
+    if (!PyArg_ParseTuple(args, "O:GObject.handler_block_by_func", &pyfunc))
+       return NULL;
+
+    if (!PyCallable_Check(pyfunc)) {
+       PyErr_SetString(PyExc_TypeError, "first argument must be callable");
+       return NULL;
+    }
+
+    closure = gclosure_from_pyfunc(self, pyfunc);
+    if (!closure) {
+       repr = PyObject_Repr((PyObject*)pyfunc);
+       PyErr_Format(PyExc_TypeError, "nothing connected to %s",
+                    PYGLIB_PyUnicode_AsString(repr));
+       Py_DECREF(repr);
+       return NULL;
+    }
+    
+    retval = g_signal_handlers_block_matched(self->obj,
+                                            G_SIGNAL_MATCH_CLOSURE,
+                                            0, 0,
+                                            closure,
+                                            NULL, NULL);
+    return pygi_guint_to_py (retval);
+}
+
+static PyObject *
+pygobject_handler_unblock_by_func(PyGObject *self, PyObject *args)
+{
+    PyObject *pyfunc = NULL, *repr = NULL;
+    GClosure *closure = NULL;
+    guint retval;
+    
+    CHECK_GOBJECT(self);
+
+    if (!PyArg_ParseTuple(args, "O:GObject.handler_unblock_by_func", &pyfunc))
+       return NULL;
+
+    if (!PyCallable_Check(pyfunc)) {
+       PyErr_SetString(PyExc_TypeError, "first argument must be callable");
+       return NULL;
+    }
+
+    closure = gclosure_from_pyfunc(self, pyfunc);
+    if (!closure) {
+       repr = PyObject_Repr((PyObject*)pyfunc);
+       PyErr_Format(PyExc_TypeError, "nothing connected to %s",
+                    PYGLIB_PyUnicode_AsString(repr));
+       Py_DECREF(repr);
+       return NULL;
+    }
+    
+    retval = g_signal_handlers_unblock_matched(self->obj,
+                                              G_SIGNAL_MATCH_CLOSURE,
+                                              0, 0,
+                                              closure,
+                                              NULL, NULL);
+    return pygi_guint_to_py (retval);
+}
+
+
+static PyMethodDef pygobject_methods[] = {
+    { "get_property", (PyCFunction)pygobject_get_property, METH_VARARGS },
+    { "get_properties", (PyCFunction)pygobject_get_properties, METH_VARARGS },
+    { "set_property", (PyCFunction)pygobject_set_property, METH_VARARGS },
+    { "set_properties", (PyCFunction)pygobject_set_properties, METH_VARARGS|METH_KEYWORDS },
+    { "bind_property", (PyCFunction)pygobject_bind_property, METH_VARARGS|METH_KEYWORDS },
+    { "connect", (PyCFunction)pygobject_connect, METH_VARARGS },
+    { "connect_after", (PyCFunction)pygobject_connect_after, METH_VARARGS },
+    { "connect_object", (PyCFunction)pygobject_connect_object, METH_VARARGS },
+    { "connect_object_after", (PyCFunction)pygobject_connect_object_after, METH_VARARGS },
+    { "disconnect_by_func", (PyCFunction)pygobject_disconnect_by_func, METH_VARARGS },
+    { "handler_block_by_func", (PyCFunction)pygobject_handler_block_by_func, METH_VARARGS },
+    { "handler_unblock_by_func", (PyCFunction)pygobject_handler_unblock_by_func, METH_VARARGS },
+    { "emit", (PyCFunction)pygobject_emit, METH_VARARGS },
+    { "chain", (PyCFunction)pygobject_chain_from_overridden,METH_VARARGS },
+    { "weak_ref", (PyCFunction)pygobject_weak_ref, METH_VARARGS },
+    { "__copy__", (PyCFunction)pygobject_copy, METH_NOARGS },
+    { "__deepcopy__", (PyCFunction)pygobject_deepcopy, METH_VARARGS },
+    { NULL, NULL, 0 }
+};
+
+#ifdef PYGI_OBJECT_USE_CUSTOM_DICT
+static PyObject *
+pygobject_get_dict(PyGObject *self, void *closure)
+{
+    if (self->inst_dict == NULL) {
+        self->inst_dict = PyDict_New();
+        pygobject_toggle_ref_ensure (self);
+    }
+    Py_INCREF(self->inst_dict);
+    return self->inst_dict;
+}
+#endif
+
+static PyObject *
+pygobject_get_refcount(PyGObject *self, void *closure)
+{
+    if (self->obj == NULL) {
+        PyErr_Format(PyExc_TypeError, "GObject instance is not yet created");
+        return NULL;
+    }
+    return pygi_guint_to_py (self->obj->ref_count);
+}
+
+static PyObject *
+pygobject_get_pointer(PyGObject *self, void *closure)
+{
+    return PyCapsule_New (self->obj, NULL, NULL);
+}
+
+static int
+pygobject_setattro(PyObject *self, PyObject *name, PyObject *value)
+{
+    int res;
+    res = PyGObject_Type.tp_base->tp_setattro(self, name, value);
+    pygobject_toggle_ref_ensure ((PyGObject *) self);
+    return res;
+}
+
+static PyGetSetDef pygobject_getsets[] = {
+#ifdef PYGI_OBJECT_USE_CUSTOM_DICT
+    { "__dict__", (getter)pygobject_get_dict, (setter)0 },
+#endif
+    { "__grefcount__", (getter)pygobject_get_refcount, (setter)0, },
+    { "__gpointer__", (getter)pygobject_get_pointer, (setter)0, },
+    { NULL, 0, 0 }
+};
+
+/* ------------------------------------ */
+/* ****** GObject weak reference ****** */
+/* ------------------------------------ */
+
+typedef struct {
+    PyObject_HEAD
+    GObject *obj;
+    PyObject *callback;
+    PyObject *user_data;
+    gboolean have_floating_ref;
+} PyGObjectWeakRef;
+
+PYGLIB_DEFINE_TYPE("gi._gi.GObjectWeakRef", PyGObjectWeakRef_Type, PyGObjectWeakRef);
+
+static int
+pygobject_weak_ref_traverse(PyGObjectWeakRef *self, visitproc visit, void *arg)
+{
+    if (self->callback && visit(self->callback, arg) < 0)
+        return -1;
+    if (self->user_data && visit(self->user_data, arg) < 0)
+        return -1;
+    return 0;
+}
+
+static void
+pygobject_weak_ref_notify(PyGObjectWeakRef *self, GObject *dummy)
+{
+    self->obj = NULL;
+    if (self->callback) {
+        PyObject *retval;
+        PyGILState_STATE state = PyGILState_Ensure();
+        retval = PyObject_Call(self->callback, self->user_data, NULL);
+        if (retval) {
+            if (retval != Py_None)
+                PyErr_Format(PyExc_TypeError,
+                             "GObject weak notify callback returned a value"
+                             " of type %s, should return None",
+                             Py_TYPE(retval)->tp_name);
+            Py_DECREF(retval);
+            PyErr_Print();
+        } else
+            PyErr_Print();
+        Py_CLEAR(self->callback);
+        Py_CLEAR(self->user_data);
+        if (self->have_floating_ref) {
+            self->have_floating_ref = FALSE;
+            Py_DECREF((PyObject *) self);
+        }
+        PyGILState_Release(state);
+    }
+}
+
+static inline int
+pygobject_weak_ref_clear(PyGObjectWeakRef *self)
+{
+    Py_CLEAR(self->callback);
+    Py_CLEAR(self->user_data);
+    if (self->obj) {
+        g_object_weak_unref(self->obj, (GWeakNotify) pygobject_weak_ref_notify, self);
+        self->obj = NULL;
+    }
+    return 0;
+}
+
+static void
+pygobject_weak_ref_dealloc(PyGObjectWeakRef *self)
+{
+    PyObject_GC_UnTrack((PyObject *)self);
+    pygobject_weak_ref_clear(self);
+    PyObject_GC_Del(self);
+}
+
+static PyObject *
+pygobject_weak_ref_new(GObject *obj, PyObject *callback, PyObject *user_data)
+{
+    PyGObjectWeakRef *self;
+
+    self = PyObject_GC_New(PyGObjectWeakRef, &PyGObjectWeakRef_Type);
+    self->callback = callback;
+    self->user_data = user_data;
+    Py_XINCREF(self->callback);
+    Py_XINCREF(self->user_data);
+    self->obj = obj;
+    g_object_weak_ref(self->obj, (GWeakNotify) pygobject_weak_ref_notify, self);
+    if (callback != NULL) {
+          /* when we have a callback, we should INCREF the weakref
+           * object to make it stay alive even if it goes out of scope */
+        self->have_floating_ref = TRUE;
+        Py_INCREF((PyObject *) self);
+    }
+    return (PyObject *) self;
+}
+
+static PyObject *
+pygobject_weak_ref_unref(PyGObjectWeakRef *self, PyObject *args)
+{
+    if (!self->obj) {
+        PyErr_SetString(PyExc_ValueError, "weak ref already unreffed");
+        return NULL;
+    }
+    g_object_weak_unref(self->obj, (GWeakNotify) pygobject_weak_ref_notify, self);
+    self->obj = NULL;
+    if (self->have_floating_ref) {
+        self->have_floating_ref = FALSE;
+        Py_DECREF(self);
+    }
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyMethodDef pygobject_weak_ref_methods[] = {
+    { "unref", (PyCFunction)pygobject_weak_ref_unref, METH_NOARGS},
+    { NULL, NULL, 0}
+};
+
+static PyObject *
+pygobject_weak_ref_call(PyGObjectWeakRef *self, PyObject *args, PyObject *kw)
+{
+    static char *argnames[] = {NULL};
+
+    if (!PyArg_ParseTupleAndKeywords(args, kw, ":__call__", argnames))
+        return NULL;
+
+    if (self->obj)
+        return pygobject_new(self->obj);
+    else {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+}
+
+static gpointer
+pyobject_copy(gpointer boxed)
+{
+    PyObject *object = boxed;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+    Py_INCREF(object);
+    PyGILState_Release(state);
+    return object;
+}
+
+static void
+pyobject_free(gpointer boxed)
+{
+    PyObject *object = boxed;
+    PyGILState_STATE state;
+
+    state = PyGILState_Ensure();
+    Py_DECREF(object);
+    PyGILState_Release(state);
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pyi_object_register_types(PyObject *d)
+{
+    PyObject *o, *descr;
+
+    pygobject_custom_key = g_quark_from_static_string("PyGObject::custom");
+    pygobject_class_key = g_quark_from_static_string("PyGObject::class");
+    pygobject_class_init_key = g_quark_from_static_string("PyGObject::class-init");
+    pygobject_wrapper_key = g_quark_from_static_string("PyGObject::wrapper");
+    pygobject_has_updated_constructor_key =
+        g_quark_from_static_string("PyGObject::has-updated-constructor");
+    pygobject_instance_data_key = g_quark_from_static_string("PyGObject::instance-data");
+
+    /* GObject */
+    if (!PY_TYPE_OBJECT)
+       PY_TYPE_OBJECT = g_boxed_type_register_static("PyObject",
+                                                     pyobject_copy,
+                                                     pyobject_free);
+    PyGObject_Type.tp_dealloc = (destructor)pygobject_dealloc;
+    PyGObject_Type.tp_richcompare = pygobject_richcompare;
+    PyGObject_Type.tp_repr = (reprfunc)pygobject_repr;
+    PyGObject_Type.tp_hash = (hashfunc)pygobject_hash;
+    PyGObject_Type.tp_setattro = (setattrofunc)pygobject_setattro;
+    PyGObject_Type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
+                              Py_TPFLAGS_HAVE_GC);
+    PyGObject_Type.tp_traverse = (traverseproc)pygobject_traverse;
+    PyGObject_Type.tp_clear = (inquiry)pygobject_clear;
+    PyGObject_Type.tp_weaklistoffset = offsetof(PyGObject, weakreflist);
+    PyGObject_Type.tp_methods = pygobject_methods;
+    PyGObject_Type.tp_getset = pygobject_getsets;
+#ifdef PYGI_OBJECT_USE_CUSTOM_DICT
+    PyGObject_Type.tp_dictoffset = offsetof(PyGObject, inst_dict);
+#endif
+    PyGObject_Type.tp_init = (initproc)pygobject_init;
+    PyGObject_Type.tp_free = (freefunc)pygobject_free;
+    PyGObject_Type.tp_alloc = PyType_GenericAlloc;
+    PyGObject_Type.tp_new = PyType_GenericNew;
+    pygobject_register_class(d, "GObject", G_TYPE_OBJECT,
+                            &PyGObject_Type, NULL);
+    PyDict_SetItemString(PyGObject_Type.tp_dict, "__gdoc__",
+                        pyg_object_descr_doc_get());
+
+    /* GProps */
+    PyGProps_Type.tp_dealloc = (destructor)PyGProps_dealloc;
+    PyGProps_Type.tp_as_sequence = (PySequenceMethods*)&_PyGProps_as_sequence;
+    PyGProps_Type.tp_getattro = (getattrofunc)PyGProps_getattro;
+    PyGProps_Type.tp_setattro = (setattrofunc)PyGProps_setattro;
+    PyGProps_Type.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC;
+    PyGProps_Type.tp_doc = "The properties of the GObject accessible as "
+       "Python attributes.";
+    PyGProps_Type.tp_traverse = (traverseproc)pygobject_props_traverse;
+    PyGProps_Type.tp_iter = (getiterfunc)pygobject_props_get_iter;
+    PyGProps_Type.tp_methods = pygobject_props_methods;
+    if (PyType_Ready(&PyGProps_Type) < 0)
+        return -1;
+
+    /* GPropsDescr */
+    PyGPropsDescr_Type.tp_flags = Py_TPFLAGS_DEFAULT;
+    PyGPropsDescr_Type.tp_descr_get = pyg_props_descr_descr_get;
+    if (PyType_Ready(&PyGPropsDescr_Type) < 0)
+        return -1;
+    descr = PyObject_New(PyObject, &PyGPropsDescr_Type);
+    PyDict_SetItemString(PyGObject_Type.tp_dict, "props", descr);
+    PyDict_SetItemString(PyGObject_Type.tp_dict, "__module__",
+                        o=PYGLIB_PyUnicode_FromString("gi._gi"));
+    Py_DECREF(o);
+
+    /* GPropsIter */
+    PyGPropsIter_Type.tp_dealloc = (destructor)pyg_props_iter_dealloc;
+    PyGPropsIter_Type.tp_flags = Py_TPFLAGS_DEFAULT;
+    PyGPropsIter_Type.tp_doc = "GObject properties iterator";
+    PyGPropsIter_Type.tp_iternext = (iternextfunc)pygobject_props_iter_next;
+    if (PyType_Ready(&PyGPropsIter_Type) < 0)
+        return -1;
+
+    PyGObjectWeakRef_Type.tp_dealloc = (destructor)pygobject_weak_ref_dealloc;
+    PyGObjectWeakRef_Type.tp_call = (ternaryfunc)pygobject_weak_ref_call;
+    PyGObjectWeakRef_Type.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC;
+    PyGObjectWeakRef_Type.tp_doc = "A GObject weak reference";
+    PyGObjectWeakRef_Type.tp_traverse = (traverseproc)pygobject_weak_ref_traverse;
+    PyGObjectWeakRef_Type.tp_clear = (inquiry)pygobject_weak_ref_clear;
+    PyGObjectWeakRef_Type.tp_methods = pygobject_weak_ref_methods;
+    if (PyType_Ready(&PyGObjectWeakRef_Type) < 0)
+        return -1;
+    PyDict_SetItemString(d, "GObjectWeakRef", (PyObject *) &PyGObjectWeakRef_Type);
+
+    return 0;
+}
+
+PyObject *
+pyg_object_new (PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+    PyObject *pytype;
+    GType type;
+    GObject *obj = NULL;
+    GObjectClass *class;
+    guint n_params = 0, i;
+    GParameter *params = NULL;
+
+    if (!PyArg_ParseTuple (args, "O:gobject.new", &pytype)) {
+       return NULL;
+    }
+
+    if ((type = pyg_type_from_object (pytype)) == 0)
+       return NULL;
+
+    if (G_TYPE_IS_ABSTRACT(type)) {
+       PyErr_Format(PyExc_TypeError, "cannot create instance of abstract "
+                    "(non-instantiable) type `%s'", g_type_name(type));
+       return NULL;
+    }
+
+    if ((class = g_type_class_ref (type)) == NULL) {
+       PyErr_SetString(PyExc_TypeError,
+                       "could not get a reference to type class");
+       return NULL;
+    }
+
+    if (!pygobject_prepare_construct_properties (class, kwargs, &n_params, &params))
+        goto cleanup;
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+    obj = g_object_newv(type, n_params, params);
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+    if (!obj)
+       PyErr_SetString (PyExc_RuntimeError, "could not create object");
+
+ cleanup:
+    for (i = 0; i < n_params; i++) {
+       g_free((gchar *) params[i].name);
+       g_value_unset(&params[i].value);
+    }
+    g_free(params);
+    g_type_class_unref(class);
+
+    if (obj) {
+        pygobject_sink (obj);
+       self = (PyGObject *) pygobject_new((GObject *)obj);
+        g_object_unref(obj);
+    } else
+        self = NULL;
+
+    return (PyObject *) self;
+}
diff --git a/gi/pygobject-object.h b/gi/pygobject-object.h
new file mode 100644 (file)
index 0000000..6658244
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef _PYGOBJECT_OBJECT_H_
+#define _PYGOBJECT_OBJECT_H_
+
+#include <Python.h>
+#include <glib-object.h>
+#include "pygi-python-compat.h"
+#include "pygobject-internal.h"
+
+/* Data that belongs to the GObject instance, not the Python wrapper */
+struct _PyGObjectData {
+    PyTypeObject *type; /* wrapper type for this instance */
+    GSList *closures;
+};
+
+extern GType PY_TYPE_OBJECT;
+extern GQuark pygobject_instance_data_key;
+extern GQuark pygobject_custom_key;
+extern GQuark pygobject_wrapper_key;
+extern GQuark pygobject_class_key;
+extern GQuark pygobject_class_init_key;
+
+extern PyTypeObject PyGObjectWeakRef_Type;
+extern PyTypeObject PyGPropsIter_Type;
+extern PyTypeObject PyGPropsDescr_Type;
+extern PyTypeObject PyGProps_Type;
+extern PyTypeObject PyGObject_Type;
+extern PyTypeObject *PyGObject_MetaType;
+
+static inline PyGObjectData *
+pyg_object_peek_inst_data(GObject *obj)
+{
+    return ((PyGObjectData *)
+            g_object_get_qdata(obj, pygobject_instance_data_key));
+}
+
+gboolean      pygobject_prepare_construct_properties  (GObjectClass *class,
+                                                       PyObject *kwargs,
+                                                       guint *n_params,
+                                                       GParameter **params);
+void          pygobject_register_class   (PyObject *dict,
+                                          const gchar *type_name,
+                                          GType gtype, PyTypeObject *type,
+                                          PyObject *bases);
+void          pygobject_register_wrapper (PyObject *self);
+PyObject *    pygobject_new              (GObject *obj);
+PyObject *    pygobject_new_full         (GObject *obj, gboolean steal, gpointer g_class);
+void          pygobject_sink             (GObject *obj);
+PyTypeObject *pygobject_lookup_class     (GType gtype);
+void          pygobject_watch_closure    (PyObject *self, GClosure *closure);
+int           pyi_object_register_types  (PyObject *d);
+void          pygobject_ref_float(PyGObject *self);
+void          pygobject_ref_sink(PyGObject *self);
+PyObject *    pyg_object_new             (PyGObject *self, PyObject *args, PyObject *kwargs);
+
+GClosure *    gclosure_from_pyfunc(PyGObject *object, PyObject *func);
+
+#endif /*_PYGOBJECT_OBJECT_H_*/
diff --git a/gi/pygobject.h b/gi/pygobject.h
new file mode 100644 (file)
index 0000000..d242515
--- /dev/null
@@ -0,0 +1,638 @@
+/* -*- Mode: C; c-basic-offset: 4 -*- */
+#ifndef _PYGOBJECT_H_
+#define _PYGOBJECT_H_
+
+#include <Python.h>
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* PyGClosure is a _private_ structure */
+typedef void (* PyClosureExceptionHandler) (GValue *ret, guint n_param_values, const GValue *params);
+typedef struct _PyGClosure PyGClosure;
+typedef struct _PyGObjectData PyGObjectData;
+
+struct _PyGClosure {
+    GClosure closure;
+    PyObject *callback;
+    PyObject *extra_args; /* tuple of extra args to pass to callback */
+    PyObject *swap_data; /* other object for gtk_signal_connect__object */
+    PyClosureExceptionHandler exception_handler;
+};
+
+typedef enum {
+    PYGOBJECT_USING_TOGGLE_REF = 1 << 0,
+    PYGOBJECT_IS_FLOATING_REF = 1 << 1,
+    PYGOBJECT_GOBJECT_WAS_FLOATING = 1 << 2
+} PyGObjectFlags;
+
+  /* closures is just an alias for what is found in the
+   * PyGObjectData */
+typedef struct {
+    PyObject_HEAD
+    GObject *obj;
+    PyObject *inst_dict; /* the instance dictionary -- must be last */
+    PyObject *weakreflist; /* list of weak references */
+    
+      /*< private >*/
+      /* using union to preserve ABI compatibility (structure size
+       * must not change) */
+    union {
+        GSList *closures; /* stale field; no longer updated DO-NOT-USE! */
+        PyGObjectFlags flags;
+    } private_flags;
+
+} PyGObject;
+
+#define pygobject_get(v) (((PyGObject *)(v))->obj)
+#define pygobject_check(v,base) (PyObject_TypeCheck(v,base))
+
+typedef struct {
+    PyObject_HEAD
+    gpointer boxed;
+    GType gtype;
+    gboolean free_on_dealloc;
+} PyGBoxed;
+
+#define pyg_boxed_get(v,t)      ((t *)((PyGBoxed *)(v))->boxed)
+#define pyg_boxed_get_ptr(v)    (((PyGBoxed *)(v))->boxed)
+#define pyg_boxed_set_ptr(v,p)  (((PyGBoxed *)(v))->boxed = (gpointer)p)
+#define pyg_boxed_check(v,typecode) (PyObject_TypeCheck(v, &PyGBoxed_Type) && ((PyGBoxed *)(v))->gtype == typecode)
+
+typedef struct {
+    PyObject_HEAD
+    gpointer pointer;
+    GType gtype;
+} PyGPointer;
+
+#define pyg_pointer_get(v,t)      ((t *)((PyGPointer *)(v))->pointer)
+#define pyg_pointer_get_ptr(v)    (((PyGPointer *)(v))->pointer)
+#define pyg_pointer_set_ptr(v,p)  (((PyGPointer *)(v))->pointer = (gpointer)p)
+#define pyg_pointer_check(v,typecode) (PyObject_TypeCheck(v, &PyGPointer_Type) && ((PyGPointer *)(v))->gtype == typecode)
+
+typedef void (*PyGFatalExceptionFunc) (void);
+typedef void (*PyGThreadBlockFunc) (void);
+
+typedef struct {
+    PyObject_HEAD
+    GParamSpec *pspec;
+} PyGParamSpec;
+
+#define pyg_param_spec_get(v)    (((PyGParamSpec *)v)->pspec)
+#define pyg_param_spec_set(v,p)  (((PyGParamSpec *)v)->pspec = (GParamSpec*)p)
+#define pyg_param_spec_check(v)  (PyObject_TypeCheck(v, &PyGParamSpec_Type))
+
+/* Deprecated in favor of lower case with underscore macros above. */
+#define PyGParamSpec_Get    pyg_param_spec_get
+#define PyGParamSpec_Check  pyg_param_spec_check
+
+typedef int (*PyGClassInitFunc) (gpointer gclass, PyTypeObject *pyclass);
+typedef PyTypeObject * (*PyGTypeRegistrationFunction) (const gchar *name,
+                                                      gpointer data);
+
+struct _PyGObject_Functions {
+    /* 
+     * All field names in here are considered private,
+     * use the macros below instead, which provides stability
+     */
+    void (* register_class)(PyObject *dict, const gchar *class_name,
+                           GType gtype, PyTypeObject *type, PyObject *bases);
+    void (* register_wrapper)(PyObject *self);
+    PyTypeObject *(* lookup_class)(GType type);
+    PyObject *(* newgobj)(GObject *obj);
+
+    GClosure *(* closure_new)(PyObject *callback, PyObject *extra_args,
+                             PyObject *swap_data);
+    void      (* object_watch_closure)(PyObject *self, GClosure *closure);
+    GDestroyNotify destroy_notify;
+
+    GType (* type_from_object)(PyObject *obj);
+    PyObject *(* type_wrapper_new)(GType type);
+
+    gint (* enum_get_value)(GType enum_type, PyObject *obj, gint *val);
+    gint (* flags_get_value)(GType flag_type, PyObject *obj, guint *val);
+    void (* register_gtype_custom)(GType gtype,
+                           PyObject *(* from_func)(const GValue *value),
+                           int (* to_func)(GValue *value, PyObject *obj));
+    int (* value_from_pyobject)(GValue *value, PyObject *obj);
+    PyObject *(* value_as_pyobject)(const GValue *value, gboolean copy_boxed);
+
+    void (* register_interface)(PyObject *dict, const gchar *class_name,
+                               GType gtype, PyTypeObject *type);
+
+    PyTypeObject *boxed_type;
+    void (* register_boxed)(PyObject *dict, const gchar *class_name,
+                           GType boxed_type, PyTypeObject *type);
+    PyObject *(* boxed_new)(GType boxed_type, gpointer boxed,
+                           gboolean copy_boxed, gboolean own_ref);
+
+    PyTypeObject *pointer_type;
+    void (* register_pointer)(PyObject *dict, const gchar *class_name,
+                             GType pointer_type, PyTypeObject *type);
+    PyObject *(* pointer_new)(GType boxed_type, gpointer pointer);
+
+    void (* enum_add_constants)(PyObject *module, GType enum_type,
+                               const gchar *strip_prefix);
+    void (* flags_add_constants)(PyObject *module, GType flags_type,
+                                const gchar *strip_prefix);
+
+    const gchar *(* constant_strip_prefix)(const gchar *name,
+                                    const gchar *strip_prefix);
+
+    gboolean (* error_check)(GError **error);
+
+    /* hooks to register handlers for getting GDK threads to cooperate
+     * with python threading */
+    void (* set_thread_block_funcs) (PyGThreadBlockFunc block_threads_func,
+                                    PyGThreadBlockFunc unblock_threads_func);
+    PyGThreadBlockFunc block_threads;
+    PyGThreadBlockFunc unblock_threads;
+
+    PyTypeObject *paramspec_type;
+    PyObject *(* paramspec_new)(GParamSpec *spec);
+    GParamSpec *(*paramspec_get)(PyObject *tuple);
+    int (*pyobj_to_unichar_conv)(PyObject *pyobj, void* ptr);
+    gboolean (*parse_constructor_args)(GType        obj_type,
+                                       char       **arg_names,
+                                       char       **prop_names,
+                                       GParameter  *params,
+                                       guint       *nparams,
+                                       PyObject   **py_args);
+    PyObject *(* param_gvalue_as_pyobject) (const GValue* gvalue, 
+                                            gboolean copy_boxed,
+                                           const GParamSpec* pspec);
+    int (* gvalue_from_param_pyobject) (GValue* value, 
+                                        PyObject* py_obj, 
+                                       const GParamSpec* pspec);
+    PyTypeObject *enum_type;
+    PyObject *(*enum_add)(PyObject *module,
+                         const char *type_name_,
+                         const char *strip_prefix,
+                         GType gtype);
+    PyObject* (*enum_from_gtype)(GType gtype, int value);
+    
+    PyTypeObject *flags_type;
+    PyObject *(*flags_add)(PyObject *module,
+                          const char *type_name_,
+                          const char *strip_prefix,
+                          GType gtype);
+    PyObject* (*flags_from_gtype)(GType gtype, guint value);
+
+    gboolean threads_enabled;
+    int       (*enable_threads) (void);
+
+    int       (*gil_state_ensure) (void);
+    void      (*gil_state_release) (int flag);
+    
+    void      (*register_class_init) (GType gtype, PyGClassInitFunc class_init);
+    void      (*register_interface_info) (GType gtype, const GInterfaceInfo *info);
+    void      (*closure_set_exception_handler) (GClosure *closure, PyClosureExceptionHandler handler);
+
+    void      (*add_warning_redirection) (const char *domain,
+                                          PyObject   *warning);
+    void      (*disable_warning_redirections) (void);
+
+    /* type_register_custom API now removed, but leave a pointer here to not
+     * break ABI. */
+    void      *_type_register_custom;
+
+    gboolean  (*gerror_exception_check) (GError **error);
+    PyObject* (*option_group_new) (GOptionGroup *group);
+    GType (* type_from_object_strict) (PyObject *obj, gboolean strict);
+
+    PyObject *(* newgobj_full)(GObject *obj, gboolean steal, gpointer g_class);
+    PyTypeObject *object_type;
+    int (* value_from_pyobject_with_error)(GValue *value, PyObject *obj);
+};
+
+
+/* Deprecated, only available for API compatibility. */
+#define pyg_threads_enabled           TRUE
+#define pyg_gil_state_ensure          PyGILState_Ensure
+#define pyg_gil_state_release         PyGILState_Release
+#define pyg_begin_allow_threads       Py_BEGIN_ALLOW_THREADS
+#define pyg_end_allow_threads         Py_END_ALLOW_THREADS
+#define pyg_enable_threads()
+#define pyg_set_thread_block_funcs(a, b)
+#define pyg_block_threads()
+#define pyg_unblock_threads()
+
+
+#ifndef _INSIDE_PYGOBJECT_
+
+#if defined(NO_IMPORT) || defined(NO_IMPORT_PYGOBJECT)
+extern struct _PyGObject_Functions *_PyGObject_API;
+#else
+struct _PyGObject_Functions *_PyGObject_API;
+#endif
+
+#define pygobject_register_class    (_PyGObject_API->register_class)
+#define pygobject_register_wrapper  (_PyGObject_API->register_wrapper)
+#define pygobject_lookup_class      (_PyGObject_API->lookup_class)
+#define pygobject_new               (_PyGObject_API->newgobj)
+#define pygobject_new_full          (_PyGObject_API->newgobj_full)
+#define PyGObject_Type              (*_PyGObject_API->object_type)
+#define pyg_closure_new             (_PyGObject_API->closure_new)
+#define pygobject_watch_closure     (_PyGObject_API->object_watch_closure)
+#define pyg_closure_set_exception_handler (_PyGObject_API->closure_set_exception_handler)
+#define pyg_destroy_notify          (_PyGObject_API->destroy_notify)
+#define pyg_type_from_object_strict   (_PyGObject_API->type_from_object_strict)
+#define pyg_type_from_object        (_PyGObject_API->type_from_object)
+#define pyg_type_wrapper_new        (_PyGObject_API->type_wrapper_new)
+#define pyg_enum_get_value          (_PyGObject_API->enum_get_value)
+#define pyg_flags_get_value         (_PyGObject_API->flags_get_value)
+#define pyg_register_gtype_custom   (_PyGObject_API->register_gtype_custom)
+#define pyg_value_from_pyobject     (_PyGObject_API->value_from_pyobject)
+#define pyg_value_from_pyobject_with_error (_PyGObject_API->value_from_pyobject_with_error)
+#define pyg_value_as_pyobject       (_PyGObject_API->value_as_pyobject)
+#define pyg_register_interface      (_PyGObject_API->register_interface)
+#define PyGBoxed_Type               (*_PyGObject_API->boxed_type)
+#define pyg_register_boxed          (_PyGObject_API->register_boxed)
+#define pyg_boxed_new               (_PyGObject_API->boxed_new)
+#define PyGPointer_Type             (*_PyGObject_API->pointer_type)
+#define pyg_register_pointer        (_PyGObject_API->register_pointer)
+#define pyg_pointer_new             (_PyGObject_API->pointer_new)
+#define pyg_enum_add_constants      (_PyGObject_API->enum_add_constants)
+#define pyg_flags_add_constants     (_PyGObject_API->flags_add_constants)
+#define pyg_constant_strip_prefix   (_PyGObject_API->constant_strip_prefix)
+#define pyg_error_check             (_PyGObject_API->error_check)
+#define PyGParamSpec_Type           (*_PyGObject_API->paramspec_type)
+#define pyg_param_spec_new          (_PyGObject_API->paramspec_new)
+#define pyg_param_spec_from_object  (_PyGObject_API->paramspec_get)
+#define pyg_pyobj_to_unichar_conv   (_PyGObject_API->pyobj_to_unichar_conv)
+#define pyg_parse_constructor_args  (_PyGObject_API->parse_constructor_args)
+#define pyg_param_gvalue_as_pyobject   (_PyGObject_API->value_as_pyobject)
+#define pyg_param_gvalue_from_pyobject (_PyGObject_API->gvalue_from_param_pyobject)
+#define PyGEnum_Type                (*_PyGObject_API->enum_type)
+#define pyg_enum_add                (_PyGObject_API->enum_add)
+#define pyg_enum_from_gtype         (_PyGObject_API->enum_from_gtype)
+#define PyGFlags_Type               (*_PyGObject_API->flags_type)
+#define pyg_flags_add               (_PyGObject_API->flags_add)
+#define pyg_flags_from_gtype        (_PyGObject_API->flags_from_gtype)
+#define pyg_register_class_init     (_PyGObject_API->register_class_init)
+#define pyg_register_interface_info (_PyGObject_API->register_interface_info)
+#define pyg_add_warning_redirection   (_PyGObject_API->add_warning_redirection)
+#define pyg_disable_warning_redirections (_PyGObject_API->disable_warning_redirections)
+#define pyg_gerror_exception_check (_PyGObject_API->gerror_exception_check)
+#define pyg_option_group_new       (_PyGObject_API->option_group_new)
+
+
+/**
+ * pygobject_init:
+ * @req_major: minimum version major number, or -1
+ * @req_minor: minimum version minor number, or -1
+ * @req_micro: minimum version micro number, or -1
+ * 
+ * Imports and initializes the 'gobject' python module.  Can
+ * optionally check for a required minimum version if @req_major,
+ * @req_minor, and @req_micro are all different from -1.
+ * 
+ * Returns: a new reference to the gobject module on success, NULL in
+ * case of failure (and raises ImportError).
+ **/
+static inline PyObject *
+pygobject_init(int req_major, int req_minor, int req_micro)
+{
+    PyObject *gobject, *cobject;
+    
+    gobject = PyImport_ImportModule("gi._gobject");
+    if (!gobject) {
+        if (PyErr_Occurred())
+        {
+            PyObject *type, *value, *traceback;
+            PyObject *py_orig_exc;
+            PyErr_Fetch(&type, &value, &traceback);
+            py_orig_exc = PyObject_Repr(value);
+            Py_XDECREF(type);
+            Py_XDECREF(value);
+            Py_XDECREF(traceback);
+
+
+#if PY_VERSION_HEX < 0x03000000
+            PyErr_Format(PyExc_ImportError,
+                         "could not import gobject (error was: %s)",
+                         PyString_AsString(py_orig_exc));
+#else
+            {
+                /* Can not use PyErr_Format because it doesn't have
+                 * a format string for dealing with PyUnicode objects
+                 * like PyUnicode_FromFormat has
+                 */
+                PyObject *errmsg = PyUnicode_FromFormat("could not import gobject (error was: %U)",
+                                                        py_orig_exc);
+
+                if (errmsg) {
+                   PyErr_SetObject(PyExc_ImportError,
+                                   errmsg);
+                   Py_DECREF(errmsg);
+                }
+                /* if errmsg is NULL then we might have OOM
+                 * PyErr should already be set and trying to
+                 * return our own error would be futile
+                 */
+            }
+#endif
+            Py_DECREF(py_orig_exc);
+        } else {
+            PyErr_SetString(PyExc_ImportError,
+                            "could not import gobject (no error given)");
+        }
+        return NULL;
+    }
+
+    cobject = PyObject_GetAttrString(gobject, "_PyGObject_API");
+    if (cobject && PyCapsule_CheckExact(cobject)) {
+        _PyGObject_API = (struct _PyGObject_Functions *) PyCapsule_GetPointer(cobject, "gobject._PyGObject_API");
+        Py_DECREF (cobject);
+    } else {
+        PyErr_SetString(PyExc_ImportError,
+                        "could not import gobject (could not find _PyGObject_API object)");
+        Py_XDECREF (cobject);
+        Py_DECREF(gobject);
+        return NULL;
+    }
+
+    if (req_major != -1)
+    {
+        int found_major, found_minor, found_micro;
+        PyObject *version;
+
+        version = PyObject_GetAttrString(gobject, "pygobject_version");
+        if (!version) {
+            PyErr_SetString(PyExc_ImportError,
+                            "could not import gobject (version too old)");
+            Py_DECREF(gobject);
+            return NULL;
+        }
+        if (!PyArg_ParseTuple(version, "iii",
+                              &found_major, &found_minor, &found_micro)) {
+            PyErr_SetString(PyExc_ImportError,
+                            "could not import gobject (version has invalid format)");
+            Py_DECREF(version);
+            Py_DECREF(gobject);
+            return NULL;
+        }
+        Py_DECREF(version);
+        if (req_major != found_major ||
+            req_minor >  found_minor ||
+            (req_minor == found_minor && req_micro > found_micro)) {
+            PyErr_Format(PyExc_ImportError,
+                         "could not import gobject (version mismatch, %d.%d.%d is required, "
+                         "found %d.%d.%d)", req_major, req_minor, req_micro,
+                         found_major, found_minor, found_micro);
+            Py_DECREF(gobject);
+            return NULL;
+        }
+    }
+    return gobject;
+}
+
+/**
+ * PYLIST_FROMGLIBLIST:
+ * @type: the type of the GLib list e.g. #GList or #GSList
+ * @prefix: the prefix of functions that manipulate a list of the type
+ * given by type.
+ *
+ * A macro that creates a type specific code block which converts a GLib
+ * list (#GSList or #GList) to a Python list. The first two args of the macro
+ * are used to specify the type and list function prefix so that the type
+ * specific macros can be generated.
+ *
+ * The rest of the args are for the standard args for the type specific
+ * macro(s) created from this macro.
+ */
+ #define PYLIST_FROMGLIBLIST(type,prefix,py_list,list,item_convert_func,\
+                            list_free,list_item_free)  \
+G_STMT_START \
+{ \
+    gint i, len; \
+    PyObject *item; \
+    void (*glib_list_free)(type*) = list_free; \
+    GFunc glib_list_item_free = (GFunc)list_item_free;  \
+ \
+    len = prefix##_length(list); \
+    py_list = PyList_New(len); \
+    for (i = 0; i < len; i++) { \
+        gpointer list_item = prefix##_nth_data(list, i); \
+ \
+        item = item_convert_func; \
+        PyList_SetItem(py_list, i, item); \
+    } \
+    if (glib_list_item_free != NULL) \
+        prefix##_foreach(list, glib_list_item_free, NULL); \
+    if (glib_list_free != NULL) \
+        glib_list_free(list); \
+} G_STMT_END
+
+/**
+ * PYLIST_FROMGLIST:
+ * @py_list: the name of the Python list
+ *
+ * @list: the #GList to be converted to a Python list
+ *
+ * @item_convert_func: the function that converts a list item to a Python
+ * object. The function must refer to the list item using "@list_item" and
+ * must return a #PyObject* object. An example conversion function is:
+ * [[
+ * PyString_FromString(list_item)
+ * ]]
+ * A more elaborate function is:
+ * [[
+ * pyg_boxed_new(GTK_TYPE_RECENT_INFO, list_item, TRUE, TRUE)
+ * ]]
+ * @list_free: the name of a function that takes a single arg (the list) and
+ * frees its memory. Can be NULL if the list should not be freed. An example
+ * is:
+ * [[
+ * g_list_free
+ * ]]
+ * @list_item_free: the name of a #GFunc function that frees the memory used
+ * by the items in the list or %NULL if the list items do not have to be
+ * freed. A simple example is:
+ * [[
+ * g_free
+ * ]]
+ *
+ * A macro that adds code that converts a #GList to a Python list.
+ *
+ */
+#define PYLIST_FROMGLIST(py_list,list,item_convert_func,list_free,\
+                         list_item_free) \
+        PYLIST_FROMGLIBLIST(GList,g_list,py_list,list,item_convert_func,\
+                            list_free,list_item_free)
+
+/**
+ * PYLIST_FROMGSLIST:
+ * @py_list: the name of the Python list
+ *
+ * @list: the #GSList to be converted to a Python list
+ *
+ * @item_convert_func: the function that converts a list item to a Python
+ * object. The function must refer to the list item using "@list_item" and
+ * must return a #PyObject* object. An example conversion function is:
+ * [[
+ * PyString_FromString(list_item)
+ * ]]
+ * A more elaborate function is:
+ * [[
+ * pyg_boxed_new(GTK_TYPE_RECENT_INFO, list_item, TRUE, TRUE)
+ * ]]
+ * @list_free: the name of a function that takes a single arg (the list) and
+ * frees its memory. Can be %NULL if the list should not be freed. An example
+ * is:
+ * [[
+ * g_list_free
+ * ]]
+ * @list_item_free: the name of a #GFunc function that frees the memory used
+ * by the items in the list or %NULL if the list items do not have to be
+ * freed. A simple example is:
+ * [[
+ * g_free
+ * ]]
+ *
+ * A macro that adds code that converts a #GSList to a Python list.
+ *
+ */
+#define PYLIST_FROMGSLIST(py_list,list,item_convert_func,list_free,\
+                          list_item_free) \
+        PYLIST_FROMGLIBLIST(GSList,g_slist,py_list,list,item_convert_func,\
+                            list_free,list_item_free)
+
+/**
+ * PYLIST_ASGLIBLIST
+ * @type: the type of the GLib list e.g. GList or GSList
+ * @prefix: the prefix of functions that manipulate a list of the type
+ * given by type e.g. g_list or g_slist
+ *
+ * A macro that creates a type specific code block to be used to convert a
+ * Python list to a GLib list (GList or GSList). The first two args of the
+ * macro are used to specify the type and list function prefix so that the
+ * type specific macros can be generated.
+ *
+ * The rest of the args are for the standard args for the type specific
+ * macro(s) created from this macro.
+ */
+#define PYLIST_ASGLIBLIST(type,prefix,py_list,list,check_func,\
+                           convert_func,child_free_func,errormsg,errorreturn) \
+G_STMT_START \
+{ \
+    Py_ssize_t i, n_list; \
+    GFunc glib_child_free_func = (GFunc)child_free_func;        \
+ \
+    if (!(py_list = PySequence_Fast(py_list, ""))) { \
+        errormsg; \
+        return errorreturn; \
+    } \
+    n_list = PySequence_Fast_GET_SIZE(py_list); \
+    for (i = 0; i < n_list; i++) { \
+        PyObject *py_item = PySequence_Fast_GET_ITEM(py_list, i); \
+ \
+        if (!check_func) { \
+            if (glib_child_free_func) \
+                    prefix##_foreach(list, glib_child_free_func, NULL); \
+            prefix##_free(list); \
+            Py_DECREF(py_list); \
+            errormsg; \
+            return errorreturn; \
+        } \
+        list = prefix##_prepend(list, convert_func); \
+    }; \
+        Py_DECREF(py_list); \
+        list =  prefix##_reverse(list); \
+} \
+G_STMT_END
+/**
+ * PYLIST_ASGLIST
+ * @py_list: the Python list to be converted
+ * @list: the #GList list to be converted
+ * @check_func: the expression that takes a #PyObject* arg (must be named
+ * @py_item) and returns an int value indicating if the Python object matches
+ * the required list item type (0 - %False or 1 - %True). An example is:
+ * [[
+ * (PyString_Check(py_item)||PyUnicode_Check(py_item))
+ * ]]
+ * @convert_func: the function that takes a #PyObject* arg (must be named
+ * py_item) and returns a pointer to the converted list object. An example
+ * is:
+ * [[
+ * pygobject_get(py_item)
+ * ]]
+ * @child_free_func: the name of a #GFunc function that frees a GLib list
+ * item or %NULL if the list item does not have to be freed. This function is
+ * used to help free the items in a partially created list if there is an
+ * error. An example is:
+ * [[
+ * g_free
+ * ]]
+ * @errormsg: a function that sets up a Python error message. An example is:
+ * [[
+ * PyErr_SetString(PyExc_TypeError, "strings must be a sequence of" "strings
+ * or unicode objects")
+ * ]]
+ * @errorreturn: the value to return if an error occurs, e.g.:
+ * [[
+ * %NULL
+ * ]]
+ *
+ * A macro that creates code that converts a Python list to a #GList. The
+ * returned list must be freed using the appropriate list free function when
+ * it's no longer needed. If an error occurs the child_free_func is used to
+ * release the memory used by the list items and then the list memory is
+ * freed.
+ */
+#define PYLIST_ASGLIST(py_list,list,check_func,convert_func,child_free_func,\
+                       errormsg,errorreturn) \
+        PYLIST_ASGLIBLIST(GList,g_list,py_list,list,check_func,convert_func,\
+                          child_free_func,errormsg,errorreturn)
+
+/**
+ * PYLIST_ASGSLIST
+ * @py_list: the Python list to be converted
+ * @list: the #GSList list to be converted
+ * @check_func: the expression that takes a #PyObject* arg (must be named
+ * @py_item) and returns an int value indicating if the Python object matches
+ * the required list item type (0 - %False or 1 - %True). An example is:
+ * [[
+ * (PyString_Check(py_item)||PyUnicode_Check(py_item))
+ * ]]
+ * @convert_func: the function that takes a #PyObject* arg (must be named
+ * py_item) and returns a pointer to the converted list object. An example
+ * is:
+ * [[
+ * pygobject_get(py_item)
+ * ]]
+ * @child_free_func: the name of a #GFunc function that frees a GLib list
+ * item or %NULL if the list item does not have to be freed. This function is
+ * used to help free the items in a partially created list if there is an
+ * error. An example is:
+ * [[
+ * g_free
+ * ]]
+ * @errormsg: a function that sets up a Python error message. An example is:
+ * [[
+ * PyErr_SetString(PyExc_TypeError, "strings must be a sequence of" "strings
+ * or unicode objects")
+ * ]]
+ * @errorreturn: the value to return if an error occurs, e.g.:
+ * [[
+ * %NULL
+ * ]]
+ *
+ * A macro that creates code that converts a Python list to a #GSList. The
+ * returned list must be freed using the appropriate list free function when
+ * it's no longer needed. If an error occurs the child_free_func is used to
+ * release the memory used by the list items and then the list memory is
+ * freed.
+ */
+#define PYLIST_ASGSLIST(py_list,list,check_func,convert_func,child_free_func,\
+                        errormsg,errorreturn) \
+        PYLIST_ASGLIBLIST(GSList,g_slist,py_list,list,check_func,convert_func,\
+                          child_free_func,errormsg,errorreturn)
+
+#endif /* !_INSIDE_PYGOBJECT_ */
+
+G_END_DECLS
+
+#endif /* !_PYGOBJECT_H_ */
diff --git a/gi/pygoptioncontext.c b/gi/pygoptioncontext.c
new file mode 100644 (file)
index 0000000..61ecb76
--- /dev/null
@@ -0,0 +1,374 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 2006  Johannes Hoelzl
+ *
+ *   pygoptioncontext.c: GOptionContext wrapper
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "pygoptioncontext.h"
+#include "pygi-error.h"
+#include "pygi-util.h"
+#include "pygi-basictype.h"
+
+PYGLIB_DEFINE_TYPE("gi._gi.OptionContext", PyGOptionContext_Type, PyGOptionContext)
+
+/**
+ * pyg_option_group_transfer_group:
+ * @group: a GOptionGroup wrapper
+ *
+ * This is used to transfer the GOptionGroup to a GOptionContext. After this
+ * is called, the calle must handle the release of the GOptionGroup.
+ *
+ * When #NULL is returned, the GOptionGroup was already transfered.
+ *
+ * Returns: Either #NULL or the wrapped GOptionGroup.
+ */
+static GOptionGroup *
+pyglib_option_group_transfer_group(PyObject *obj)
+{
+    PyGOptionGroup *self = (PyGOptionGroup*)obj;
+
+    if (self->is_in_context)
+       return NULL;
+
+    self->is_in_context = TRUE;
+    
+    /* Here we increase the reference count of the PyGOptionGroup, because now
+     * the GOptionContext holds an reference to us (it is the userdata passed
+     * to g_option_group_new().
+     *
+     * The GOptionGroup is freed with the GOptionContext.
+     *
+     * We set it here because if we would do this in the init method we would
+     * hold two references and the PyGOptionGroup would never be freed.
+     */
+    Py_INCREF(self);
+
+    return self->group;
+}
+
+/**
+ * pyg_option_context_new:
+ * @context: a GOptionContext
+ *
+ * Returns: A new GOptionContext wrapper.
+ */
+PyObject *
+pyg_option_context_new (GOptionContext *context)
+{
+    PyGOptionContext *self;
+
+    self = (PyGOptionContext *)PyObject_NEW(PyGOptionContext, &PyGOptionContext_Type);
+    if (self == NULL)
+        return NULL;
+
+    self->context = context;
+    self->main_group = NULL;
+
+    return (PyObject *)self;
+}
+
+static int
+pyg_option_context_init(PyGOptionContext *self,
+                        PyObject *args,
+                        PyObject *kwargs)
+{
+    char *parameter_string;
+
+    if (!PyArg_ParseTuple(args, "s:gi._gi.GOptionContext.__init__",
+                          &parameter_string))
+        return -1;
+
+    self->context = g_option_context_new(parameter_string);
+    return 0;
+}
+
+static void
+pyg_option_context_dealloc(PyGOptionContext *self)
+{
+    Py_CLEAR(self->main_group);
+
+    if (self->context != NULL)
+    {
+        GOptionContext *tmp = self->context;
+        self->context = NULL;
+        g_option_context_free(tmp);
+    }
+
+    PyObject_Del(self);
+}
+
+static PyObject *
+pyg_option_context_parse(PyGOptionContext *self,
+                         PyObject *args,
+                         PyObject *kwargs)
+{
+    static char *kwlist[] = { "argv", NULL };
+    PyObject *arg;
+    PyObject *new_argv, *argv;
+    Py_ssize_t argv_length, pos;
+    gint argv_length_int;
+    char **argv_content, **original;
+    GError *error = NULL;
+    gboolean result;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:GOptionContext.parse",
+                                     kwlist, &argv))
+        return NULL;
+
+    if (!PyList_Check(argv))
+    {
+        PyErr_SetString(PyExc_TypeError,
+                        "GOptionContext.parse expects a list of strings.");
+        return NULL;
+    }
+
+    argv_length = PyList_Size(argv);
+    if (argv_length == -1)
+    {
+        PyErr_SetString(PyExc_TypeError,
+                        "GOptionContext.parse expects a list of strings.");
+        return NULL;
+    }
+
+    argv_content = g_new(char*, argv_length + 1);
+    argv_content[argv_length] = NULL;
+    for (pos = 0; pos < argv_length; pos++)
+    {
+        arg = PyList_GetItem(argv, pos);
+        argv_content[pos] = g_strdup(PYGLIB_PyUnicode_AsString(arg));
+        if (argv_content[pos] == NULL)
+        {
+            g_strfreev(argv_content);
+            return NULL;
+        }
+    }
+    original = g_strdupv(argv_content);
+
+    g_assert(argv_length <= G_MAXINT);
+    argv_length_int = (gint)argv_length;
+    Py_BEGIN_ALLOW_THREADS;
+    result = g_option_context_parse(self->context, &argv_length_int, &argv_content,
+                                    &error);
+    Py_END_ALLOW_THREADS;
+    argv_length = argv_length_int;
+
+    if (!result)
+    {
+        g_strfreev(argv_content);
+        g_strfreev(original);
+        pygi_error_check(&error);
+        return NULL;
+    }
+
+    new_argv = PyList_New(g_strv_length(argv_content));
+    for (pos = 0; pos < argv_length; pos++)
+    {
+        arg = PYGLIB_PyUnicode_FromString(argv_content[pos]);
+        PyList_SetItem(new_argv, pos, arg);
+    }
+
+    g_strfreev(original);
+    g_strfreev(argv_content);
+    return new_argv;
+}
+
+static PyObject *
+pyg_option_context_set_help_enabled(PyGOptionContext *self,
+                                    PyObject *args,
+                                    PyObject *kwargs)
+{
+    static char *kwlist[] = { "help_enable", NULL };
+
+    PyObject *help_enabled;
+    if (! PyArg_ParseTupleAndKeywords(args, kwargs,
+                                      "O:GOptionContext.set_help_enabled",
+                                      kwlist, &help_enabled))
+        return NULL;
+
+    g_option_context_set_help_enabled(self->context, PyObject_IsTrue(help_enabled));
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+pyg_option_context_get_help_enabled(PyGOptionContext *self)
+{
+    return pygi_gboolean_to_py (g_option_context_get_help_enabled(self->context));
+}
+
+static PyObject *
+pyg_option_context_set_ignore_unknown_options(PyGOptionContext *self,
+                                              PyObject *args,
+                                              PyObject *kwargs)
+{
+    static char *kwlist[] = { "ignore_unknown_options", NULL };
+    PyObject *ignore_unknown_options;
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwargs,
+                                "O:GOptionContext.set_ignore_unknown_options",
+                                kwlist, &ignore_unknown_options))
+        return NULL;
+
+    g_option_context_set_ignore_unknown_options(self->context,
+                                               PyObject_IsTrue(ignore_unknown_options));
+    
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+pyg_option_context_get_ignore_unknown_options(PyGOptionContext *self)
+{
+    return pygi_gboolean_to_py (
+        g_option_context_get_ignore_unknown_options(self->context));
+}
+
+static PyObject *
+pyg_option_context_set_main_group(PyGOptionContext *self,
+                                  PyObject *args,
+                                  PyObject *kwargs)
+{
+    static char *kwlist[] = { "group", NULL };
+    GOptionGroup *g_group;
+    PyObject *group;
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwargs,
+                                      "O:GOptionContext.set_main_group",
+                                      kwlist, &group))
+        return NULL;
+
+    if (PyObject_IsInstance(group, (PyObject*) &PyGOptionGroup_Type) != 1) {
+        PyErr_SetString(PyExc_TypeError,
+                        "GOptionContext.set_main_group expects a GOptionGroup.");
+        return NULL;
+    }
+
+    g_group = pyglib_option_group_transfer_group(group);
+    if (g_group == NULL)
+    {
+        PyErr_SetString(PyExc_RuntimeError,
+                       "Group is already in a OptionContext.");
+        return NULL;
+    }
+
+    g_option_context_set_main_group(self->context, g_group);
+
+    Py_INCREF(group);
+    self->main_group = (PyGOptionGroup*) group;
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+pyg_option_context_get_main_group(PyGOptionContext *self)
+{
+    if (self->main_group == NULL)
+    {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+    Py_INCREF(self->main_group);
+    return (PyObject*) self->main_group;
+}
+
+static PyObject *
+pyg_option_context_add_group(PyGOptionContext *self,
+                             PyObject *args,
+                             PyObject *kwargs)
+{
+    static char *kwlist[] = { "group", NULL };
+    GOptionGroup *g_group;
+    PyObject *group;
+
+    if (! PyArg_ParseTupleAndKeywords(args, kwargs,
+                                      "O:GOptionContext.add_group",
+                                      kwlist, &group))
+        return NULL;
+
+    if (PyObject_IsInstance(group, (PyObject*) &PyGOptionGroup_Type) != 1) {
+        PyErr_SetString(PyExc_TypeError,
+                        "GOptionContext.add_group expects a GOptionGroup.");
+        return NULL;
+    }
+
+    g_group = pyglib_option_group_transfer_group(group);
+    if (g_group == NULL)
+    {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "Group is already in a OptionContext.");
+        return NULL;
+    }
+    Py_INCREF(group);
+
+    g_option_context_add_group(self->context, g_group);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject*
+pyg_option_context_richcompare(PyObject *self, PyObject *other, int op)
+{
+    if (Py_TYPE(self) == Py_TYPE(other) && Py_TYPE(self) == &PyGOptionContext_Type)
+        return pyg_ptr_richcompare(((PyGOptionContext*)self)->context,
+                                   ((PyGOptionContext*)other)->context,
+                                   op);
+    else {
+       Py_INCREF(Py_NotImplemented);
+       return Py_NotImplemented;
+    }
+}
+
+static PyObject *
+pyg_option_get_context(PyGOptionContext *self)
+{
+    return PyCapsule_New (self->context, "goption.context", NULL);
+}
+
+static PyMethodDef pyg_option_context_methods[] = {
+    { "parse", (PyCFunction)pyg_option_context_parse, METH_VARARGS | METH_KEYWORDS },
+    { "set_help_enabled", (PyCFunction)pyg_option_context_set_help_enabled, METH_VARARGS | METH_KEYWORDS },
+    { "get_help_enabled", (PyCFunction)pyg_option_context_get_help_enabled, METH_NOARGS },
+    { "set_ignore_unknown_options", (PyCFunction)pyg_option_context_set_ignore_unknown_options, METH_VARARGS | METH_KEYWORDS },
+    { "get_ignore_unknown_options", (PyCFunction)pyg_option_context_get_ignore_unknown_options, METH_NOARGS },
+    { "set_main_group", (PyCFunction)pyg_option_context_set_main_group, METH_VARARGS | METH_KEYWORDS },
+    { "get_main_group", (PyCFunction)pyg_option_context_get_main_group, METH_NOARGS },
+    { "add_group", (PyCFunction)pyg_option_context_add_group, METH_VARARGS | METH_KEYWORDS },
+    { "_get_context", (PyCFunction)pyg_option_get_context, METH_NOARGS },
+    { NULL, NULL, 0 },
+};
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_option_context_register_types(PyObject *d)
+{
+    PyGOptionContext_Type.tp_dealloc = (destructor)pyg_option_context_dealloc;
+    PyGOptionContext_Type.tp_richcompare = pyg_option_context_richcompare;
+    PyGOptionContext_Type.tp_flags = Py_TPFLAGS_DEFAULT;
+    PyGOptionContext_Type.tp_methods = pyg_option_context_methods;
+    PyGOptionContext_Type.tp_init = (initproc)pyg_option_context_init;
+    PYGLIB_REGISTER_TYPE(d, PyGOptionContext_Type, "OptionContext");
+
+    return 0;
+}
diff --git a/gi/pygoptioncontext.h b/gi/pygoptioncontext.h
new file mode 100644 (file)
index 0000000..4dea56d
--- /dev/null
@@ -0,0 +1,37 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pyglib - Python bindings for GLib toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYG_OPTIONCONTEXT_H__
+#define __PYG_OPTIONCONTEXT_H__
+
+#include "pygoptiongroup.h"
+
+extern PyTypeObject PyGOptionContext_Type;
+
+typedef struct {
+    PyObject_HEAD
+    PyGOptionGroup *main_group;
+    GOptionContext *context;
+} PyGOptionContext;
+
+PyObject* pyg_option_context_new(GOptionContext *context);
+
+int pygi_option_context_register_types(PyObject *d);
+
+#endif /* __PYG_OPTIONCONTEXT_H__ */
diff --git a/gi/pygoptiongroup.c b/gi/pygoptiongroup.c
new file mode 100644 (file)
index 0000000..f144d81
--- /dev/null
@@ -0,0 +1,301 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygobject - Python bindings for the GLib, GObject and GIO
+ * Copyright (C) 2006  Johannes Hoelzl
+ *
+ *   pygoptiongroup.c: GOptionContext and GOptionGroup wrapper
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "pygoptiongroup.h"
+#include "pygi-error.h"
+#include "pygi-util.h"
+
+PYGLIB_DEFINE_TYPE("gi._gi.OptionGroup", PyGOptionGroup_Type, PyGOptionGroup)
+
+/**
+ * pyg_option_group_new:
+ * @group: a GOptionGroup
+ *
+ * The returned GOptionGroup can't be used to set any hooks, translation domains
+ * or add entries. It's only intend is, to use for GOptionContext.add_group().
+ *
+ * Returns: the GOptionGroup wrapper.
+ */
+PyObject *
+pyg_option_group_new (GOptionGroup *group)
+{
+    PyGOptionGroup *self;
+
+    self = (PyGOptionGroup *)PyObject_NEW(PyGOptionGroup,
+                      &PyGOptionGroup_Type);
+    if (self == NULL)
+        return NULL;
+
+    self->group = group;
+    self->other_owner = TRUE;
+    self->is_in_context = FALSE;
+
+    return (PyObject *)self;
+}
+
+static gboolean
+check_if_owned(PyGOptionGroup *self)
+{
+    if (self->other_owner)
+    {
+        PyErr_SetString(PyExc_ValueError, "The GOptionGroup was not created by "
+                        "gi._gi.OptionGroup(), so operation is not possible.");
+        return TRUE;
+    }
+    return FALSE;
+}
+
+static void
+destroy_g_group(PyGOptionGroup *self)
+{
+    PyGILState_STATE state;
+    state = PyGILState_Ensure();
+
+    self->group = NULL;
+    Py_CLEAR(self->callback);
+    g_slist_foreach(self->strings, (GFunc) g_free, NULL);
+    g_slist_free(self->strings);
+    self->strings = NULL;
+
+    if (self->is_in_context)
+    {
+        Py_DECREF(self);
+    }
+
+    PyGILState_Release(state);
+}
+
+static int
+pyg_option_group_init(PyGOptionGroup *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { "name", "description", "help_description",
+                              "callback", NULL };
+    char *name, *description, *help_description;
+    PyObject *callback;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "zzzO:GOptionGroup.__init__",
+                                     kwlist, &name, &description,
+                                     &help_description, &callback))
+        return -1;
+
+    self->group = g_option_group_new(name, description, help_description,
+                                     self, (GDestroyNotify) destroy_g_group);
+    self->other_owner = FALSE;
+    self->is_in_context = FALSE;
+
+    Py_INCREF(callback);
+    self->callback = callback;
+
+    return 0;
+}
+
+static void
+pyg_option_group_dealloc(PyGOptionGroup *self)
+{
+    if (!self->other_owner && !self->is_in_context)
+    {
+        GOptionGroup *tmp = self->group;
+        self->group = NULL;
+       if (tmp) {
+           G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+           g_option_group_free(tmp);
+           G_GNUC_END_IGNORE_DEPRECATIONS
+       }
+    }
+
+    PyObject_Del(self);
+}
+
+static gboolean
+arg_func(const gchar *option_name,
+         const gchar *value,
+         PyGOptionGroup *self,
+         GError **error)
+{
+    PyObject *ret;
+    PyGILState_STATE state;
+    gboolean no_error;
+
+    state = PyGILState_Ensure();
+    if (value == NULL)
+        ret = PyObject_CallFunction(self->callback, "sOO",
+                                    option_name, Py_None, self);
+    else
+        ret = PyObject_CallFunction(self->callback, "ssO",
+                                    option_name, value, self);
+
+    if (ret != NULL)
+    {
+        Py_DECREF(ret);
+        no_error = TRUE;
+    } else
+       no_error = pygi_gerror_exception_check(error) != -1;
+
+    PyGILState_Release(state);
+    return no_error;
+}
+
+static PyObject *
+pyg_option_group_add_entries(PyGOptionGroup *self, PyObject *args,
+                             PyObject *kwargs)
+{
+    static char *kwlist[] = { "entries", NULL };
+    gssize entry_count, pos;
+    PyObject *entry_tuple, *list;
+    GOptionEntry *entries;
+
+    if (check_if_owned(self))
+       return NULL;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:GOptionGroup.add_entries",
+                                     kwlist, &list))
+        return NULL;
+
+    if (!PyList_Check(list))
+    {
+        PyErr_SetString(PyExc_TypeError,
+                        "GOptionGroup.add_entries expected a list of entries");
+        return NULL;
+    }
+
+    entry_count = PyList_Size(list);
+    if (entry_count == -1)
+    {
+        PyErr_SetString(PyExc_TypeError,
+                        "GOptionGroup.add_entries expected a list of entries");
+        return NULL;
+    }
+
+    entries = g_new0(GOptionEntry, entry_count + 1);
+    for (pos = 0; pos < entry_count; pos++)
+    {
+        gchar *long_name, *description, *arg_description;
+        entry_tuple = PyList_GetItem(list, pos);
+        if (!PyTuple_Check(entry_tuple))
+        {
+            PyErr_SetString(PyExc_TypeError, "GOptionGroup.add_entries "
+                                             "expected a list of entries");
+            g_free(entries);
+            return NULL;
+        }
+        if (!PyArg_ParseTuple(entry_tuple, "scisz",
+            &long_name,
+            &(entries[pos].short_name),
+            &(entries[pos].flags),
+            &description,
+            &arg_description))
+        {
+            PyErr_SetString(PyExc_TypeError, "GOptionGroup.add_entries "
+                                             "expected a list of entries");
+            g_free(entries);
+            return NULL;
+        }
+        long_name = g_strdup(long_name);
+        self->strings = g_slist_prepend(self->strings, long_name);
+        entries[pos].long_name = long_name;
+
+        description = g_strdup(description);
+        self->strings = g_slist_prepend(self->strings, description);
+        entries[pos].description = description;
+
+        arg_description = g_strdup(arg_description);
+        self->strings = g_slist_prepend(self->strings, arg_description);
+        entries[pos].arg_description = arg_description;
+
+        entries[pos].arg = G_OPTION_ARG_CALLBACK;
+        entries[pos].arg_data = arg_func;
+    }
+
+    g_option_group_add_entries(self->group, entries);
+
+    g_free(entries);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+
+static PyObject *
+pyg_option_group_set_translation_domain(PyGOptionGroup *self,
+                                        PyObject *args,
+                                        PyObject *kwargs)
+{
+    static char *kwlist[] = { "domain", NULL };
+    char *domain;
+
+    if (check_if_owned(self))
+       return NULL;
+
+    if (self->group == NULL)
+    {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "The corresponding GOptionGroup was already freed, "
+                        "probably through the release of GOptionContext");
+        return NULL;
+    }
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+                                     "z:GOptionGroup.set_translate_domain",
+                                     kwlist, &domain))
+        return NULL;
+
+    g_option_group_set_translation_domain(self->group, domain);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject*
+pyg_option_group_richcompare(PyObject *self, PyObject *other, int op)
+{
+    if (Py_TYPE(self) == Py_TYPE(other) && 
+          Py_TYPE(self) == &PyGOptionGroup_Type) {
+        return pyg_ptr_richcompare(((PyGOptionGroup*)self)->group,
+                                   ((PyGOptionGroup*)other)->group,
+                                   op);
+    } else {
+        Py_INCREF(Py_NotImplemented);
+        return Py_NotImplemented;
+    }
+}
+
+static PyMethodDef pyg_option_group_methods[] = {
+    { "add_entries", (PyCFunction)pyg_option_group_add_entries, METH_VARARGS | METH_KEYWORDS },
+    { "set_translation_domain", (PyCFunction)pyg_option_group_set_translation_domain, METH_VARARGS | METH_KEYWORDS },
+    { NULL, NULL, 0 },
+};
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_option_group_register_types(PyObject *d)
+{
+    PyGOptionGroup_Type.tp_dealloc = (destructor)pyg_option_group_dealloc;
+    PyGOptionGroup_Type.tp_richcompare = pyg_option_group_richcompare;
+    PyGOptionGroup_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+    PyGOptionGroup_Type.tp_methods = pyg_option_group_methods;
+    PyGOptionGroup_Type.tp_init = (initproc)pyg_option_group_init;
+    PYGLIB_REGISTER_TYPE(d, PyGOptionGroup_Type, "OptionGroup");
+
+    return 0;
+}
diff --git a/gi/pygoptiongroup.h b/gi/pygoptiongroup.h
new file mode 100644 (file)
index 0000000..65d08e4
--- /dev/null
@@ -0,0 +1,43 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pyglib - Python bindings for GLib toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYG_OPTIONGROUP_H__
+#define __PYG_OPTIONGROUP_H__
+
+#include <Python.h>
+#include <glib.h>
+
+extern PyTypeObject PyGOptionGroup_Type;
+
+typedef struct {
+    PyObject_HEAD
+    GOptionGroup *group;
+    gboolean other_owner, is_in_context;
+    PyObject *callback;
+    GSList *strings; /* all strings added with the entries, are freed on 
+                        GOptionGroup.destroy() */
+} PyGOptionGroup;
+
+PyObject* pyg_option_group_new(GOptionGroup *group);
+
+int pygi_option_group_register_types(PyObject *d);
+
+#endif /* __PYG_OPTIONGROUP_H__ */
+
+
diff --git a/gi/pygparamspec.c b/gi/pygparamspec.c
new file mode 100644 (file)
index 0000000..2c59181
--- /dev/null
@@ -0,0 +1,423 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ * Copyright (C) 2004       Johan Dahlin
+ *
+ *   pygenum.c: GEnum and GFlag wrappers
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <Python.h>
+#include <glib-object.h>
+
+#include "pygenum.h"
+#include "pygflags.h"
+#include "pygi-type.h"
+#include "pygparamspec.h"
+#include "pygi-util.h"
+#include "pygi-basictype.h"
+
+PYGLIB_DEFINE_TYPE("gobject.GParamSpec", PyGParamSpec_Type, PyGParamSpec);
+
+static PyObject*
+pyg_param_spec_richcompare(PyObject *self, PyObject *other, int op)
+{
+    if (Py_TYPE(self) == Py_TYPE(other) && Py_TYPE(self) == &PyGParamSpec_Type)
+        return pyg_ptr_richcompare (pyg_param_spec_get (self),
+                                    pyg_param_spec_get (other),
+                                    op);
+    else {
+        Py_INCREF(Py_NotImplemented);
+        return Py_NotImplemented;
+    }
+}
+
+static PYGLIB_Py_hash_t
+pyg_param_spec_hash(PyGParamSpec *self)
+{
+    return PYGLIB_Py_hash_t_FromVoidPtr (pyg_param_spec_get (self));
+}
+
+static PyObject *
+pyg_param_spec_repr(PyGParamSpec *self)
+{
+    char buf[80];
+
+    g_snprintf(buf, sizeof(buf), "<%s '%s'>",
+              G_PARAM_SPEC_TYPE_NAME (pyg_param_spec_get (self)),
+              g_param_spec_get_name (pyg_param_spec_get (self)));
+    return PYGLIB_PyUnicode_FromString(buf);
+}
+
+static void
+pyg_param_spec_dealloc(PyGParamSpec *self)
+{
+    g_param_spec_unref (pyg_param_spec_get (self));
+    PyObject_DEL(self);
+}
+
+
+static PyObject *
+pygenum_from_pspec(GParamSpec *pspec)
+{
+    PyObject *pyclass;
+    GParamSpecEnum *enum_pspec;
+    GType enum_type;
+    
+    enum_pspec = G_PARAM_SPEC_ENUM(pspec);
+    enum_type = G_ENUM_CLASS_TYPE(enum_pspec->enum_class);
+    pyclass = (PyObject*)g_type_get_qdata(enum_type, pygenum_class_key);
+    if (pyclass == NULL) {
+       pyclass = pyg_enum_add(NULL, g_type_name(enum_type), NULL, enum_type);
+       if (pyclass == NULL)
+           pyclass = Py_None;
+    }
+    
+    Py_INCREF(pyclass);
+    return pyclass;
+}
+
+static PyObject *
+pygflags_from_pspec(GParamSpec *pspec)
+{
+    PyObject *pyclass;
+    GParamSpecFlags *flag_pspec;
+    GType flag_type;
+
+    flag_pspec = G_PARAM_SPEC_FLAGS(pspec);
+    flag_type = G_FLAGS_CLASS_TYPE(flag_pspec->flags_class);
+    pyclass = (PyObject*)g_type_get_qdata(flag_type, pygflags_class_key);
+    if (pyclass == NULL) {
+       pyclass = pyg_flags_add(NULL, g_type_name(flag_type), NULL, flag_type);
+       if (pyclass == NULL)
+           pyclass = Py_None;
+    }
+    Py_INCREF(pyclass);
+    return pyclass;
+}    
+
+static PyObject *
+pyg_param_spec_getattr(PyGParamSpec *self, const gchar *attr)
+{
+    GParamSpec *pspec;
+
+    pspec = pyg_param_spec_get (self);
+
+    /* common attributes */
+    if (!strcmp(attr, "__gtype__")) {
+       return pyg_type_wrapper_new(G_PARAM_SPEC_TYPE(pspec));
+    } else if (!strcmp(attr, "name")) {
+       return Py_BuildValue("s", g_param_spec_get_name(pspec));
+    } else if (!strcmp(attr, "nick")) {
+       return Py_BuildValue("s", g_param_spec_get_nick(pspec));
+    } else if (!strcmp(attr, "blurb") || !strcmp(attr, "__doc__")) {
+       return Py_BuildValue("s", g_param_spec_get_blurb(pspec));
+    } else if (!strcmp(attr, "flags")) {
+       return pygi_guint_to_py (pspec->flags);
+    } else if (!strcmp(attr, "value_type")) {
+       return pyg_type_wrapper_new(pspec->value_type);
+    } else if (!strcmp(attr, "owner_type")) {
+       return pyg_type_wrapper_new(pspec->owner_type);
+    }
+
+    if (G_IS_PARAM_SPEC_CHAR(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return PYGLIB_PyUnicode_FromFormat(
+               "%c", G_PARAM_SPEC_CHAR(pspec)->default_value);
+       } else if (!strcmp(attr, "minimum")) {
+           return pygi_gint8_to_py (G_PARAM_SPEC_CHAR(pspec)->minimum);
+       } else if (!strcmp(attr, "maximum")) {
+           return pygi_gint8_to_py (G_PARAM_SPEC_CHAR(pspec)->maximum);
+       }
+    } else if (G_IS_PARAM_SPEC_UCHAR(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return PYGLIB_PyUnicode_FromFormat(
+               "%c", G_PARAM_SPEC_UCHAR(pspec)->default_value);
+       } else if (!strcmp(attr, "minimum")) {
+           return pygi_guint8_to_py (G_PARAM_SPEC_UCHAR(pspec)->minimum);
+       } else if (!strcmp(attr, "maximum")) {
+           return pygi_guint8_to_py (G_PARAM_SPEC_UCHAR(pspec)->maximum);
+       }
+    } else if (G_IS_PARAM_SPEC_BOOLEAN(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return pygi_gboolean_to_py (G_PARAM_SPEC_BOOLEAN(pspec)->default_value);
+       }
+    } else if (G_IS_PARAM_SPEC_INT(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return pygi_gint_to_py (G_PARAM_SPEC_INT(pspec)->default_value);
+       } else if (!strcmp(attr, "minimum")) {
+           return pygi_gint_to_py (G_PARAM_SPEC_INT(pspec)->minimum);
+       } else if (!strcmp(attr, "maximum")) {
+           return pygi_gint_to_py (G_PARAM_SPEC_INT(pspec)->maximum);
+       }
+    } else if (G_IS_PARAM_SPEC_UINT(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return pygi_guint_to_py (G_PARAM_SPEC_UINT(pspec)->default_value);
+       } else if (!strcmp(attr, "minimum")) {
+           return pygi_guint_to_py (G_PARAM_SPEC_UINT(pspec)->minimum);
+       } else if (!strcmp(attr, "maximum")) {
+           return pygi_guint_to_py (G_PARAM_SPEC_UINT(pspec)->maximum);
+       }
+    } else if (G_IS_PARAM_SPEC_LONG(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return pygi_glong_to_py (G_PARAM_SPEC_LONG(pspec)->default_value);
+       } else if (!strcmp(attr, "minimum")) {
+           return pygi_glong_to_py (G_PARAM_SPEC_LONG(pspec)->minimum);
+       } else if (!strcmp(attr, "maximum")) {
+           return pygi_glong_to_py (G_PARAM_SPEC_LONG(pspec)->maximum);
+       }
+    } else if (G_IS_PARAM_SPEC_ULONG(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return pygi_gulong_to_py (G_PARAM_SPEC_ULONG(pspec)->default_value);
+       } else if (!strcmp(attr, "minimum")) {
+           return pygi_gulong_to_py (G_PARAM_SPEC_ULONG(pspec)->minimum);
+       } else if (!strcmp(attr, "maximum")) {
+           return pygi_gulong_to_py (G_PARAM_SPEC_ULONG(pspec)->maximum);
+       }
+    } else if (G_IS_PARAM_SPEC_INT64(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return pygi_gint64_to_py (G_PARAM_SPEC_INT64(pspec)->default_value);
+       } else if (!strcmp(attr, "minimum")) {
+           return pygi_gint64_to_py (G_PARAM_SPEC_INT64(pspec)->minimum);
+       } else if (!strcmp(attr, "maximum")) {
+           return pygi_gint64_to_py (G_PARAM_SPEC_INT64(pspec)->maximum);
+       }
+    } else if (G_IS_PARAM_SPEC_UINT64(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return pygi_guint64_to_py (G_PARAM_SPEC_UINT64(pspec)->default_value);
+       } else if (!strcmp(attr, "minimum")) {
+           return pygi_guint64_to_py (G_PARAM_SPEC_UINT64(pspec)->minimum);
+       } else if (!strcmp(attr, "maximum")) {
+           return pygi_guint64_to_py (G_PARAM_SPEC_UINT64(pspec)->maximum);
+       }
+    } else if (G_IS_PARAM_SPEC_UNICHAR(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return PYGLIB_PyUnicode_FromFormat(
+               "%c", G_PARAM_SPEC_UNICHAR(pspec)->default_value);
+       }
+    } else if (G_IS_PARAM_SPEC_ENUM(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return pyg_enum_from_gtype(
+               pspec->value_type, G_PARAM_SPEC_ENUM(pspec)->default_value);
+       } else if (!strcmp(attr, "enum_class")) {
+           return pygenum_from_pspec(pspec);
+       }
+    } else if (G_IS_PARAM_SPEC_FLAGS(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return pyg_flags_from_gtype(
+               pspec->value_type, G_PARAM_SPEC_FLAGS(pspec)->default_value);
+       } else if (!strcmp(attr, "flags_class")) {
+           return pygflags_from_pspec(pspec);
+       }
+    } else if (G_IS_PARAM_SPEC_FLOAT(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return pygi_gfloat_to_py (G_PARAM_SPEC_FLOAT(pspec)->default_value);
+       } else if (!strcmp(attr, "minimum")) {
+           return pygi_gfloat_to_py (G_PARAM_SPEC_FLOAT(pspec)->minimum);
+       } else if (!strcmp(attr, "maximum")) {
+           return pygi_gfloat_to_py (G_PARAM_SPEC_FLOAT(pspec)->maximum);
+       } else if (!strcmp(attr, "epsilon")) {
+           return pygi_gfloat_to_py (G_PARAM_SPEC_FLOAT(pspec)->epsilon);
+       }
+    } else if (G_IS_PARAM_SPEC_DOUBLE(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return pygi_gdouble_to_py (
+               G_PARAM_SPEC_DOUBLE(pspec)->default_value);
+       } else if (!strcmp(attr, "minimum")) {
+           return pygi_gdouble_to_py (G_PARAM_SPEC_DOUBLE(pspec)->minimum);
+       } else if (!strcmp(attr, "maximum")) {
+           return pygi_gdouble_to_py (G_PARAM_SPEC_DOUBLE(pspec)->maximum);
+       } else if (!strcmp(attr, "epsilon")) {
+           return pygi_gdouble_to_py (G_PARAM_SPEC_DOUBLE(pspec)->epsilon);
+       }
+    } else if (G_IS_PARAM_SPEC_STRING(pspec)) {
+       if (!strcmp(attr, "default_value")) {
+           return Py_BuildValue(
+               "s", G_PARAM_SPEC_STRING(pspec)->default_value);
+       } else if (!strcmp(attr, "cset_first")) {
+           return Py_BuildValue(
+               "s", G_PARAM_SPEC_STRING(pspec)->cset_first);
+       } else if (!strcmp(attr, "cset_nth")) {
+           return Py_BuildValue(
+               "s", G_PARAM_SPEC_STRING(pspec)->cset_nth);
+       } else if (!strcmp(attr, "substitutor")) {
+           return Py_BuildValue(
+               "c", G_PARAM_SPEC_STRING(pspec)->substitutor);
+       } else if (!strcmp(attr, "null_fold_if_empty")) {
+           return pygi_gboolean_to_py (
+               G_PARAM_SPEC_STRING(pspec)->null_fold_if_empty);
+       } else if (!strcmp(attr, "ensure_non_null")) {
+           return pygi_gboolean_to_py (
+               G_PARAM_SPEC_STRING(pspec)->ensure_non_null);
+       }
+    } else {
+       /* This is actually not what's exported by GObjects paramspecs,
+        * But we exported this in earlier versions, so it's better to keep it here
+        * compatibility. But don't return it in __dir__, to "hide" it.
+        */
+       if (!strcmp(attr, "default_value")) {
+           /* XXX: Raise deprecation warning */
+           Py_INCREF(Py_None);
+           return Py_None;
+       }
+    }
+
+    PyErr_SetString(PyExc_AttributeError, attr);
+    return NULL;
+}
+
+
+static PyObject *
+pyg_param_spec_dir(PyGParamSpec *self, PyObject *dummy)
+{
+    GParamSpec *pspec = pyg_param_spec_get (self);
+
+    if (G_IS_PARAM_SPEC_CHAR(pspec)) {
+        return Py_BuildValue("[sssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value", "flags",
+                             "maximum", "minimum", "name", "nick",
+                             "owner_type", "value_type");
+    } else if (G_IS_PARAM_SPEC_UCHAR(pspec)) {
+        return Py_BuildValue("[sssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value",
+                             "flags", "maximum", "minimum",
+                             "name", "nick", "owner_type",
+                             "value_type");
+    } else if (G_IS_PARAM_SPEC_BOOLEAN(pspec)) {
+        return Py_BuildValue("[sssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value",
+                             "flags", "name", "nick", "owner_type",
+                             "value_type");
+    } else if (G_IS_PARAM_SPEC_INT(pspec)) {
+        return Py_BuildValue("[sssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value",
+                             "flags", "maximum", "minimum", "name",
+                             "nick", "owner_type", "value_type");
+    } else if (G_IS_PARAM_SPEC_UINT(pspec)) {
+        return Py_BuildValue("[sssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value",
+                             "flags", "maximum", "minimum",
+                             "name", "nick", "owner_type",
+                             "value_type");
+    } else if (G_IS_PARAM_SPEC_LONG(pspec)) {
+        return Py_BuildValue("[sssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value",
+                             "flags", "maximum", "minimum", "name",
+                             "nick", "owner_type", "value_type");
+    } else if (G_IS_PARAM_SPEC_ULONG(pspec)) {
+        return Py_BuildValue("[sssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value",
+                             "flags", "maximum", "minimum", "name",
+                             "nick", "owner_type", "value_type");
+    } else if (G_IS_PARAM_SPEC_INT64(pspec)) {
+        return Py_BuildValue("[sssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value",
+                             "flags", "maximum", "minimum", "name",
+                             "nick", "owner_type", "value_type");
+    } else if (G_IS_PARAM_SPEC_UINT64(pspec)) {
+        return Py_BuildValue("[sssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value",
+                             "flags", "maximum", "minimum",
+                             "name", "nick", "owner_type",
+                             "value_type");
+    } else if (G_IS_PARAM_SPEC_UNICHAR(pspec)) {
+        return Py_BuildValue("[sssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value",
+                             "flags", "name", "nick", "owner_type",
+                             "value_type");
+    } else if (G_IS_PARAM_SPEC_ENUM(pspec)) {
+        return Py_BuildValue("[ssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value", "enum_class",
+                             "flags", "name", "nick", "owner_type",
+                             "value_type");
+    } else if (G_IS_PARAM_SPEC_FLAGS(pspec)) {
+        return Py_BuildValue("[ssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value",
+                             "flags", "flags_class", "name", "nick",
+                             "owner_type", "value_type");
+    } else if (G_IS_PARAM_SPEC_FLOAT(pspec)) {
+        return Py_BuildValue("[ssssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "epsilon",
+                             "flags", "maximum", "minimum", "name", "nick", "owner_type",
+                             "value_type",
+                             "default_value");
+    } else if (G_IS_PARAM_SPEC_DOUBLE(pspec)) {
+        return Py_BuildValue("[ssssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "default_value", "epsilon",
+                             "flags", "maximum", "minimum", "name", "nick",
+                             "owner_type", "value_type");
+    } else if (G_IS_PARAM_SPEC_STRING(pspec)) {
+        return Py_BuildValue("[ssssssssssssss]", "__doc__", "__gtype__",
+                             "blurb", "cset_first", "cset_nth", "default_value",
+                             "ensure_non_null", "flags", "name", "nick",
+                             "null_fold_if_empty", "owner_type", "substitutor",
+                             "value_type");
+    } else {
+        return Py_BuildValue("[ssssssss]", "__doc__", "__gtype__", "blurb",
+                             "flags", "name", "nick",
+                             "owner_type", "value_type");
+    }
+}
+
+static PyMethodDef pyg_param_spec_methods[] = {
+    { "__dir__", (PyCFunction)pyg_param_spec_dir, METH_NOARGS},
+    { NULL, NULL, 0}
+};
+
+/**
+ * pyg_param_spec_new:
+ * @pspec: a GParamSpec.
+ *
+ * Creates a wrapper for a GParamSpec.
+ *
+ * Returns: the GParamSpec wrapper.
+ */
+PyObject *
+pyg_param_spec_new(GParamSpec *pspec)
+{
+    PyGParamSpec *self;
+
+    self = (PyGParamSpec *)PyObject_NEW(PyGParamSpec,
+                                       &PyGParamSpec_Type);
+    if (self == NULL)
+       return NULL;
+
+    pyg_param_spec_set (self, g_param_spec_ref (pspec));
+    return (PyObject *)self;
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_paramspec_register_types(PyObject *d)
+{
+    Py_TYPE(&PyGParamSpec_Type) = &PyType_Type;
+    PyGParamSpec_Type.tp_dealloc = (destructor)pyg_param_spec_dealloc;
+    PyGParamSpec_Type.tp_getattr = (getattrfunc)pyg_param_spec_getattr;
+    PyGParamSpec_Type.tp_richcompare = pyg_param_spec_richcompare;
+    PyGParamSpec_Type.tp_flags = Py_TPFLAGS_DEFAULT;
+    PyGParamSpec_Type.tp_repr = (reprfunc)pyg_param_spec_repr;
+    PyGParamSpec_Type.tp_hash = (hashfunc)pyg_param_spec_hash;
+    PyGParamSpec_Type.tp_methods = pyg_param_spec_methods;
+
+    if (PyType_Ready(&PyGParamSpec_Type))
+        return -1;
+    PyDict_SetItemString(d, "GParamSpec", (PyObject *)&PyGParamSpec_Type);
+
+    return 0;
+}
diff --git a/gi/pygparamspec.h b/gi/pygparamspec.h
new file mode 100644 (file)
index 0000000..8798535
--- /dev/null
@@ -0,0 +1,31 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *   pyginterface.c: wrapper for the gobject library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGOBJECT_PARAMSPEC_H__ 
+#define __PYGOBJECT_PARAMSPEC_H__
+
+#include <glib-object.h>
+
+extern PyTypeObject PyGParamSpec_Type;
+PyObject * pyg_param_spec_new (GParamSpec *pspec);
+
+int pygi_paramspec_register_types(PyObject *d);
+
+#endif /* __PYGOBJECT_PARAMSPEC_H__ */
diff --git a/gi/pygpointer.c b/gi/pygpointer.c
new file mode 100644 (file)
index 0000000..8e13374
--- /dev/null
@@ -0,0 +1,202 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *
+ *   pygpointer.c: wrapper for GPointer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <Python.h>
+#include <glib-object.h>
+#include "pygpointer.h"
+#include "pygi-type.h"
+#include "pygi-type.h"
+#include "pygi-util.h"
+
+
+GQuark pygpointer_class_key;
+
+PYGLIB_DEFINE_TYPE("gobject.GPointer", PyGPointer_Type, PyGPointer);
+
+static void
+pyg_pointer_dealloc(PyGPointer *self)
+{
+    Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static PyObject*
+pyg_pointer_richcompare(PyObject *self, PyObject *other, int op)
+{
+    if (Py_TYPE(self) == Py_TYPE(other))
+        return pyg_ptr_richcompare (pyg_pointer_get_ptr (self),
+                                    pyg_pointer_get_ptr (other),
+                                    op);
+    else {
+        Py_INCREF(Py_NotImplemented);
+        return Py_NotImplemented;
+    }
+}
+
+static PYGLIB_Py_hash_t
+pyg_pointer_hash(PyGPointer *self)
+{
+    return PYGLIB_Py_hash_t_FromVoidPtr (pyg_pointer_get_ptr (self));
+}
+
+static PyObject *
+pyg_pointer_repr(PyGPointer *self)
+{
+    gchar buf[128];
+
+    g_snprintf(buf, sizeof(buf), "<%s at 0x%" G_GUINTPTR_FORMAT ">",
+               g_type_name(self->gtype),
+               (guintptr)pyg_pointer_get_ptr (self));
+    return PYGLIB_PyUnicode_FromString(buf);
+}
+
+static int
+pyg_pointer_init(PyGPointer *self, PyObject *args, PyObject *kwargs)
+{
+    gchar buf[512];
+
+    if (!PyArg_ParseTuple(args, ":GPointer.__init__"))
+       return -1;
+
+    pyg_pointer_set_ptr (self, NULL);
+    self->gtype = 0;
+
+    g_snprintf(buf, sizeof(buf), "%s can not be constructed",
+              Py_TYPE(self)->tp_name);
+    PyErr_SetString(PyExc_NotImplementedError, buf);
+    return -1;
+}
+
+static void
+pyg_pointer_free(PyObject *op)
+{
+  PyObject_FREE(op);
+}
+
+/**
+ * pyg_register_pointer:
+ * @dict: the module dictionary to store the wrapper class.
+ * @class_name: the Python name for the wrapper class.
+ * @pointer_type: the GType of the pointer type being wrapped.
+ * @type: the wrapper class.
+ *
+ * Registers a wrapper for a pointer type.  The wrapper class will be
+ * a subclass of gobject.GPointer, and a reference to the wrapper
+ * class will be stored in the provided module dictionary.
+ */
+void
+pyg_register_pointer(PyObject *dict, const gchar *class_name,
+                    GType pointer_type, PyTypeObject *type)
+{
+    PyObject *o;
+
+    g_return_if_fail(dict != NULL);
+    g_return_if_fail(class_name != NULL);
+    g_return_if_fail(pointer_type != 0);
+
+    if (!type->tp_dealloc) type->tp_dealloc = (destructor)pyg_pointer_dealloc;
+
+    Py_TYPE(type) = &PyType_Type;
+    g_assert (Py_TYPE (&PyGPointer_Type) != NULL);
+    type->tp_base = &PyGPointer_Type;
+
+    if (PyType_Ready(type) < 0) {
+       g_warning("could not get type `%s' ready", type->tp_name);
+       return;
+    }
+
+    PyDict_SetItemString(type->tp_dict, "__gtype__",
+                        o=pyg_type_wrapper_new(pointer_type));
+    Py_DECREF(o);
+
+    g_type_set_qdata(pointer_type, pygpointer_class_key, type);
+
+    PyDict_SetItemString(dict, (char *)class_name, (PyObject *)type);
+}
+
+/**
+ * pyg_pointer_new:
+ * @pointer_type: the GType of the pointer value.
+ * @pointer: the pointer value.
+ *
+ * Creates a wrapper for a pointer value.  Since G_TYPE_POINTER types
+ * don't register any information about how to copy/free them, there
+ * is no guarantee that the pointer will remain valid, and there is
+ * nothing registered to release the pointer when the pointer goes out
+ * of scope.  This is why we don't recommend people use these types.
+ *
+ * Returns: the boxed wrapper.
+ */
+PyObject *
+pyg_pointer_new(GType pointer_type, gpointer pointer)
+{
+    PyGILState_STATE state;
+    PyGPointer *self;
+    PyTypeObject *tp;
+    g_return_val_if_fail(pointer_type != 0, NULL);
+
+    state = PyGILState_Ensure();
+
+    if (!pointer) {
+       Py_INCREF(Py_None);
+       PyGILState_Release(state);
+       return Py_None;
+    }
+
+    tp = g_type_get_qdata(pointer_type, pygpointer_class_key);
+
+    if (!tp)
+        tp = (PyTypeObject *)pygi_type_import_by_g_type(pointer_type);
+
+    if (!tp)
+       tp = (PyTypeObject *)&PyGPointer_Type; /* fallback */
+    self = PyObject_NEW(PyGPointer, tp);
+
+    PyGILState_Release(state);
+
+    if (self == NULL)
+       return NULL;
+
+    pyg_pointer_set_ptr (self, pointer);
+    self->gtype = pointer_type;
+
+    return (PyObject *)self;
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_pointer_register_types(PyObject *d)
+{
+    pygpointer_class_key     = g_quark_from_static_string("PyGPointer::class");
+
+    PyGPointer_Type.tp_dealloc = (destructor)pyg_pointer_dealloc;
+    PyGPointer_Type.tp_richcompare = pyg_pointer_richcompare;
+    PyGPointer_Type.tp_repr = (reprfunc)pyg_pointer_repr;
+    PyGPointer_Type.tp_hash = (hashfunc)pyg_pointer_hash;
+    PyGPointer_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+    PyGPointer_Type.tp_init = (initproc)pyg_pointer_init;
+    PyGPointer_Type.tp_free = (freefunc)pyg_pointer_free;
+    PYGOBJECT_REGISTER_GTYPE(d, PyGPointer_Type, "GPointer", G_TYPE_POINTER);
+
+    return 0;
+}
diff --git a/gi/pygpointer.h b/gi/pygpointer.h
new file mode 100644 (file)
index 0000000..df2c1e0
--- /dev/null
@@ -0,0 +1,35 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pygtk- Python bindings for the GTK toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYGOBJECT_POINTER_H__ 
+#define __PYGOBJECT_POINTER_H__
+
+#include <Python.h>
+
+extern GQuark pygpointer_class_key;
+
+extern PyTypeObject PyGPointer_Type;
+
+void       pyg_register_pointer (PyObject *dict, const gchar *class_name,
+                                 GType pointer_type, PyTypeObject *type);
+PyObject * pyg_pointer_new      (GType pointer_type, gpointer pointer);
+
+int pygi_pointer_register_types(PyObject *d);
+
+#endif /* __PYGOBJECT_POINTER_H__ */
diff --git a/gi/pygspawn.c b/gi/pygspawn.c
new file mode 100644 (file)
index 0000000..9457c6d
--- /dev/null
@@ -0,0 +1,278 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pyglib - Python bindings for GLib toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *
+ *   pygspawn.c: wrapper for the glib library.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include <glib.h>
+
+#include "pygi-python-compat.h"
+#include "pygi-basictype.h"
+#include "pygspawn.h"
+#include "pygi-error.h"
+
+struct _PyGChildSetupData {
+    PyObject *func;
+    PyObject *data;
+};
+
+PYGLIB_DEFINE_TYPE("gi._gi.Pid", PyGPid_Type, PYGLIB_PyLongObject)
+
+static GPid
+pyg_pid_get_pid (PyObject *self)
+{
+#ifdef G_OS_WIN32
+    return (GPid)PyLong_AsVoidPtr (self);
+#else
+    return (GPid)PYGLIB_PyLong_AsLong (self);
+#endif
+}
+
+static PyObject *
+pyg_pid_close(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    g_spawn_close_pid(pyg_pid_get_pid (self));
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyMethodDef pyg_pid_methods[] = {
+    { "close", (PyCFunction)pyg_pid_close, METH_NOARGS },
+    { NULL, NULL, 0 }
+};
+
+static void
+pyg_pid_free(PyObject *gpid)
+{
+    g_spawn_close_pid(pyg_pid_get_pid (gpid));
+    PYGLIB_PyLong_Type.tp_free((void *) gpid);
+}
+
+static int
+pyg_pid_tp_init(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    PyErr_SetString(PyExc_TypeError, "gi._gi.Pid cannot be manually instantiated");
+    return -1;
+}
+
+PyObject *
+pyg_pid_new(GPid pid)
+{
+    PyObject *long_val;
+#ifdef G_OS_WIN32
+    long_val = PyLong_FromVoidPtr (pid);
+#else
+    long_val = pygi_gint_to_py (pid);
+#endif
+    return PyObject_CallMethod((PyObject*)&PyGPid_Type, "__new__", "ON",
+                               &PyGPid_Type, long_val);
+}
+
+static void
+_pyg_spawn_async_callback(gpointer user_data)
+{
+    struct _PyGChildSetupData *data;
+    PyObject *retval;
+    PyGILState_STATE gil;
+
+    data = (struct _PyGChildSetupData *) user_data;
+    gil = PyGILState_Ensure();
+    if (data->data)
+        retval = PyObject_CallFunction(data->func, "O", data->data);
+    else
+        retval = PyObject_CallFunction(data->func, NULL);
+    if (retval)
+       Py_DECREF(retval);
+    else
+       PyErr_Print();
+    Py_DECREF(data->func);
+    Py_XDECREF(data->data);
+    g_slice_free(struct _PyGChildSetupData, data);
+    PyGILState_Release(gil);
+}
+
+PyObject *
+pyglib_spawn_async(PyObject *object, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { "argv", "envp", "working_directory", "flags",
+                              "child_setup", "user_data", "standard_input",
+                              "standard_output", "standard_error", NULL };
+    PyObject *pyargv, *pyenvp = NULL;
+    char **argv, **envp = NULL;
+    PyObject *func = Py_None, *user_data = NULL;
+    char *working_directory = NULL;
+    int flags = 0, _stdin = -1, _stdout = -1, _stderr = -1;
+    PyObject *pystdin = NULL, *pystdout = NULL, *pystderr = NULL;
+    gint *standard_input, *standard_output, *standard_error;
+    struct _PyGChildSetupData *callback_data = NULL;
+    GError *error = NULL;
+    GPid child_pid = 0;
+    Py_ssize_t len, i;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OsiOOOOO:gi._gi.spawn_async",
+                                     kwlist,
+                                     &pyargv, &pyenvp, &working_directory, &flags,
+                                     &func, &user_data,
+                                     &pystdin, &pystdout, &pystderr))
+        return NULL;
+
+    if (pystdin && PyObject_IsTrue(pystdin))
+        standard_input = &_stdin;
+    else
+        standard_input = NULL;
+
+    if (pystdout && PyObject_IsTrue(pystdout))
+        standard_output = &_stdout;
+    else
+        standard_output = NULL;
+
+    if (pystderr && PyObject_IsTrue(pystderr))
+        standard_error = &_stderr;
+    else
+        standard_error = NULL;
+
+      /* parse argv */
+    if (!PySequence_Check(pyargv)) {
+        PyErr_SetString(PyExc_TypeError,
+                        "gi._gi.spawn_async: "
+                       "first argument must be a sequence of strings");
+        return NULL;
+    }
+    len = PySequence_Length(pyargv);
+    argv = g_new0(char *, len + 1);
+    for (i = 0; i < len; ++i) {
+        PyObject *tmp = PySequence_ITEM(pyargv, i);
+        if (tmp == NULL || !PYGLIB_PyUnicode_Check(tmp)) {
+            PyErr_SetString(PyExc_TypeError,
+                            "gi._gi.spawn_async: "
+                           "first argument must be a sequence of strings");
+            g_free(argv);
+            Py_XDECREF(tmp);
+            return NULL;
+        }
+        argv[i] = PYGLIB_PyUnicode_AsString(tmp);
+        Py_DECREF(tmp);
+    }
+
+      /* parse envp */
+    if (pyenvp) {
+        if (!PySequence_Check(pyenvp)) {
+            PyErr_SetString(PyExc_TypeError,
+                            "gi._gi.spawn_async: "
+                           "second argument must be a sequence of strings");
+            g_free(argv);
+            return NULL;
+        }
+        len = PySequence_Length(pyenvp);
+        envp = g_new0(char *, len + 1);
+        for (i = 0; i < len; ++i) {
+            PyObject *tmp = PySequence_ITEM(pyenvp, i);
+            if (tmp == NULL || !PYGLIB_PyUnicode_Check(tmp)) {
+                PyErr_SetString(PyExc_TypeError,
+                                "gi._gi.spawn_async: "
+                               "second argument must be a sequence of strings");
+                g_free(envp);
+                Py_XDECREF(tmp);
+               g_free(argv);
+                return NULL;
+            }
+            envp[i] = PYGLIB_PyUnicode_AsString(tmp);
+            Py_DECREF(tmp);
+        }
+    }
+
+    if (func != Py_None) {
+        if (!PyCallable_Check(func)) {
+            PyErr_SetString(PyExc_TypeError, "child_setup parameter must be callable or None");
+            g_free(argv);
+            if (envp)
+                g_free(envp);
+            return NULL;
+        }
+        callback_data = g_slice_new(struct _PyGChildSetupData);
+        callback_data->func = func;
+        callback_data->data = user_data;
+        Py_INCREF(callback_data->func);
+        if (callback_data->data)
+            Py_INCREF(callback_data->data);
+    }
+
+    if (!g_spawn_async_with_pipes(working_directory, argv, envp, flags,
+                                  (func != Py_None ? _pyg_spawn_async_callback : NULL),
+                                  callback_data, &child_pid,
+                                  standard_input,
+                                  standard_output,
+                                  standard_error,
+                                  &error))
+
+
+    {
+        g_free(argv);
+        if (envp) g_free(envp);
+        if (callback_data) {
+            Py_DECREF(callback_data->func);
+            Py_XDECREF(callback_data->data);
+            g_slice_free(struct _PyGChildSetupData, callback_data);
+        }
+        pygi_error_check(&error);
+        return NULL;
+    }
+    g_free(argv);
+    if (envp) g_free(envp);
+
+    if (standard_input)
+        pystdin = pygi_gint_to_py(*standard_input);
+    else {
+        Py_INCREF(Py_None);
+        pystdin = Py_None;
+    }
+
+    if (standard_output)
+        pystdout = pygi_gint_to_py(*standard_output);
+    else {
+        Py_INCREF(Py_None);
+        pystdout = Py_None;
+    }
+
+    if (standard_error)
+        pystderr = pygi_gint_to_py(*standard_error);
+    else {
+        Py_INCREF(Py_None);
+        pystderr = Py_None;
+    }
+
+    return Py_BuildValue("NNNN", pyg_pid_new(child_pid), pystdin, pystdout, pystderr);
+}
+
+/**
+ * Returns 0 on success, or -1 and sets an exception.
+ */
+int
+pygi_spawn_register_types(PyObject *d)
+{
+    PyGPid_Type.tp_base = &PYGLIB_PyLong_Type;
+    PyGPid_Type.tp_flags = Py_TPFLAGS_DEFAULT;
+    PyGPid_Type.tp_methods = pyg_pid_methods;
+    PyGPid_Type.tp_init = pyg_pid_tp_init;
+    PyGPid_Type.tp_free = (freefunc)pyg_pid_free;
+    PyGPid_Type.tp_new = PYGLIB_PyLong_Type.tp_new;
+    PYGLIB_REGISTER_TYPE(d, PyGPid_Type, "Pid");
+
+    return 0;
+}
diff --git a/gi/pygspawn.h b/gi/pygspawn.h
new file mode 100644 (file)
index 0000000..c493ef8
--- /dev/null
@@ -0,0 +1,30 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * pyglib - Python bindings for GLib toolkit.
+ * Copyright (C) 1998-2003  James Henstridge
+ *               2004-2008  Johan Dahlin
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PYG_PID_H__
+#define __PYG_PID_H__
+
+PyObject * pyg_pid_new(GPid pid);
+int pygi_spawn_register_types(PyObject *d);
+
+PyObject * pyglib_spawn_async(PyObject *self, PyObject *args, PyObject *kwargs);
+
+
+#endif /* __PYG_PID_H__ */
+
diff --git a/gi/pygtkcompat.py b/gi/pygtkcompat.py
new file mode 100644 (file)
index 0000000..4a9c4be
--- /dev/null
@@ -0,0 +1,27 @@
+from __future__ import absolute_import
+import warnings
+
+from gi import PyGIDeprecationWarning
+
+warnings.warn('gi.pygtkcompat is being deprecated in favor of using "pygtkcompat" directly.',
+              PyGIDeprecationWarning)
+
+# pyflakes.ignore
+from pygtkcompat import (enable,
+                         enable_gtk,
+                         enable_vte,
+                         enable_poppler,
+                         enable_webkit,
+                         enable_gudev,
+                         enable_gst,
+                         enable_goocanvas)
+
+
+__all__ = ['enable',
+           'enable_gtk',
+           'enable_vte',
+           'enable_poppler',
+           'enable_webkit',
+           'enable_gudev',
+           'enable_gst',
+           'enable_goocanvas']
diff --git a/gi/repository/__init__.py b/gi/repository/__init__.py
new file mode 100644 (file)
index 0000000..5c5552a
--- /dev/null
@@ -0,0 +1,30 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2009 Johan Dahlin <johan@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from __future__ import absolute_import
+
+import sys
+
+from ..importer import DynamicImporter
+
+sys.meta_path.append(DynamicImporter('gi.repository'))
+
+del DynamicImporter
+del sys
diff --git a/gi/repository/meson.build b/gi/repository/meson.build
new file mode 100644 (file)
index 0000000..fdc136b
--- /dev/null
@@ -0,0 +1,5 @@
+python_sources = ['__init__.py']
+
+python.install_sources(python_sources,
+  subdir : join_paths('gi', 'repository')
+)
diff --git a/gi/types.py b/gi/types.py
new file mode 100644 (file)
index 0000000..47ed18a
--- /dev/null
@@ -0,0 +1,365 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2005-2009 Johan Dahlin <johan@gnome.org>
+#
+#   types.py: base types for introspected items.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from __future__ import absolute_import
+
+import warnings
+import re
+
+from ._constants import TYPE_INVALID
+from .docstring import generate_doc_string
+
+from ._gi import \
+    InterfaceInfo, \
+    ObjectInfo, \
+    StructInfo, \
+    VFuncInfo, \
+    register_interface_info, \
+    hook_up_vfunc_implementation, \
+    GInterface
+from . import _gi
+
+StructInfo, GInterface  # pyflakes
+
+from . import _propertyhelper as propertyhelper
+from . import _signalhelper as signalhelper
+
+
+def snake_case(name):
+    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
+    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
+
+
+class MetaClassHelper(object):
+    def _setup_methods(cls):
+        for method_info in cls.__info__.get_methods():
+            setattr(cls, method_info.__name__, method_info)
+
+    def _setup_class_methods(cls):
+        info = cls.__info__
+        class_struct = info.get_class_struct()
+        if class_struct is None:
+            return
+        for method_info in class_struct.get_methods():
+            name = method_info.__name__
+            # Don't mask regular methods or base class methods with TypeClass methods.
+            if not hasattr(cls, name):
+                setattr(cls, name, classmethod(method_info))
+
+    def _setup_fields(cls):
+        for field_info in cls.__info__.get_fields():
+            name = field_info.get_name().replace('-', '_')
+            setattr(cls, name, property(field_info.get_value, field_info.set_value))
+
+    def _setup_constants(cls):
+        for constant_info in cls.__info__.get_constants():
+            name = constant_info.get_name()
+            value = constant_info.get_value()
+            setattr(cls, name, value)
+
+    def _setup_vfuncs(cls):
+        for vfunc_name, py_vfunc in cls.__dict__.items():
+            if not vfunc_name.startswith("do_") or not callable(py_vfunc):
+                continue
+
+            skip_ambiguity_check = False
+
+            # If a method name starts with "do_" assume it is a vfunc, and search
+            # in the base classes for a method with the same name to override.
+            # Recursion is necessary as overriden methods in most immediate parent
+            # classes may shadow vfuncs from classes higher in the hierarchy.
+            vfunc_info = None
+            for base in cls.__mro__:
+                method = getattr(base, vfunc_name, None)
+                if method is not None and isinstance(method, VFuncInfo):
+                    vfunc_info = method
+                    break
+
+                if not hasattr(base, '__info__') or not hasattr(base.__info__, 'get_vfuncs'):
+                    continue
+
+                base_name = snake_case(base.__info__.get_type_name())
+
+                for v in base.__info__.get_vfuncs():
+                    if vfunc_name == 'do_%s_%s' % (base_name, v.get_name()):
+                        vfunc_info = v
+                        skip_ambiguity_check = True
+                        break
+
+                if vfunc_info:
+                    break
+
+            # If we did not find a matching method name in the bases, we might
+            # be overriding an interface virtual method. Since interfaces do not
+            # provide implementations, there will be no method attribute installed
+            # on the object. Instead we have to search through
+            # InterfaceInfo.get_vfuncs(). Note that the infos returned by
+            # get_vfuncs() use the C vfunc name (ie. there is no "do_" prefix).
+            if vfunc_info is None:
+                vfunc_info = find_vfunc_info_in_interface(cls.__bases__, vfunc_name[len("do_"):])
+
+            if vfunc_info is not None:
+                # Check to see if there are vfuncs with the same name in the bases.
+                # We have no way of specifying which one we are supposed to override.
+                if not skip_ambiguity_check:
+                    ambiguous_base = find_vfunc_conflict_in_bases(vfunc_info, cls.__bases__)
+                    if ambiguous_base is not None:
+                        base_info = vfunc_info.get_container()
+                        raise TypeError('Method %s() on class %s.%s is ambiguous '
+                                        'with methods in base classes %s.%s and %s.%s' %
+                                        (vfunc_name,
+                                         cls.__info__.get_namespace(),
+                                         cls.__info__.get_name(),
+                                         base_info.get_namespace(),
+                                         base_info.get_name(),
+                                         ambiguous_base.__info__.get_namespace(),
+                                         ambiguous_base.__info__.get_name()
+                                        ))
+                hook_up_vfunc_implementation(vfunc_info, cls.__gtype__,
+                                             py_vfunc)
+
+    def _setup_native_vfuncs(cls):
+        # Only InterfaceInfo and ObjectInfo have the get_vfuncs() method.
+        # We skip InterfaceInfo because interfaces have no implementations for vfuncs.
+        # Also check if __info__ in __dict__, not hasattr('__info__', ...)
+        # because we do not want to accidentally retrieve __info__ from a base class.
+        class_info = cls.__dict__.get('__info__')
+        if class_info is None or not isinstance(class_info, ObjectInfo):
+            return
+
+        # Special case skipping of vfuncs for GObject.Object because they will break
+        # the static bindings which will try to use them.
+        if cls.__module__ == 'gi.repository.GObject' and cls.__name__ == 'Object':
+            return
+
+        for vfunc_info in class_info.get_vfuncs():
+            name = 'do_%s' % vfunc_info.__name__
+            setattr(cls, name, vfunc_info)
+
+
+def find_vfunc_info_in_interface(bases, vfunc_name):
+    for base in bases:
+        # All wrapped interfaces inherit from GInterface.
+        # This can be seen in IntrospectionModule.__getattr__() in module.py.
+        # We do not need to search regular classes here, only wrapped interfaces.
+        # We also skip GInterface, because it is not wrapped and has no __info__ attr.
+        # Skip bases without __info__ (static _gi.GObject)
+        if base is GInterface or\
+                not issubclass(base, GInterface) or\
+                not hasattr(base, '__info__'):
+            continue
+
+        # Only look at this classes vfuncs if it is an interface.
+        if isinstance(base.__info__, InterfaceInfo):
+            for vfunc in base.__info__.get_vfuncs():
+                if vfunc.get_name() == vfunc_name:
+                    return vfunc
+
+        # Recurse into the parent classes
+        vfunc = find_vfunc_info_in_interface(base.__bases__, vfunc_name)
+        if vfunc is not None:
+            return vfunc
+
+    return None
+
+
+def find_vfunc_conflict_in_bases(vfunc, bases):
+    for klass in bases:
+        if not hasattr(klass, '__info__') or \
+                not hasattr(klass.__info__, 'get_vfuncs'):
+            continue
+        vfuncs = klass.__info__.get_vfuncs()
+        vfunc_name = vfunc.get_name()
+        for v in vfuncs:
+            if v.get_name() == vfunc_name and v != vfunc:
+                return klass
+
+        aklass = find_vfunc_conflict_in_bases(vfunc, klass.__bases__)
+        if aklass is not None:
+            return aklass
+    return None
+
+
+class _GObjectMetaBase(type):
+    """Metaclass for automatically registering GObject classes."""
+    def __init__(cls, name, bases, dict_):
+        type.__init__(cls, name, bases, dict_)
+        propertyhelper.install_properties(cls)
+        signalhelper.install_signals(cls)
+        cls._type_register(cls.__dict__)
+
+    def _type_register(cls, namespace):
+        # don't register the class if already registered
+        if '__gtype__' in namespace:
+            return
+
+        # Do not register a new GType for the overrides, as this would sort of
+        # defeat the purpose of overrides...
+        if cls.__module__.startswith('gi.overrides.'):
+            return
+
+        _gi.type_register(cls, namespace.get('__gtype_name__'))
+
+
+_gi._install_metaclass(_GObjectMetaBase)
+
+
+class GObjectMeta(_GObjectMetaBase, MetaClassHelper):
+    """Meta class used for GI GObject based types."""
+    def __init__(cls, name, bases, dict_):
+        super(GObjectMeta, cls).__init__(name, bases, dict_)
+        is_gi_defined = False
+        if cls.__module__ == 'gi.repository.' + cls.__info__.get_namespace():
+            is_gi_defined = True
+
+        is_python_defined = False
+        if not is_gi_defined and cls.__module__ != GObjectMeta.__module__:
+            is_python_defined = True
+
+        if is_python_defined:
+            cls._setup_vfuncs()
+        elif is_gi_defined:
+            if isinstance(cls.__info__, ObjectInfo):
+                cls._setup_class_methods()
+            cls._setup_methods()
+            cls._setup_constants()
+            cls._setup_native_vfuncs()
+
+            if isinstance(cls.__info__, ObjectInfo):
+                cls._setup_fields()
+            elif isinstance(cls.__info__, InterfaceInfo):
+                register_interface_info(cls.__info__.get_g_type())
+
+    def mro(cls):
+        return mro(cls)
+
+    @property
+    def __doc__(cls):
+        """Meta class property which shows up on any class using this meta-class."""
+        if cls == GObjectMeta:
+            return ''
+
+        doc = cls.__dict__.get('__doc__', None)
+        if doc is not None:
+            return doc
+
+        # For repository classes, dynamically generate a doc string if it wasn't overridden.
+        if cls.__module__.startswith(('gi.repository.', 'gi.overrides')):
+            return generate_doc_string(cls.__info__)
+
+        return None
+
+
+def mro(C):
+    """Compute the class precedence list (mro) according to C3, with GObject
+    interface considerations.
+
+    We override Python's MRO calculation to account for the fact that
+    GObject classes are not affected by the diamond problem:
+    http://en.wikipedia.org/wiki/Diamond_problem
+
+    Based on http://www.python.org/download/releases/2.3/mro/
+    """
+    # TODO: If this turns out being too slow, consider using generators
+    bases = []
+    bases_of_subclasses = [[C]]
+
+    if C.__bases__:
+        for base in C.__bases__:
+            # Python causes MRO's to be calculated starting with the lowest
+            # base class and working towards the descendant, storing the result
+            # in __mro__ at each point. Therefore at this point we know that
+            # we already have our base class MRO's available to us, there is
+            # no need for us to (re)calculate them.
+            if hasattr(base, '__mro__'):
+                bases_of_subclasses += [list(base.__mro__)]
+            else:
+                warnings.warn('Mixin class %s is an old style class, please '
+                              'update this to derive from "object".' % base,
+                              RuntimeWarning)
+                # For old-style classes (Python2 only), the MRO is not
+                # easily accessible. As we do need it here, we calculate
+                # it via recursion, according to the C3 algorithm. Using C3
+                # for old style classes deviates from Python's own behaviour,
+                # but visible effects here would be a corner case triggered by
+                # questionable design.
+                bases_of_subclasses += [mro(base)]
+        bases_of_subclasses += [list(C.__bases__)]
+
+    while bases_of_subclasses:
+        for subclass_bases in bases_of_subclasses:
+            candidate = subclass_bases[0]
+            not_head = [s for s in bases_of_subclasses if candidate in s[1:]]
+            if not_head and GInterface not in candidate.__bases__:
+                candidate = None  # conflict, reject candidate
+            else:
+                break
+
+        if candidate is None:
+            raise TypeError('Cannot create a consistent method resolution '
+                            'order (MRO)')
+
+        bases.append(candidate)
+
+        for subclass_bases in bases_of_subclasses[:]:  # remove candidate
+            if subclass_bases and subclass_bases[0] == candidate:
+                del subclass_bases[0]
+                if not subclass_bases:
+                    bases_of_subclasses.remove(subclass_bases)
+
+    return bases
+
+
+def nothing(*args, **kwargs):
+    pass
+
+
+class StructMeta(type, MetaClassHelper):
+    """Meta class used for GI Struct based types."""
+
+    def __init__(cls, name, bases, dict_):
+        super(StructMeta, cls).__init__(name, bases, dict_)
+
+        # Avoid touching anything else than the base class.
+        g_type = cls.__info__.get_g_type()
+        if g_type != TYPE_INVALID and g_type.pytype is not None:
+            return
+
+        cls._setup_fields()
+        cls._setup_methods()
+
+        for method_info in cls.__info__.get_methods():
+            if method_info.is_constructor() and \
+                    method_info.__name__ == 'new' and \
+                    (not method_info.get_arguments() or
+                     cls.__info__.get_size() == 0):
+                cls.__new__ = staticmethod(method_info)
+                # Boxed will raise an exception
+                # if arguments are given to __init__
+                cls.__init__ = nothing
+                break
+
+    @property
+    def __doc__(cls):
+        if cls == StructMeta:
+            return ''
+        return generate_doc_string(cls.__info__)
diff --git a/meson.build b/meson.build
new file mode 100644 (file)
index 0000000..b2af788
--- /dev/null
@@ -0,0 +1,165 @@
+project('pygobject', 'c',
+  version : '3.30.1',
+  meson_version : '>= 0.46.0',
+  default_options : [ 'warning_level=1',
+                      'buildtype=debugoptimized'])
+
+pygobject_version = meson.project_version()
+version_arr = pygobject_version.split('.')
+pygobject_version_major = version_arr[0].to_int()
+pygobject_version_minor = version_arr[1].to_int()
+pygobject_version_micro = version_arr[2].to_int()
+
+platform_version = '@0@.0'.format(pygobject_version_major)
+
+pymod = import('python')
+python = pymod.find_installation(get_option('python'))
+
+python_dep = python.dependency()
+
+glib_version_req = '>= 2.38.0'
+gi_version_req = '>= 1.46.0'
+pycairo_version_req = '>= 1.11.1'
+libffi_version_req = '>= 3.0'
+
+gi_dep = dependency('gobject-introspection-1.0', version : gi_version_req,
+  fallback: ['gobject-introspection', 'girepo_dep'])
+glib_dep = dependency('glib-2.0', version : glib_version_req,
+  fallback: ['glib', 'libglib_dep'])
+gobject_dep = dependency('gobject-2.0', version : glib_version_req,
+  fallback: ['glib', 'libgobject_dep'])
+gio_dep = dependency('gio-2.0', version : glib_version_req,
+  fallback: ['glib', 'libgio_dep'])
+gmodule_dep = dependency('gmodule-2.0', version : glib_version_req,
+  fallback: ['glib', 'libgmodule_dep'])
+ffi_dep = dependency('libffi', version : '>= 3.0',
+  fallback : ['libffi', 'ffi_dep'])
+
+with_pycairo = get_option('pycairo')
+
+if with_pycairo
+  cairo_dep = dependency('cairo')
+  cairo_gobject_dep = dependency('cairo-gobject')
+
+  if python.language_version().version_compare('>= 3.0')
+    pycairo_name = 'py3cairo'
+  else
+    pycairo_name = 'pycairo'
+  endif
+
+  pycairo_dep = dependency(
+    pycairo_name,
+    version: pycairo_version_req,
+    fallback: ['pycairo', 'pycairo_dep'],
+    default_options: ['python=' + get_option('python')],
+  )
+endif
+
+cc = meson.get_compiler('c')
+
+main_c_args = [
+  '-Wall',
+  '-Warray-bounds',
+  '-Wcast-align',
+  '-Wdeclaration-after-statement',
+  '-Wduplicated-branches',
+  '-Wextra',
+  '-Wformat=2',
+  '-Wformat-nonliteral',
+  '-Wformat-security',
+  '-Wimplicit-function-declaration',
+  '-Winit-self',
+  '-Wjump-misses-init',
+  '-Wlogical-op',
+  '-Wmissing-declarations',
+  '-Wmissing-format-attribute',
+  '-Wmissing-include-dirs',
+  '-Wmissing-noreturn',
+  '-Wmissing-prototypes',
+  '-Wnested-externs',
+  '-Wnull-dereference',
+  '-Wold-style-definition',
+  '-Wpacked',
+  '-Wpointer-arith',
+  '-Wrestrict',
+  '-Wreturn-type',
+  '-Wshadow',
+  '-Wsign-compare',
+  '-Wstrict-aliasing',
+  '-Wstrict-prototypes',
+  '-Wundef',
+  '-Wunused-but-set-variable',
+  '-Wwrite-strings',
+]
+
+main_c_args += [
+  '-Wno-incompatible-pointer-types-discards-qualifiers',
+  '-Wno-missing-field-initializers',
+  '-Wno-unused-parameter',
+  '-Wno-discarded-qualifiers',
+  '-Wno-sign-conversion',
+  '-Wno-cast-function-type',
+  '-Wno-int-conversion',
+]
+
+main_c_args += [
+  '-fno-strict-aliasing',
+  '-fvisibility=hidden',
+]
+
+if not ['3.3', '3.4'].contains(python.language_version())
+  main_c_args += [
+    '-Wswitch-default',
+  ]
+endif
+
+main_c_args = cc.get_supported_arguments(main_c_args)
+
+pyext_c_args = ['-DPY_SSIZE_T_CLEAN']
+
+cdata = configuration_data()
+
+cdata.set('PYGOBJECT_MAJOR_VERSION', pygobject_version_major)
+cdata.set('PYGOBJECT_MINOR_VERSION', pygobject_version_minor)
+cdata.set('PYGOBJECT_MICRO_VERSION', pygobject_version_micro)
+
+configure_file(output : 'config.h', configuration : cdata)
+
+pkgconf = configuration_data()
+
+pkgconf.set('prefix', join_paths(get_option('prefix')))
+pkgconf.set('exec_prefix', '${prefix}')
+pkgconf.set('includedir', join_paths('${prefix}', get_option('includedir')))
+pkgconf.set('datarootdir', join_paths('${prefix}', get_option('datadir')))
+pkgconf.set('datadir', '${datarootdir}')
+pkgconf.set('VERSION', pygobject_version)
+
+pkg_install_dir = '@0@/pkgconfig'.format(get_option('libdir'))
+
+configure_file(input : 'pygobject-@0@.pc.in'.format(platform_version),
+  output : 'pygobject-@0@.pc'.format(platform_version),
+  configuration : pkgconf,
+  install_dir : pkg_install_dir)
+
+pygobject_dep = declare_dependency(
+  include_directories: include_directories('gi'),
+  dependencies: [gobject_dep, ffi_dep],
+  version: meson.project_version(),
+)
+
+if pygobject_version_minor.is_odd()
+  py_version = '@0@.dev0'.format(pygobject_version)
+else
+  py_version = pygobject_version
+endif
+
+pkginfo_conf = configuration_data()
+pkginfo_conf.set('VERSION', py_version)
+configure_file(input : 'PKG-INFO.in',
+  output : 'PyGObject-@0@.egg-info'.format(py_version),
+  configuration : pkginfo_conf,
+  install_dir : python.get_install_dir())
+
+subdir('gi')
+subdir('pygtkcompat')
+subdir('tests')
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644 (file)
index 0000000..31f3aa3
--- /dev/null
@@ -0,0 +1,2 @@
+option('python', type : 'string', value : 'python3')
+option('pycairo', type : 'boolean', value : true, description : 'build with pycairo integration')
diff --git a/pygobject-3.0.pc.in b/pygobject-3.0.pc.in
new file mode 100644 (file)
index 0000000..ca49423
--- /dev/null
@@ -0,0 +1,26 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+includedir=@includedir@
+datarootdir=@datarootdir@
+datadir=@datadir@
+
+# you can use the --variable=pygobjectincludedir argument to
+# pkg-config to get this value. You might want to use this to
+# install additional headers.
+pygobjectincludedir=${includedir}/pygobject-3.0
+
+Name: PyGObject
+Description: Python bindings for GObject
+Requires: gobject-2.0
+Requires.private: libffi
+Version: @VERSION@
+Cflags: -I${pygobjectincludedir}
+
+# overridesdir has now moved to the gi module
+# third parties can access it in a python script:
+#
+# import gi
+# installdir = gi._overridesdir
+#
+# the version of python you run the script from
+# will determine the actual overrides path
diff --git a/pygobject.doap b/pygobject.doap
new file mode 100644 (file)
index 0000000..63f184f
--- /dev/null
@@ -0,0 +1,74 @@
+<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+         xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
+         xmlns:foaf="http://xmlns.com/foaf/0.1/"
+         xmlns:gnome="http://api.gnome.org/doap-extensions#"
+         xmlns="http://usefulinc.com/ns/doap#">
+
+  <name xml:lang="en">PyGObject</name>
+  <shortdesc xml:lang="en">Python bindings for GObject Introspection</shortdesc>
+  <description>
+GObject is a object system used by GTK+,  GStreamer and other libraries.
+
+PyGObject provides a convenient wrapper for use in Python programs when accessing GObject libraries.
+
+Like the GObject library itself PyGObject is licensed under the GNU LGPL, so is suitable for use in both free software and proprietary applications. It is already in use in many applications ranging from small single purpose scripts up to large full featured applications.
+
+PyGObject now dynamically accesses any GObject libraries that uses GObject Introspection. It replaces the need for separate modules such as PyGTK, GIO and python-gnome to build a full GNOME 3.0 application. Once new functionality is added to gobject library it is instantly available as a Python API without the need for intermediate Python glue.
+  </description>
+  <homepage rdf:resource="https://wiki.gnome.org/Projects/PyGObject" />
+  <mailing-list rdf:resource="http://mail.gnome.org/mailman/listinfo/python-hackers-list" />
+  <category rdf:resource="http://api.gnome.org/doap-extensions#core" />
+  <download-page rdf:resource="http://download.gnome.org/sources/pygobject/" />
+  <bug-database rdf:resource="https://bugzilla.gnome.org/browse.cgi?product=pygobject" />
+  <programming-language>C</programming-language>
+  <programming-language>Python</programming-language>
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Tomeu Vizoso</foaf:name>
+      <foaf:mbox rdf:resource="mailto:tomeu.vizoso@collabora.co.uk" />
+      <gnome:userid>tomeuv</gnome:userid>
+    </foaf:Person>
+  </maintainer>
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Martin Pitt</foaf:name>
+      <foaf:mbox rdf:resource="mailto:martinpitt@gnome.org" />
+      <gnome:userid>martinpitt</gnome:userid>
+    </foaf:Person>
+  </maintainer>
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Paolo Borelli</foaf:name>
+      <foaf:mbox rdf:resource="mailto:pborelli@gnome.org" />
+      <gnome:userid>pborelli</gnome:userid>
+    </foaf:Person>
+  </maintainer>
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Ignacio Casal Quinteiro</foaf:name>
+      <foaf:mbox rdf:resource="mailto:icq@gnome.org" />
+      <gnome:userid>icq</gnome:userid>
+    </foaf:Person>
+  </maintainer>
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Sebastian Pölsterl</foaf:name>
+      <foaf:mbox rdf:resource="mailto:sebp@k-d-w.org" />
+      <gnome:userid>sebp</gnome:userid>
+    </foaf:Person>
+  </maintainer>
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Simon Feltman</foaf:name>
+      <foaf:mbox rdf:resource="mailto:sfeltman@src.gnome.org" />
+      <gnome:userid>sfeltman</gnome:userid>
+    </foaf:Person>
+  </maintainer>
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Christoph Reiter</foaf:name>
+      <foaf:mbox rdf:resource="mailto:creiter@src.gnome.org" />
+      <gnome:userid>creiter</gnome:userid>
+    </foaf:Person>
+  </maintainer>
+</Project>
diff --git a/pygtkcompat/__init__.py b/pygtkcompat/__init__.py
new file mode 100644 (file)
index 0000000..8ae0337
--- /dev/null
@@ -0,0 +1,20 @@
+
+# pyflakes.ignore
+from .pygtkcompat import (enable,
+                          enable_gtk,
+                          enable_vte,
+                          enable_poppler,
+                          enable_webkit,
+                          enable_gudev,
+                          enable_gst,
+                          enable_goocanvas)
+
+
+__all__ = ['enable',
+           'enable_gtk',
+           'enable_vte',
+           'enable_poppler',
+           'enable_webkit',
+           'enable_gudev',
+           'enable_gst',
+           'enable_goocanvas']
diff --git a/pygtkcompat/generictreemodel.py b/pygtkcompat/generictreemodel.py
new file mode 100644 (file)
index 0000000..226dffc
--- /dev/null
@@ -0,0 +1,423 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# generictreemodel - GenericTreeModel implementation for pygtk compatibility.
+# Copyright (C) 2013 Simon Feltman
+#
+#   generictreemodel.py: GenericTreeModel implementation for pygtk compatibility
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+# System
+import sys
+import random
+import collections
+import ctypes
+import platform
+
+# GObject
+from gi.repository import GObject
+from gi.repository import Gtk
+
+
+class _CTreeIter(ctypes.Structure):
+    _fields_ = [('stamp', ctypes.c_int),
+                ('user_data', ctypes.c_void_p),
+                ('user_data2', ctypes.c_void_p),
+                ('user_data3', ctypes.c_void_p)]
+
+    @classmethod
+    def from_iter(cls, iter):
+        offset = sys.getsizeof(object())  # size of PyObject_HEAD
+        return ctypes.POINTER(cls).from_address(id(iter) + offset)
+
+
+if platform.python_implementation() == "PyPy":
+    def _get_user_data_as_pyobject(iter):
+        raise NotImplementedError("Not yet supported under PyPy")
+else:
+    def _get_user_data_as_pyobject(iter):
+        citer = _CTreeIter.from_iter(iter)
+        return ctypes.cast(citer.contents.user_data, ctypes.py_object).value
+
+
+def handle_exception(default_return):
+    """Returns a function which can act as a decorator for wrapping exceptions and
+    returning "default_return" upon an exception being thrown.
+
+    This is used to wrap Gtk.TreeModel "do_" method implementations so we can return
+    a proper value from the override upon an exception occurring with client code
+    implemented by the "on_" methods.
+    """
+    def decorator(func):
+        def wrapped_func(*args, **kargs):
+            try:
+                return func(*args, **kargs)
+            except:
+                # Use excepthook directly to avoid any printing to the screen
+                # if someone installed an except hook.
+                sys.excepthook(*sys.exc_info())
+            return default_return
+        return wrapped_func
+    return decorator
+
+
+class GenericTreeModel(GObject.GObject, Gtk.TreeModel):
+    """A base implementation of a Gtk.TreeModel for python.
+
+    The GenericTreeModel eases implementing the Gtk.TreeModel interface in Python.
+    The class can be subclassed to provide a TreeModel implementation which works
+    directly with Python objects instead of iterators.
+
+    All of the on_* methods should be overridden by subclasses to provide the
+    underlying implementation a way to access custom model data. For the purposes of
+    this API, all custom model data supplied or handed back through the overridable
+    API will use the argument names: node, parent, and child in regards to user data
+    python objects.
+
+    The create_tree_iter, set_user_data, invalidate_iters, iter_is_valid methods are
+    available to help manage Gtk.TreeIter objects and their Python object references.
+
+    GenericTreeModel manages a pool of user data nodes that have been used with iters.
+    This pool stores a references to user data nodes as a dictionary value with the
+    key being the integer id of the data. This id is what the Gtk.TreeIter objects
+    use to reference data in the pool.
+    References will be removed from the pool when the model is deleted or explicitly
+    by using the optional "node" argument to the "row_deleted" method when notifying
+    the model of row deletion.
+    """
+
+    leak_references = GObject.Property(default=True, type=bool,
+                                       blurb="If True, strong references to user data attached to iters are "
+                                       "stored in a dictionary pool (default). Otherwise the user data is "
+                                       "stored as a raw pointer to a python object without a reference.")
+
+    #
+    # Methods
+    #
+    def __init__(self):
+        """Initialize. Make sure to call this from derived classes if overridden."""
+        super(GenericTreeModel, self).__init__()
+        self.stamp = 0
+
+        #: Dictionary of (id(user_data): user_data), used when leak-refernces=False
+        self._held_refs = dict()
+
+        # Set initial stamp
+        self.invalidate_iters()
+
+    def iter_depth_first(self):
+        """Depth-first iteration of the entire TreeModel yielding the python nodes."""
+        stack = collections.deque([None])
+        while stack:
+            it = stack.popleft()
+            if it is not None:
+                yield self.get_user_data(it)
+            children = [self.iter_nth_child(it, i) for i in range(self.iter_n_children(it))]
+            stack.extendleft(reversed(children))
+
+    def invalidate_iter(self, iter):
+        """Clear user data and its reference from the iter and this model."""
+        iter.stamp = 0
+        if iter.user_data:
+            if iter.user_data in self._held_refs:
+                del self._held_refs[iter.user_data]
+            iter.user_data = None
+
+    def invalidate_iters(self):
+        """
+        This method invalidates all TreeIter objects associated with this custom tree model
+        and frees their locally pooled references.
+        """
+        self.stamp = random.randint(-2147483648, 2147483647)
+        self._held_refs.clear()
+
+    def iter_is_valid(self, iter):
+        """
+        :Returns:
+            True if the gtk.TreeIter specified by iter is valid for the custom tree model.
+        """
+        return iter.stamp == self.stamp
+
+    def get_user_data(self, iter):
+        """Get the user_data associated with the given TreeIter.
+
+        GenericTreeModel stores arbitrary Python objects mapped to instances of Gtk.TreeIter.
+        This method allows to retrieve the Python object held by the given iterator.
+        """
+        if self.leak_references:
+            return self._held_refs[iter.user_data]
+        else:
+            return _get_user_data_as_pyobject(iter)
+
+    def set_user_data(self, iter, user_data):
+        """Applies user_data and stamp to the given iter.
+
+        If the models "leak_references" property is set, a reference to the
+        user_data is stored with the model to ensure we don't run into bad
+        memory problems with the TreeIter.
+        """
+        iter.user_data = id(user_data)
+
+        if user_data is None:
+            self.invalidate_iter(iter)
+        else:
+            iter.stamp = self.stamp
+            if self.leak_references:
+                self._held_refs[iter.user_data] = user_data
+
+    def create_tree_iter(self, user_data):
+        """Create a Gtk.TreeIter instance with the given user_data specific for this model.
+
+        Use this method to create Gtk.TreeIter instance instead of directly calling
+        Gtk.Treeiter(), this will ensure proper reference managment of wrapped used_data.
+        """
+        iter = Gtk.TreeIter()
+        self.set_user_data(iter, user_data)
+        return iter
+
+    def _create_tree_iter(self, data):
+        """Internal creation of a (bool, TreeIter) pair for returning directly
+        back to the view interfacing with this model."""
+        if data is None:
+            return (False, None)
+        else:
+            it = self.create_tree_iter(data)
+            return (True, it)
+
+    def row_deleted(self, path, node=None):
+        """Notify the model a row has been deleted.
+
+        Use the node parameter to ensure the user_data reference associated
+        with the path is properly freed by this model.
+
+        :Parameters:
+            path : Gtk.TreePath
+                Path to the row that has been deleted.
+            node : object
+                Python object used as the node returned from "on_get_iter". This is
+                optional but ensures the model will not leak references to this object.
+        """
+        super(GenericTreeModel, self).row_deleted(path)
+        node_id = id(node)
+        if node_id in self._held_refs:
+            del self._held_refs[node_id]
+
+    #
+    # GtkTreeModel Interface Implementation
+    #
+    @handle_exception(0)
+    def do_get_flags(self):
+        """Internal method."""
+        return self.on_get_flags()
+
+    @handle_exception(0)
+    def do_get_n_columns(self):
+        """Internal method."""
+        return self.on_get_n_columns()
+
+    @handle_exception(GObject.TYPE_INVALID)
+    def do_get_column_type(self, index):
+        """Internal method."""
+        return self.on_get_column_type(index)
+
+    @handle_exception((False, None))
+    def do_get_iter(self, path):
+        """Internal method."""
+        return self._create_tree_iter(self.on_get_iter(path))
+
+    @handle_exception(False)
+    def do_iter_next(self, iter):
+        """Internal method."""
+        if iter is None:
+            next_data = self.on_iter_next(None)
+        else:
+            next_data = self.on_iter_next(self.get_user_data(iter))
+
+        self.set_user_data(iter, next_data)
+        return next_data is not None
+
+    @handle_exception(None)
+    def do_get_path(self, iter):
+        """Internal method."""
+        path = self.on_get_path(self.get_user_data(iter))
+        if path is None:
+            return None
+        else:
+            return Gtk.TreePath(path)
+
+    @handle_exception(None)
+    def do_get_value(self, iter, column):
+        """Internal method."""
+        return self.on_get_value(self.get_user_data(iter), column)
+
+    @handle_exception((False, None))
+    def do_iter_children(self, parent):
+        """Internal method."""
+        data = self.get_user_data(parent) if parent else None
+        return self._create_tree_iter(self.on_iter_children(data))
+
+    @handle_exception(False)
+    def do_iter_has_child(self, parent):
+        """Internal method."""
+        return self.on_iter_has_child(self.get_user_data(parent))
+
+    @handle_exception(0)
+    def do_iter_n_children(self, iter):
+        """Internal method."""
+        if iter is None:
+            return self.on_iter_n_children(None)
+        return self.on_iter_n_children(self.get_user_data(iter))
+
+    @handle_exception((False, None))
+    def do_iter_nth_child(self, parent, n):
+        """Internal method."""
+        if parent is None:
+            data = self.on_iter_nth_child(None, n)
+        else:
+            data = self.on_iter_nth_child(self.get_user_data(parent), n)
+        return self._create_tree_iter(data)
+
+    @handle_exception((False, None))
+    def do_iter_parent(self, child):
+        """Internal method."""
+        return self._create_tree_iter(self.on_iter_parent(self.get_user_data(child)))
+
+    @handle_exception(None)
+    def do_ref_node(self, iter):
+        self.on_ref_node(self.get_user_data(iter))
+
+    @handle_exception(None)
+    def do_unref_node(self, iter):
+        self.on_unref_node(self.get_user_data(iter))
+
+    #
+    # Python Subclass Overridables
+    #
+    def on_get_flags(self):
+        """Overridable.
+
+        :Returns Gtk.TreeModelFlags:
+            The flags for this model. See: Gtk.TreeModelFlags
+        """
+        raise NotImplementedError
+
+    def on_get_n_columns(self):
+        """Overridable.
+
+        :Returns:
+            The number of columns for this model.
+        """
+        raise NotImplementedError
+
+    def on_get_column_type(self, index):
+        """Overridable.
+
+        :Returns:
+            The column type for the given index.
+        """
+        raise NotImplementedError
+
+    def on_get_iter(self, path):
+        """Overridable.
+
+        :Returns:
+            A python object (node) for the given TreePath.
+        """
+        raise NotImplementedError
+
+    def on_iter_next(self, node):
+        """Overridable.
+
+        :Parameters:
+            node : object
+                Node at current level.
+
+        :Returns:
+            A python object (node) following the given node at the current level.
+        """
+        raise NotImplementedError
+
+    def on_get_path(self, node):
+        """Overridable.
+
+        :Returns:
+            A TreePath for the given node.
+        """
+        raise NotImplementedError
+
+    def on_get_value(self, node, column):
+        """Overridable.
+
+        :Parameters:
+            node : object
+            column : int
+                Column index to get the value from.
+
+        :Returns:
+            The value of the column for the given node."""
+        raise NotImplementedError
+
+    def on_iter_children(self, parent):
+        """Overridable.
+
+        :Returns:
+            The first child of parent or None if parent has no children.
+            If parent is None, return the first node of the model.
+        """
+        raise NotImplementedError
+
+    def on_iter_has_child(self, node):
+        """Overridable.
+
+        :Returns:
+            True if the given node has children.
+        """
+        raise NotImplementedError
+
+    def on_iter_n_children(self, node):
+        """Overridable.
+
+        :Returns:
+            The number of children for the given node. If node is None,
+            return the number of top level nodes.
+        """
+        raise NotImplementedError
+
+    def on_iter_nth_child(self, parent, n):
+        """Overridable.
+
+        :Parameters:
+            parent : object
+            n : int
+                Index of child within parent.
+
+        :Returns:
+            The child for the given parent index starting at 0. If parent None,
+            return the top level node corresponding to "n".
+            If "n" is larger then available nodes, return None.
+        """
+        raise NotImplementedError
+
+    def on_iter_parent(self, child):
+        """Overridable.
+
+        :Returns:
+            The parent node of child or None if child is a top level node."""
+        raise NotImplementedError
+
+    def on_ref_node(self, node):
+        pass
+
+    def on_unref_node(self, node):
+        pass
diff --git a/pygtkcompat/meson.build b/pygtkcompat/meson.build
new file mode 100644 (file)
index 0000000..9e43c44
--- /dev/null
@@ -0,0 +1,8 @@
+python_sources = [
+  '__init__.py',
+  'generictreemodel.py',
+  'pygtkcompat.py']
+
+python.install_sources(python_sources,
+  subdir : 'pygtkcompat'
+)
diff --git a/pygtkcompat/pygtkcompat.py b/pygtkcompat/pygtkcompat.py
new file mode 100644 (file)
index 0000000..85eb35b
--- /dev/null
@@ -0,0 +1,655 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2011-2012 Johan Dahlin <johan@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+"""
+PyGTK compatibility layer.
+
+This modules goes a little bit longer to maintain PyGTK compatibility than
+the normal overrides system.
+
+It is recommended to not depend on this layer, but only use it as an
+intermediate step when porting your application to PyGI.
+
+Compatibility might never be 100%, but the aim is to make it possible to run
+a well behaved PyGTK application mostly unmodified on top of PyGI.
+
+"""
+
+import sys
+import warnings
+
+import gi
+from gi.repository import GObject
+from gi import _compat
+
+
+_patches = []
+_module_patches = []
+_unset = object()
+_enabled_registry = {}
+
+
+def _patch(obj, name, new_value):
+    old_value = getattr(obj, name, _unset)
+    setattr(obj, name, new_value)
+    _patches.append((obj, name, old_value))
+
+
+def _patch_module(name, new_value):
+    old_value = sys.modules.get(name, _unset)
+    sys.modules[name] = new_value
+    _module_patches.append((name, old_value))
+
+
+def _install_enums(module, dest=None, strip=''):
+    if dest is None:
+        dest = module
+    modname = dest.__name__.rsplit('.', 1)[1].upper()
+    for attr in dir(module):
+        try:
+            obj = getattr(module, attr, None)
+        except:
+            continue
+        try:
+            if issubclass(obj, GObject.GEnum):
+                for value, enum in obj.__enum_values__.items():
+                    name = enum.value_name
+                    name = name.replace(modname + '_', '')
+                    if strip and name.startswith(strip):
+                        name = name[len(strip):]
+                    _patch(dest, name, enum)
+        except TypeError:
+            continue
+        try:
+            if issubclass(obj, GObject.GFlags):
+                for value, flag in obj.__flags_values__.items():
+                    try:
+                        name = flag.value_names[-1].replace(modname + '_', '')
+                    except IndexError:
+                        # FIXME: this happens for some large flags which do not
+                        # fit into a long on 32 bit systems
+                        continue
+                    _patch(dest, name, flag)
+        except TypeError:
+            continue
+
+
+def _check_enabled(name, version=None):
+    """Returns True in case it is already enabled"""
+
+    if name in _enabled_registry:
+        enabled_version = _enabled_registry[name]
+        if enabled_version != version:
+            raise ValueError(
+                "%r already enabled with different version (%r)" % (
+                    name, enabled_version))
+        return True
+    else:
+        _enabled_registry[name] = version
+        return False
+
+
+def enable():
+    if _check_enabled(""):
+        return
+
+    # gobject
+    from gi.repository import GLib
+    _patch_module('glib', GLib)
+
+    # gobject
+    from gi.repository import GObject
+    _patch_module('gobject', GObject)
+    from gi import _propertyhelper
+    _patch_module('gobject.propertyhelper', _propertyhelper)
+
+    # gio
+    from gi.repository import Gio
+    _patch_module('gio', Gio)
+
+
+def _disable_all():
+    """Reverse all effects of the enable_xxx() calls except for
+    require_version() calls and imports.
+    """
+
+    _enabled_registry.clear()
+
+    for obj, name, old_value in reversed(_patches):
+        if old_value is _unset:
+            delattr(obj, name)
+        else:
+            # try if deleting is enough (for override proxies)
+            delattr(obj, name)
+            if getattr(obj, name, _unset) is not old_value:
+                setattr(obj, name, old_value)
+    del _patches[:]
+
+    for name, old_value in reversed(_module_patches):
+        if old_value is _unset:
+            del sys.modules[name]
+        else:
+            sys.modules[name] = old_value
+    del _module_patches[:]
+
+    _compat.reload(sys)
+    if _compat.PY2:
+        sys.setdefaultencoding('ascii')
+
+
+def enable_gtk(version='3.0'):
+    if _check_enabled("gtk", version):
+        return
+
+    if version == "4.0":
+        raise ValueError("version 4.0 not supported")
+
+    # set the default encoding like PyGTK
+    _compat.reload(sys)
+    if _compat.PY2:
+        sys.setdefaultencoding('utf-8')
+
+    # atk
+    gi.require_version('Atk', '1.0')
+    from gi.repository import Atk
+    _patch_module('atk', Atk)
+    _install_enums(Atk)
+
+    # pango
+    gi.require_version('Pango', '1.0')
+    from gi.repository import Pango
+    _patch_module('pango', Pango)
+    _install_enums(Pango)
+
+    # pangocairo
+    gi.require_version('PangoCairo', '1.0')
+    from gi.repository import PangoCairo
+    _patch_module('pangocairo', PangoCairo)
+
+    # gdk
+    gi.require_version('Gdk', version)
+    gi.require_version('GdkPixbuf', '2.0')
+    from gi.repository import Gdk
+    from gi.repository import GdkPixbuf
+    _patch_module('gtk.gdk', Gdk)
+    _install_enums(Gdk)
+    _install_enums(GdkPixbuf, dest=Gdk)
+    _patch(Gdk, "_2BUTTON_PRESS", 5)
+    _patch(Gdk, "BUTTON_PRESS", 4)
+
+    _patch(Gdk, "screen_get_default", Gdk.Screen.get_default)
+    _patch(Gdk, "Pixbuf", GdkPixbuf.Pixbuf)
+    _patch(Gdk, "PixbufLoader", GdkPixbuf.PixbufLoader.new_with_type)
+    _patch(Gdk, "pixbuf_new_from_data", GdkPixbuf.Pixbuf.new_from_data)
+    _patch(Gdk, "pixbuf_new_from_file", GdkPixbuf.Pixbuf.new_from_file)
+    try:
+        _patch(Gdk, "pixbuf_new_from_file_at_scale", GdkPixbuf.Pixbuf.new_from_file_at_scale)
+    except AttributeError:
+        pass
+    _patch(Gdk, "pixbuf_new_from_file_at_size", GdkPixbuf.Pixbuf.new_from_file_at_size)
+    _patch(Gdk, "pixbuf_new_from_inline", GdkPixbuf.Pixbuf.new_from_inline)
+    _patch(Gdk, "pixbuf_new_from_stream", GdkPixbuf.Pixbuf.new_from_stream)
+    _patch(Gdk, "pixbuf_new_from_stream_at_scale", GdkPixbuf.Pixbuf.new_from_stream_at_scale)
+    _patch(Gdk, "pixbuf_new_from_xpm_data", GdkPixbuf.Pixbuf.new_from_xpm_data)
+    _patch(Gdk, "pixbuf_get_file_info", GdkPixbuf.Pixbuf.get_file_info)
+
+    orig_get_formats = GdkPixbuf.Pixbuf.get_formats
+
+    def get_formats():
+        formats = orig_get_formats()
+        result = []
+
+        def make_dict(format_):
+            result = {}
+            result['description'] = format_.get_description()
+            result['name'] = format_.get_name()
+            result['mime_types'] = format_.get_mime_types()
+            result['extensions'] = format_.get_extensions()
+            return result
+
+        for format_ in formats:
+            result.append(make_dict(format_))
+        return result
+
+    _patch(Gdk, "pixbuf_get_formats", get_formats)
+
+    orig_get_frame_extents = Gdk.Window.get_frame_extents
+
+    def get_frame_extents(window):
+        try:
+            try:
+                rect = Gdk.Rectangle(0, 0, 0, 0)
+            except TypeError:
+                rect = Gdk.Rectangle()
+            orig_get_frame_extents(window, rect)
+        except TypeError:
+            rect = orig_get_frame_extents(window)
+        return rect
+    _patch(Gdk.Window, "get_frame_extents", get_frame_extents)
+
+    orig_get_origin = Gdk.Window.get_origin
+
+    def get_origin(self):
+        return orig_get_origin(self)[1:]
+    _patch(Gdk.Window, "get_origin", get_origin)
+
+    _patch(Gdk, "screen_width", Gdk.Screen.width)
+    _patch(Gdk, "screen_height", Gdk.Screen.height)
+
+    orig_gdk_window_get_geometry = Gdk.Window.get_geometry
+
+    def gdk_window_get_geometry(window):
+        return orig_gdk_window_get_geometry(window) + (window.get_visual().get_best_depth(),)
+    _patch(Gdk.Window, "get_geometry", gdk_window_get_geometry)
+
+    # gtk
+    gi.require_version('Gtk', version)
+    from gi.repository import Gtk
+    _patch_module('gtk', Gtk)
+    _patch(Gtk, "gdk", Gdk)
+
+    _patch(Gtk, "pygtk_version", (2, 99, 0))
+
+    _patch(Gtk, "gtk_version", (Gtk.MAJOR_VERSION,
+                                Gtk.MINOR_VERSION,
+                                Gtk.MICRO_VERSION))
+    _install_enums(Gtk)
+
+    # Action
+
+    def set_tool_item_type(menuaction, gtype):
+        warnings.warn('set_tool_item_type() is not supported',
+                      gi.PyGIDeprecationWarning, stacklevel=2)
+    _patch(Gtk.Action, "set_tool_item_type", classmethod(set_tool_item_type))
+
+    # Alignment
+
+    orig_Alignment = Gtk.Alignment
+
+    class Alignment(orig_Alignment):
+        def __init__(self, xalign=0.0, yalign=0.0, xscale=0.0, yscale=0.0):
+            orig_Alignment.__init__(self)
+            self.props.xalign = xalign
+            self.props.yalign = yalign
+            self.props.xscale = xscale
+            self.props.yscale = yscale
+
+    _patch(Gtk, "Alignment", Alignment)
+
+    # Box
+
+    orig_pack_end = Gtk.Box.pack_end
+
+    def pack_end(self, child, expand=True, fill=True, padding=0):
+        orig_pack_end(self, child, expand, fill, padding)
+    _patch(Gtk.Box, "pack_end", pack_end)
+
+    orig_pack_start = Gtk.Box.pack_start
+
+    def pack_start(self, child, expand=True, fill=True, padding=0):
+        orig_pack_start(self, child, expand, fill, padding)
+    _patch(Gtk.Box, "pack_start", pack_start)
+
+    # TreeViewColumn
+
+    orig_tree_view_column_pack_end = Gtk.TreeViewColumn.pack_end
+
+    def tree_view_column_pack_end(self, cell, expand=True):
+        orig_tree_view_column_pack_end(self, cell, expand)
+    _patch(Gtk.TreeViewColumn, "pack_end", tree_view_column_pack_end)
+
+    orig_tree_view_column_pack_start = Gtk.TreeViewColumn.pack_start
+
+    def tree_view_column_pack_start(self, cell, expand=True):
+        orig_tree_view_column_pack_start(self, cell, expand)
+    _patch(Gtk.TreeViewColumn, "pack_start", tree_view_column_pack_start)
+
+    # CellLayout
+
+    orig_cell_pack_end = Gtk.CellLayout.pack_end
+
+    def cell_pack_end(self, cell, expand=True):
+        orig_cell_pack_end(self, cell, expand)
+    _patch(Gtk.CellLayout, "pack_end", cell_pack_end)
+
+    orig_cell_pack_start = Gtk.CellLayout.pack_start
+
+    def cell_pack_start(self, cell, expand=True):
+        orig_cell_pack_start(self, cell, expand)
+    _patch(Gtk.CellLayout, "pack_start", cell_pack_start)
+
+    orig_set_cell_data_func = Gtk.CellLayout.set_cell_data_func
+
+    def set_cell_data_func(self, cell, func, user_data=_unset):
+        def callback(*args):
+            if args[-1] == _unset:
+                args = args[:-1]
+            return func(*args)
+        orig_set_cell_data_func(self, cell, callback, user_data)
+    _patch(Gtk.CellLayout, "set_cell_data_func", set_cell_data_func)
+
+    # CellRenderer
+
+    class GenericCellRenderer(Gtk.CellRenderer):
+        pass
+    _patch(Gtk, "GenericCellRenderer", GenericCellRenderer)
+
+    # ComboBox
+
+    orig_combo_row_separator_func = Gtk.ComboBox.set_row_separator_func
+
+    def combo_row_separator_func(self, func, user_data=_unset):
+        def callback(*args):
+            if args[-1] == _unset:
+                args = args[:-1]
+            return func(*args)
+        orig_combo_row_separator_func(self, callback, user_data)
+    _patch(Gtk.ComboBox, "set_row_separator_func", combo_row_separator_func)
+
+    # ComboBoxEntry
+
+    class ComboBoxEntry(Gtk.ComboBox):
+        def __init__(self, **kwds):
+            Gtk.ComboBox.__init__(self, has_entry=True, **kwds)
+
+        def set_text_column(self, text_column):
+            self.set_entry_text_column(text_column)
+
+        def get_text_column(self):
+            return self.get_entry_text_column()
+    _patch(Gtk, "ComboBoxEntry", ComboBoxEntry)
+
+    def combo_box_entry_new():
+        return Gtk.ComboBoxEntry()
+    _patch(Gtk, "combo_box_entry_new", combo_box_entry_new)
+
+    def combo_box_entry_new_with_model(model):
+        return Gtk.ComboBoxEntry(model=model)
+    _patch(Gtk, "combo_box_entry_new_with_model", combo_box_entry_new_with_model)
+
+    # Container
+
+    def install_child_property(container, flag, pspec):
+        warnings.warn('install_child_property() is not supported',
+                      gi.PyGIDeprecationWarning, stacklevel=2)
+    _patch(Gtk.Container, "install_child_property", classmethod(install_child_property))
+
+    def new_text():
+        combo = Gtk.ComboBox()
+        model = Gtk.ListStore(str)
+        combo.set_model(model)
+        combo.set_entry_text_column(0)
+        return combo
+    _patch(Gtk, "combo_box_new_text", new_text)
+
+    def append_text(self, text):
+        model = self.get_model()
+        model.append([text])
+    _patch(Gtk.ComboBox, "append_text", append_text)
+    _patch(Gtk, "expander_new_with_mnemonic", Gtk.Expander.new_with_mnemonic)
+    _patch(Gtk, "icon_theme_get_default", Gtk.IconTheme.get_default)
+    _patch(Gtk, "image_new_from_pixbuf", Gtk.Image.new_from_pixbuf)
+    _patch(Gtk, "image_new_from_stock", Gtk.Image.new_from_stock)
+    _patch(Gtk, "image_new_from_animation", Gtk.Image.new_from_animation)
+    _patch(Gtk, "image_new_from_icon_set", Gtk.Image.new_from_icon_set)
+    _patch(Gtk, "image_new_from_file", Gtk.Image.new_from_file)
+    _patch(Gtk, "settings_get_default", Gtk.Settings.get_default)
+    _patch(Gtk, "window_set_default_icon", Gtk.Window.set_default_icon)
+    try:
+        _patch(Gtk, "clipboard_get", Gtk.Clipboard.get)
+    except AttributeError:
+        pass
+
+    # AccelGroup
+    _patch(Gtk.AccelGroup, "connect_group", Gtk.AccelGroup.connect)
+
+    # StatusIcon
+    _patch(Gtk, "status_icon_position_menu", Gtk.StatusIcon.position_menu)
+    _patch(Gtk.StatusIcon, "set_tooltip", Gtk.StatusIcon.set_tooltip_text)
+
+    # Scale
+
+    orig_HScale = Gtk.HScale
+    orig_VScale = Gtk.VScale
+
+    class HScale(orig_HScale):
+        def __init__(self, adjustment=None):
+            orig_HScale.__init__(self, adjustment=adjustment)
+    _patch(Gtk, "HScale", HScale)
+
+    class VScale(orig_VScale):
+        def __init__(self, adjustment=None):
+            orig_VScale.__init__(self, adjustment=adjustment)
+    _patch(Gtk, "VScale", VScale)
+
+    _patch(Gtk, "stock_add", lambda items: None)
+
+    # Widget
+
+    _patch(Gtk.Widget, "window", property(fget=Gtk.Widget.get_window))
+
+    _patch(Gtk, "widget_get_default_direction", Gtk.Widget.get_default_direction)
+    orig_size_request = Gtk.Widget.size_request
+
+    def size_request(widget):
+        class SizeRequest(_compat.UserList):
+            def __init__(self, req):
+                self.height = req.height
+                self.width = req.width
+                _compat.UserList.__init__(self, [self.width, self.height])
+        return SizeRequest(orig_size_request(widget))
+    _patch(Gtk.Widget, "size_request", size_request)
+    _patch(Gtk.Widget, "hide_all", Gtk.Widget.hide)
+
+    class BaseGetter(object):
+        def __init__(self, context):
+            self.context = context
+
+        def __getitem__(self, state):
+            color = self.context.get_background_color(state)
+            return Gdk.Color(red=int(color.red * 65535),
+                             green=int(color.green * 65535),
+                             blue=int(color.blue * 65535))
+
+    class Styles(object):
+        def __init__(self, widget):
+            context = widget.get_style_context()
+            self.base = BaseGetter(context)
+            self.black = Gdk.Color(red=0, green=0, blue=0)
+
+    class StyleDescriptor(object):
+        def __get__(self, instance, class_):
+            return Styles(instance)
+    _patch(Gtk.Widget, "style", StyleDescriptor())
+
+    # TextView
+
+    orig_text_view_scroll_to_mark = Gtk.TextView.scroll_to_mark
+
+    def text_view_scroll_to_mark(self, mark, within_margin,
+                                 use_align=False, xalign=0.5, yalign=0.5):
+        return orig_text_view_scroll_to_mark(self, mark, within_margin,
+                                             use_align, xalign, yalign)
+    _patch(Gtk.TextView, "scroll_to_mark", text_view_scroll_to_mark)
+
+    # Window
+
+    orig_set_geometry_hints = Gtk.Window.set_geometry_hints
+
+    def set_geometry_hints(self, geometry_widget=None,
+                           min_width=-1, min_height=-1, max_width=-1, max_height=-1,
+                           base_width=-1, base_height=-1, width_inc=-1, height_inc=-1,
+                           min_aspect=-1.0, max_aspect=-1.0):
+
+        geometry = Gdk.Geometry()
+        geom_mask = Gdk.WindowHints(0)
+
+        if min_width >= 0 or min_height >= 0:
+            geometry.min_width = max(min_width, 0)
+            geometry.min_height = max(min_height, 0)
+            geom_mask |= Gdk.WindowHints.MIN_SIZE
+
+        if max_width >= 0 or max_height >= 0:
+            geometry.max_width = max(max_width, 0)
+            geometry.max_height = max(max_height, 0)
+            geom_mask |= Gdk.WindowHints.MAX_SIZE
+
+        if base_width >= 0 or base_height >= 0:
+            geometry.base_width = max(base_width, 0)
+            geometry.base_height = max(base_height, 0)
+            geom_mask |= Gdk.WindowHints.BASE_SIZE
+
+        if width_inc >= 0 or height_inc >= 0:
+            geometry.width_inc = max(width_inc, 0)
+            geometry.height_inc = max(height_inc, 0)
+            geom_mask |= Gdk.WindowHints.RESIZE_INC
+
+        if min_aspect >= 0.0 or max_aspect >= 0.0:
+            if min_aspect <= 0.0 or max_aspect <= 0.0:
+                raise TypeError("aspect ratios must be positive")
+
+            geometry.min_aspect = min_aspect
+            geometry.max_aspect = max_aspect
+            geom_mask |= Gdk.WindowHints.ASPECT
+
+        return orig_set_geometry_hints(self, geometry_widget, geometry, geom_mask)
+
+    _patch(Gtk.Window, "set_geometry_hints", set_geometry_hints)
+    _patch(Gtk, "window_list_toplevels", Gtk.Window.list_toplevels)
+    _patch(Gtk, "window_set_default_icon_name", Gtk.Window.set_default_icon_name)
+
+    # gtk.unixprint
+
+    class UnixPrint(object):
+        pass
+    unixprint = UnixPrint()
+    _patch_module('gtkunixprint', unixprint)
+
+    # gtk.keysyms
+
+    with warnings.catch_warnings():
+        warnings.simplefilter('ignore', category=RuntimeWarning)
+        from gi.overrides import keysyms
+
+    _patch_module('gtk.keysyms', keysyms)
+    _patch(Gtk, "keysyms", keysyms)
+
+    from . import generictreemodel
+    _patch(Gtk, "GenericTreeModel", generictreemodel.GenericTreeModel)
+
+
+def enable_vte():
+    if _check_enabled("vte"):
+        return
+
+    gi.require_version('Vte', '0.0')
+    from gi.repository import Vte
+    _patch_module('vte', Vte)
+
+
+def enable_poppler():
+    if _check_enabled("poppler"):
+        return
+
+    gi.require_version('Poppler', '0.18')
+    from gi.repository import Poppler
+    _patch_module('poppler', Poppler)
+
+    _patch(Poppler, "pypoppler_version", (1, 0, 0))
+
+
+def enable_webkit(version='1.0'):
+    if _check_enabled("webkit", version):
+        return
+
+    gi.require_version('WebKit', version)
+    from gi.repository import WebKit
+    _patch_module('webkit', WebKit)
+
+    _patch(WebKit.WebView, "get_web_inspector", WebKit.WebView.get_inspector)
+
+
+def enable_gudev():
+    if _check_enabled("gudev"):
+        return
+
+    gi.require_version('GUdev', '1.0')
+    from gi.repository import GUdev
+    _patch_module('gudev', GUdev)
+
+
+def enable_gst():
+    if _check_enabled("gst"):
+        return
+
+    gi.require_version('Gst', '0.10')
+    from gi.repository import Gst
+    _patch_module('gst', Gst)
+    _install_enums(Gst)
+
+    _patch(Gst, "registry_get_default", Gst.Registry.get_default)
+    _patch(Gst, "element_register", Gst.Element.register)
+    _patch(Gst, "element_factory_make", Gst.ElementFactory.make)
+    _patch(Gst, "caps_new_any", Gst.Caps.new_any)
+    _patch(Gst, "get_pygst_version", lambda: (0, 10, 19))
+    _patch(Gst, "get_gst_version", lambda: (0, 10, 40))
+
+    from gi.repository import GstInterfaces
+    _patch_module('gst.interfaces', GstInterfaces)
+    _install_enums(GstInterfaces)
+
+    from gi.repository import GstAudio
+    _patch_module('gst.audio', GstAudio)
+    _install_enums(GstAudio)
+
+    from gi.repository import GstVideo
+    _patch_module('gst.video', GstVideo)
+    _install_enums(GstVideo)
+
+    from gi.repository import GstBase
+    _patch_module('gst.base', GstBase)
+    _install_enums(GstBase)
+
+    _patch(Gst, "BaseTransform", GstBase.BaseTransform)
+    _patch(Gst, "BaseSink", GstBase.BaseSink)
+
+    from gi.repository import GstController
+    _patch_module('gst.controller', GstController)
+    _install_enums(GstController, dest=Gst)
+
+    from gi.repository import GstPbutils
+    _patch_module('gst.pbutils', GstPbutils)
+    _install_enums(GstPbutils)
+
+
+def enable_goocanvas():
+    if _check_enabled("goocanvas"):
+        return
+
+    gi.require_version('GooCanvas', '2.0')
+    from gi.repository import GooCanvas
+    _patch_module('goocanvas', GooCanvas)
+    _install_enums(GooCanvas, strip='GOO_CANVAS_')
+
+    _patch(GooCanvas, "ItemSimple", GooCanvas.CanvasItemSimple)
+    _patch(GooCanvas, "Item", GooCanvas.CanvasItem)
+    _patch(GooCanvas, "Image", GooCanvas.CanvasImage)
+    _patch(GooCanvas, "Group", GooCanvas.CanvasGroup)
+    _patch(GooCanvas, "Rect", GooCanvas.CanvasRect)
diff --git a/setup.cfg b/setup.cfg
new file mode 100644 (file)
index 0000000..2f80f59
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,15 @@
+[flake8]
+ignore = E501,E123,E124,E402,E731,E722
+exclude = subprojects
+
+[coverage:run]
+branch = True
+include = 
+       gi/*
+       tests/*
+       pygtkcompat/*
+
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff --git a/setup.py b/setup.py
new file mode 100755 (executable)
index 0000000..4640979
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,1189 @@
+#!/usr/bin/env python3
+# Copyright 2017 Christoph Reiter <reiter.christoph@gmail.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+import io
+import os
+import sys
+import errno
+import subprocess
+import tarfile
+import sysconfig
+import tempfile
+from email import parser
+
+try:
+    from setuptools import setup
+except ImportError:
+    from distutils.core import setup
+
+from distutils.core import Extension, Distribution, Command
+from distutils.errors import DistutilsSetupError, DistutilsOptionError
+from distutils.ccompiler import new_compiler
+from distutils.sysconfig import get_python_lib, customize_compiler
+from distutils import dir_util, log
+from distutils.spawn import find_executable
+
+
+PYGOBJECT_VERISON = "3.30.1"
+GLIB_VERSION_REQUIRED = "2.38.0"
+GI_VERSION_REQUIRED = "1.46.0"
+PYCAIRO_VERSION_REQUIRED = "1.11.1"
+LIBFFI_VERSION_REQUIRED = "3.0"
+
+
+def is_dev_version():
+    version = tuple(map(int, PYGOBJECT_VERISON.split(".")))
+    return version[1] % 2 != 0
+
+
+def get_command_class(name):
+    # Returns the right class for either distutils or setuptools
+    return Distribution({}).get_command_class(name)
+
+
+def get_pycairo_pkg_config_name():
+    return "py3cairo" if sys.version_info[0] == 3 else "pycairo"
+
+
+def get_version_requirement(pkg_config_name):
+    """Given a pkg-config module name gets the minimum version required"""
+
+    versions = {
+        "gobject-introspection-1.0": GI_VERSION_REQUIRED,
+        "glib-2.0": GLIB_VERSION_REQUIRED,
+        "gio-2.0": GLIB_VERSION_REQUIRED,
+        get_pycairo_pkg_config_name(): PYCAIRO_VERSION_REQUIRED,
+        "libffi": LIBFFI_VERSION_REQUIRED,
+        "cairo": "0",
+        "cairo-gobject": "0",
+    }
+
+    return versions[pkg_config_name]
+
+
+def get_versions():
+    version = PYGOBJECT_VERISON.split(".")
+    assert len(version) == 3
+
+    versions = {
+        "PYGOBJECT_MAJOR_VERSION": version[0],
+        "PYGOBJECT_MINOR_VERSION": version[1],
+        "PYGOBJECT_MICRO_VERSION": version[2],
+        "VERSION": ".".join(version),
+    }
+    return versions
+
+
+def parse_pkg_info(conf_dir):
+    """Returns an email.message.Message instance containing the content
+    of the PKG-INFO file.
+    """
+
+    versions = get_versions()
+
+    pkg_info = os.path.join(conf_dir, "PKG-INFO.in")
+    with io.open(pkg_info, "r", encoding="utf-8") as h:
+        text = h.read()
+        for key, value in versions.items():
+            text = text.replace("@%s@" % key, value)
+
+    p = parser.Parser()
+    message = p.parse(io.StringIO(text))
+    return message
+
+
+def pkg_config_get_install_hint(pkg_name):
+    """Returns an installation hint for a pkg-config name or None"""
+
+    if not sys.platform.startswith("linux"):
+        return
+
+    if find_executable("apt"):
+        dev_packages = {
+            "gobject-introspection-1.0": "libgirepository1.0-dev",
+            "glib-2.0": "libglib2.0-dev",
+            "gio-2.0": "libglib2.0-dev",
+            "cairo": "libcairo2-dev",
+            "cairo-gobject": "libcairo2-dev",
+            "libffi": "libffi-dev",
+        }
+        if pkg_name in dev_packages:
+            return "sudo apt install %s" % dev_packages[pkg_name]
+    elif find_executable("dnf"):
+        dev_packages = {
+            "gobject-introspection-1.0": "gobject-introspection-devel",
+            "glib-2.0": "glib2-devel",
+            "gio-2.0": "glib2-devel",
+            "cairo": "cairo-devel",
+            "cairo-gobject": "cairo-gobject-devel",
+            "libffi": "libffi-devel",
+        }
+        if pkg_name in dev_packages:
+            return "sudo dnf install %s" % dev_packages[pkg_name]
+
+
+class PkgConfigError(Exception):
+    pass
+
+
+class PkgConfigMissingPackageError(PkgConfigError):
+    pass
+
+
+def _run_pkg_config(pkg_name, args, _cache={}):
+    """Raises PkgConfigError"""
+
+    command = tuple(["pkg-config"] + args)
+
+    if command not in _cache:
+        try:
+            result = subprocess.check_output(command)
+        except OSError as e:
+            if e.errno == errno.ENOENT:
+                raise PkgConfigError(
+                    "%r not found.\nArguments: %r" % (command[0], command))
+            raise PkgConfigError(e)
+        except subprocess.CalledProcessError as e:
+            try:
+                subprocess.check_output(["pkg-config", "--exists", pkg_name])
+            except (subprocess.CalledProcessError, OSError):
+                raise PkgConfigMissingPackageError(e)
+            else:
+                raise PkgConfigError(e)
+        else:
+            _cache[command] = result
+
+    return _cache[command]
+
+
+def _run_pkg_config_or_exit(pkg_name, args):
+    try:
+        return _run_pkg_config(pkg_name, args)
+    except PkgConfigMissingPackageError as e:
+        hint = pkg_config_get_install_hint(pkg_name)
+        if hint:
+            raise SystemExit(
+                "%s\n\nTry installing it with: %r" % (e, hint))
+        else:
+            raise SystemExit(e)
+    except PkgConfigError as e:
+        raise SystemExit(e)
+
+
+def pkg_config_version_check(pkg_name, version):
+    _run_pkg_config_or_exit(pkg_name, [
+        "--print-errors",
+        "--exists",
+        '%s >= %s' % (pkg_name, version),
+    ])
+
+
+def pkg_config_parse(opt, pkg_name):
+    ret = _run_pkg_config_or_exit(pkg_name, [opt, pkg_name])
+
+    if sys.version_info[0] == 3:
+        output = ret.decode()
+    else:
+        output = ret
+    opt = opt[-2:]
+    return [x.lstrip(opt) for x in output.split()]
+
+
+def list_headers(d):
+    return [os.path.join(d, e) for e in os.listdir(d) if e.endswith(".h")]
+
+
+def filter_compiler_arguments(compiler, args):
+    """Given a compiler instance and a list of compiler warning flags
+    returns the list of supported flags.
+    """
+
+    if compiler.compiler_type == "msvc":
+        # TODO
+        return []
+
+    extra = []
+
+    def check_arguments(compiler, args):
+        p = subprocess.Popen(
+            [compiler.compiler[0]] + args + extra + ["-x", "c", "-E", "-"],
+            stdin=subprocess.PIPE,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE)
+        stdout, stderr = p.communicate(b"int i;\n")
+        if p.returncode != 0:
+            text = stderr.decode("ascii", "replace")
+            return False, [a for a in args if a in text]
+        else:
+            return True, []
+
+    def check_argument(compiler, arg):
+        return check_arguments(compiler, [arg])[0]
+
+    # clang doesn't error out for unknown options, force it to
+    if check_argument(compiler, '-Werror=unknown-warning-option'):
+        extra += ['-Werror=unknown-warning-option']
+    if check_argument(compiler, '-Werror=unused-command-line-argument'):
+        extra += ['-Werror=unused-command-line-argument']
+
+    # first try to remove all arguments contained in the error message
+    supported = list(args)
+    while 1:
+        ok, maybe_unknown = check_arguments(compiler, supported)
+        if ok:
+            return supported
+        elif not maybe_unknown:
+            break
+        for unknown in maybe_unknown:
+            if not check_argument(compiler, unknown):
+                supported.remove(unknown)
+
+    # hm, didn't work, try each argument one by one
+    supported = []
+    for arg in args:
+        if check_argument(compiler, arg):
+            supported.append(arg)
+    return supported
+
+
+class sdist_gnome(Command):
+    description = "Create a source tarball for GNOME"
+    user_options = []
+
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        # Don't use PEP 440 pre-release versions for GNOME releases
+        self.distribution.metadata.version = PYGOBJECT_VERISON
+
+        dist_dir = tempfile.mkdtemp()
+        try:
+            cmd = self.reinitialize_command("sdist")
+            cmd.dist_dir = dist_dir
+            cmd.ensure_finalized()
+            cmd.run()
+
+            base_name = self.distribution.get_fullname().lower()
+            cmd.make_release_tree(base_name, cmd.filelist.files)
+            try:
+                self.make_archive(base_name, "xztar", base_dir=base_name)
+            finally:
+                dir_util.remove_tree(base_name)
+        finally:
+            dir_util.remove_tree(dist_dir)
+
+
+du_sdist = get_command_class("sdist")
+
+
+class distcheck(du_sdist):
+    """Creates a tarball and does some additional sanity checks such as
+    checking if the tarball includes all files, builds successfully and
+    the tests suite passes.
+    """
+
+    def _check_manifest(self):
+        # make sure MANIFEST.in includes all tracked files
+        assert self.get_archive_files()
+
+        if subprocess.call(["git", "status"],
+                           stdout=subprocess.PIPE,
+                           stderr=subprocess.PIPE) != 0:
+            return
+
+        included_files = self.filelist.files
+        assert included_files
+
+        process = subprocess.Popen(
+            ["git", "ls-tree", "-r", "HEAD", "--name-only"],
+            stdout=subprocess.PIPE, universal_newlines=True)
+        out, err = process.communicate()
+        assert process.returncode == 0
+
+        tracked_files = out.splitlines()
+        tracked_files = [
+            f for f in tracked_files
+            if os.path.basename(f) not in [".gitignore"]]
+
+        diff = set(tracked_files) - set(included_files)
+        assert not diff, (
+            "Not all tracked files included in tarball, check MANIFEST.in",
+            diff)
+
+    def _check_dist(self):
+        # make sure the tarball builds
+        assert self.get_archive_files()
+
+        distcheck_dir = os.path.abspath(
+            os.path.join(self.dist_dir, "distcheck"))
+        if os.path.exists(distcheck_dir):
+            dir_util.remove_tree(distcheck_dir)
+        self.mkpath(distcheck_dir)
+
+        archive = self.get_archive_files()[0]
+        tfile = tarfile.open(archive, "r:gz")
+        tfile.extractall(distcheck_dir)
+        tfile.close()
+
+        name = self.distribution.get_fullname()
+        extract_dir = os.path.join(distcheck_dir, name)
+
+        old_pwd = os.getcwd()
+        os.chdir(extract_dir)
+        try:
+            self.spawn([sys.executable, "setup.py", "build"])
+            self.spawn([sys.executable, "setup.py", "install",
+                        "--root",
+                        os.path.join(distcheck_dir, "prefix"),
+                        "--record",
+                        os.path.join(distcheck_dir, "log.txt"),
+                        ])
+            self.spawn([sys.executable, "setup.py", "test"])
+        finally:
+            os.chdir(old_pwd)
+
+    def run(self):
+        du_sdist.run(self)
+        self._check_manifest()
+        self._check_dist()
+
+
+class build_tests(Command):
+    description = "build test libraries and extensions"
+    user_options = [
+        ("force", "f", "force a rebuild"),
+    ]
+
+    def initialize_options(self):
+        self.build_temp = None
+        self.build_base = None
+        self.force = False
+
+    def finalize_options(self):
+        self.set_undefined_options(
+            'build_ext',
+            ('build_temp', 'build_temp'))
+        self.set_undefined_options(
+            'build',
+            ('build_base', 'build_base'))
+
+    def _newer_group(self, sources, *targets):
+        assert targets
+
+        from distutils.dep_util import newer_group
+
+        if self.force:
+            return True
+        else:
+            for target in targets:
+                if not newer_group(sources, target):
+                    return False
+            return True
+
+    def run(self):
+        cmd = self.reinitialize_command("build_ext")
+        cmd.inplace = True
+        cmd.force = self.force
+        cmd.ensure_finalized()
+        cmd.run()
+
+        gidatadir = pkg_config_parse(
+            "--variable=gidatadir", "gobject-introspection-1.0")[0]
+        g_ir_scanner = pkg_config_parse(
+            "--variable=g_ir_scanner", "gobject-introspection-1.0")[0]
+        g_ir_compiler = pkg_config_parse(
+            "--variable=g_ir_compiler", "gobject-introspection-1.0")[0]
+
+        script_dir = get_script_dir()
+        gi_dir = os.path.join(script_dir, "gi")
+        tests_dir = os.path.join(script_dir, "tests")
+        gi_tests_dir = os.path.join(gidatadir, "tests")
+
+        schema_xml = os.path.join(tests_dir, "org.gnome.test.gschema.xml")
+        schema_bin = os.path.join(tests_dir, "gschemas.compiled")
+        if self._newer_group([schema_xml], schema_bin):
+            subprocess.check_call([
+                "glib-compile-schemas",
+                "--targetdir=%s" % tests_dir,
+                "--schema-file=%s" % schema_xml,
+            ])
+
+        compiler = new_compiler()
+        customize_compiler(compiler)
+
+        if os.name == "nt":
+            compiler.shared_lib_extension = ".dll"
+        elif sys.platform == "darwin":
+            compiler.shared_lib_extension = ".dylib"
+            if "-bundle" in compiler.linker_so:
+                compiler.linker_so = list(compiler.linker_so)
+                i = compiler.linker_so.index("-bundle")
+                compiler.linker_so[i] = "-dynamiclib"
+        else:
+            compiler.shared_lib_extension = ".so"
+
+        def build_ext(ext):
+            if compiler.compiler_type == "msvc":
+                raise Exception("MSVC support not implemented")
+
+            libname = compiler.shared_object_filename(ext.name)
+            ext_paths = [os.path.join(tests_dir, libname)]
+            if os.name == "nt":
+                implibname = libname + ".a"
+                ext_paths.append(os.path.join(tests_dir, implibname))
+
+            if self._newer_group(ext.sources + ext.depends, *ext_paths):
+                objects = compiler.compile(
+                    ext.sources,
+                    output_dir=self.build_temp,
+                    include_dirs=ext.include_dirs)
+
+                if os.name == "nt":
+                    postargs = ["-Wl,--out-implib=%s" %
+                                os.path.join(tests_dir, implibname)]
+                else:
+                    postargs = []
+
+                compiler.link_shared_object(
+                    objects,
+                    compiler.shared_object_filename(ext.name),
+                    output_dir=tests_dir,
+                    libraries=ext.libraries,
+                    library_dirs=ext.library_dirs,
+                    extra_postargs=postargs)
+
+            return ext_paths
+
+        ext = Extension(
+            name='libgimarshallingtests',
+            sources=[
+                os.path.join(gi_tests_dir, "gimarshallingtests.c"),
+                os.path.join(tests_dir, "gimarshallingtestsextra.c"),
+            ],
+            include_dirs=[
+                gi_tests_dir,
+                tests_dir,
+            ],
+            depends=[
+                os.path.join(gi_tests_dir, "gimarshallingtests.h"),
+                os.path.join(tests_dir, "gimarshallingtestsextra.h"),
+            ],
+        )
+        add_ext_pkg_config_dep(ext, compiler.compiler_type, "glib-2.0")
+        add_ext_pkg_config_dep(ext, compiler.compiler_type, "gio-2.0")
+        ext_paths = build_ext(ext)
+
+        gir_path = os.path.join(tests_dir, "GIMarshallingTests-1.0.gir")
+        typelib_path = os.path.join(
+            tests_dir, "GIMarshallingTests-1.0.typelib")
+
+        if self._newer_group(ext_paths, gir_path):
+            subprocess.check_call([
+                g_ir_scanner,
+                "--no-libtool",
+                "--include=Gio-2.0",
+                "--namespace=GIMarshallingTests",
+                "--nsversion=1.0",
+                "--symbol-prefix=gi_marshalling_tests",
+                "--warn-all",
+                "--warn-error",
+                "--library-path=%s" % tests_dir,
+                "--library=gimarshallingtests",
+                "--pkg=glib-2.0",
+                "--pkg=gio-2.0",
+                "--cflags-begin",
+                "-I%s" % gi_tests_dir,
+                "--cflags-end",
+                "--output=%s" % gir_path,
+            ] + ext.sources + ext.depends)
+
+        if self._newer_group([gir_path], typelib_path):
+            subprocess.check_call([
+                g_ir_compiler,
+                gir_path,
+                "--output=%s" % typelib_path,
+            ])
+
+        ext = Extension(
+            name='libregress',
+            sources=[
+                os.path.join(gi_tests_dir, "regress.c"),
+                os.path.join(tests_dir, "regressextra.c"),
+            ],
+            include_dirs=[
+                gi_tests_dir,
+            ],
+            depends=[
+                os.path.join(gi_tests_dir, "regress.h"),
+                os.path.join(tests_dir, "regressextra.h"),
+            ],
+        )
+        add_ext_pkg_config_dep(ext, compiler.compiler_type, "glib-2.0")
+        add_ext_pkg_config_dep(ext, compiler.compiler_type, "gio-2.0")
+        add_ext_pkg_config_dep(ext, compiler.compiler_type, "cairo")
+        add_ext_pkg_config_dep(ext, compiler.compiler_type, "cairo-gobject")
+        ext_paths = build_ext(ext)
+
+        gir_path = os.path.join(tests_dir, "Regress-1.0.gir")
+        typelib_path = os.path.join(tests_dir, "Regress-1.0.typelib")
+
+        if self._newer_group(ext_paths, gir_path):
+            subprocess.check_call([
+                g_ir_scanner,
+                "--no-libtool",
+                "--include=cairo-1.0",
+                "--include=Gio-2.0",
+                "--namespace=Regress",
+                "--nsversion=1.0",
+                "--warn-all",
+                "--warn-error",
+                "--library-path=%s" % tests_dir,
+                "--library=regress",
+                "--pkg=glib-2.0",
+                "--pkg=gio-2.0",
+                "--pkg=cairo",
+                "--pkg=cairo-gobject",
+                "--output=%s" % gir_path,
+            ] + ext.sources + ext.depends)
+
+        if self._newer_group([gir_path], typelib_path):
+            subprocess.check_call([
+                g_ir_compiler,
+                gir_path,
+                "--output=%s" % typelib_path,
+            ])
+
+        ext = Extension(
+            name='tests.testhelper',
+            sources=[
+                os.path.join(tests_dir, "testhelpermodule.c"),
+                os.path.join(tests_dir, "test-floating.c"),
+                os.path.join(tests_dir, "test-thread.c"),
+                os.path.join(tests_dir, "test-unknown.c"),
+            ],
+            include_dirs=[
+                gi_dir,
+                tests_dir,
+            ],
+            depends=list_headers(gi_dir) + list_headers(tests_dir),
+            define_macros=[("PY_SSIZE_T_CLEAN", None)],
+        )
+        add_ext_pkg_config_dep(ext, compiler.compiler_type, "glib-2.0")
+        add_ext_pkg_config_dep(ext, compiler.compiler_type, "gio-2.0")
+        add_ext_pkg_config_dep(ext, compiler.compiler_type, "cairo")
+        add_ext_compiler_flags(ext, compiler)
+
+        dist = Distribution({"ext_modules": [ext]})
+
+        build_cmd = dist.get_command_obj("build")
+        build_cmd.build_base = os.path.join(self.build_base, "pygobject_tests")
+        build_cmd.ensure_finalized()
+
+        cmd = dist.get_command_obj("build_ext")
+        cmd.inplace = True
+        cmd.force = self.force
+        cmd.ensure_finalized()
+        cmd.run()
+
+
+def get_suppression_files_for_prefix(prefix):
+    """Returns a list of valgrind suppression files for a given prefix"""
+
+    # Most specific first (/usr/share/doc is Fedora, /usr/lib is Debian)
+    # Take the first one found
+    major = str(sys.version_info[0])
+    minor = str(sys.version_info[1])
+    pyfiles = []
+    pyfiles.append(
+        os.path.join(
+            prefix, "share", "doc", "python%s%s" % (major, minor),
+            "valgrind-python.supp"))
+    pyfiles.append(
+        os.path.join(prefix, "lib", "valgrind", "python%s.supp" % major))
+    pyfiles.append(
+        os.path.join(
+            prefix, "share", "doc", "python%s-devel" % major,
+            "valgrind-python.supp"))
+    pyfiles.append(os.path.join(prefix, "lib", "valgrind", "python.supp"))
+
+    files = []
+    for f in pyfiles:
+        if os.path.isfile(f):
+            files.append(f)
+            break
+
+    files.append(os.path.join(
+        prefix, "share", "glib-2.0", "valgrind", "glib.supp"))
+    return [f for f in files if os.path.isfile(f)]
+
+
+def get_real_prefix():
+    """Returns the base Python prefix, even in a virtualenv/venv"""
+
+    return getattr(sys, "base_prefix", getattr(sys, "real_prefix", sys.prefix))
+
+
+def get_suppression_files():
+    """Returns a list of valgrind suppression files"""
+
+    prefixes = [
+        sys.prefix,
+        get_real_prefix(),
+        pkg_config_parse("--variable=prefix", "glib-2.0")[0],
+    ]
+
+    files = []
+    for prefix in prefixes:
+        files.extend(get_suppression_files_for_prefix(prefix))
+
+    files.append(os.path.join(get_script_dir(), "tests", "valgrind.supp"))
+    return sorted(set(files))
+
+
+class test(Command):
+    user_options = [
+        ("valgrind", None, "run tests under valgrind"),
+        ("valgrind-log-file=", None, "save logs instead of printing them"),
+        ("gdb", None, "run tests under gdb"),
+        ("no-capture", "s", "don't capture test output"),
+    ]
+
+    def initialize_options(self):
+        self.valgrind = None
+        self.valgrind_log_file = None
+        self.gdb = None
+        self.no_capture = None
+
+    def finalize_options(self):
+        self.valgrind = bool(self.valgrind)
+        if self.valgrind_log_file and not self.valgrind:
+            raise DistutilsOptionError("valgrind not enabled")
+        self.gdb = bool(self.gdb)
+        self.no_capture = bool(self.no_capture)
+
+    def run(self):
+        cmd = self.reinitialize_command("build_tests")
+        cmd.ensure_finalized()
+        cmd.run()
+
+        env = os.environ.copy()
+        env.pop("MSYSTEM", None)
+
+        if self.no_capture:
+            env["PYGI_TEST_VERBOSE"] = "1"
+
+        env["MALLOC_PERTURB_"] = "85"
+        env["MALLOC_CHECK_"] = "3"
+        env["G_SLICE"] = "debug-blocks"
+
+        pre_args = []
+
+        if self.valgrind:
+            env["G_SLICE"] = "always-malloc"
+            env["G_DEBUG"] = "gc-friendly"
+            env["PYTHONMALLOC"] = "malloc"
+
+            pre_args += [
+                "valgrind", "--leak-check=full", "--show-possibly-lost=no",
+                "--num-callers=20", "--child-silent-after-fork=yes",
+            ] + ["--suppressions=" + f for f in get_suppression_files()]
+
+            if self.valgrind_log_file:
+                pre_args += ["--log-file=" + self.valgrind_log_file]
+
+        if self.gdb:
+            env["PYGI_TEST_GDB"] = "1"
+            pre_args += ["gdb", "--args"]
+
+        if pre_args:
+            log.info(" ".join(pre_args))
+
+        tests_dir = os.path.join(get_script_dir(), "tests")
+        sys.exit(subprocess.call(pre_args + [
+            sys.executable,
+            os.path.join(tests_dir, "runtests.py"),
+        ], env=env))
+
+
+class quality(Command):
+    description = "run code quality tests"
+    user_options = []
+
+    def initialize_options(self):
+        pass
+
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        status = subprocess.call([
+            sys.executable, "-m", "flake8",
+        ], cwd=get_script_dir())
+        if status != 0:
+            raise SystemExit(status)
+
+
+def get_script_dir():
+    return os.path.dirname(os.path.realpath(__file__))
+
+
+def get_pycairo_include_dir():
+    """Returns the best guess at where to find the pycairo headers.
+    A bit convoluted because we have to deal with multiple pycairo
+    versions.
+
+    Raises if pycairo isn't found or it's too old.
+    """
+
+    pkg_config_name = get_pycairo_pkg_config_name()
+    min_version = get_version_requirement(pkg_config_name)
+    min_version_info = tuple(int(p) for p in min_version.split("."))
+
+    def check_path(include_dir):
+        log.info("pycairo: trying include directory: %r" % include_dir)
+        header_path = os.path.join(include_dir, "%s.h" % pkg_config_name)
+        if os.path.exists(header_path):
+            log.info("pycairo: found %r" % header_path)
+            return True
+        log.info("pycairo: header file (%r) not found" % header_path)
+        return False
+
+    def find_path(paths):
+        for p in reversed(paths):
+            if check_path(p):
+                return p
+
+    def find_new_api():
+        log.info("pycairo: new API")
+        import cairo
+
+        if cairo.version_info < min_version_info:
+            raise DistutilsSetupError(
+                "pycairo >= %s required, %s found." % (
+                    min_version, ".".join(map(str, cairo.version_info))))
+
+        if hasattr(cairo, "get_include"):
+            return [cairo.get_include()]
+        log.info("pycairo: no get_include()")
+        return []
+
+    def find_old_api():
+        log.info("pycairo: old API")
+
+        import cairo
+
+        if cairo.version_info < min_version_info:
+            raise DistutilsSetupError(
+                "pycairo >= %s required, %s found." % (
+                    min_version, ".".join(map(str, cairo.version_info))))
+
+        location = os.path.dirname(os.path.abspath(cairo.__path__[0]))
+        log.info("pycairo: found %r" % location)
+
+        def samefile(src, dst):
+            # Python 2 on Windows doesn't have os.path.samefile, so we have to
+            # provide a fallback
+            if hasattr(os.path, "samefile"):
+                return os.path.samefile(src, dst)
+            os.stat(src)
+            os.stat(dst)
+            return (os.path.normcase(os.path.abspath(src)) ==
+                    os.path.normcase(os.path.abspath(dst)))
+
+        def get_sys_path(location, name):
+            # Returns the sysconfig path for a distribution, or None
+            for scheme in sysconfig.get_scheme_names():
+                for path_type in ["platlib", "purelib"]:
+                    path = sysconfig.get_path(path_type, scheme)
+                    try:
+                        if samefile(path, location):
+                            return sysconfig.get_path(name, scheme)
+                    except EnvironmentError:
+                        pass
+
+        data_path = get_sys_path(location, "data") or sys.prefix
+        return [os.path.join(data_path, "include", "pycairo")]
+
+    def find_pkg_config():
+        log.info("pycairo: pkg-config")
+        pkg_config_version_check(pkg_config_name, min_version)
+        return pkg_config_parse("--cflags-only-I", pkg_config_name)
+
+    # First the new get_include() API added in >1.15.6
+    include_dir = find_path(find_new_api())
+    if include_dir is not None:
+        return include_dir
+
+    # Then try to find it in the data prefix based on the module path.
+    # This works with many virtualenv/userdir setups, but not all apparently,
+    # see https://gitlab.gnome.org/GNOME/pygobject/issues/150
+    include_dir = find_path(find_old_api())
+    if include_dir is not None:
+        return include_dir
+
+    # Finally, fall back to pkg-config
+    include_dir = find_path(find_pkg_config())
+    if include_dir is not None:
+        return include_dir
+
+    raise DistutilsSetupError("Could not find pycairo headers")
+
+
+def add_ext_pkg_config_dep(ext, compiler_type, name):
+    msvc_libraries = {
+        "glib-2.0": ["glib-2.0"],
+        "gio-2.0": ["gio-2.0", "gobject-2.0", "glib-2.0"],
+        "gobject-introspection-1.0":
+            ["girepository-1.0", "gobject-2.0", "glib-2.0"],
+        "cairo": ["cairo"],
+        "cairo-gobject":
+            ["cairo-gobject", "cairo", "gobject-2.0", "glib-2.0"],
+        "libffi": ["ffi"],
+    }
+
+    def add(target, new):
+        for entry in new:
+            if entry not in target:
+                target.append(entry)
+
+    fallback_libs = msvc_libraries[name]
+    if compiler_type == "msvc":
+        # assume that INCLUDE and LIB contains the right paths
+        add(ext.libraries, fallback_libs)
+    else:
+        min_version = get_version_requirement(name)
+        pkg_config_version_check(name, min_version)
+        add(ext.include_dirs, pkg_config_parse("--cflags-only-I", name))
+        add(ext.library_dirs, pkg_config_parse("--libs-only-L", name))
+        add(ext.libraries, pkg_config_parse("--libs-only-l", name))
+
+
+def add_ext_compiler_flags(ext, compiler, _cache={}):
+    cache_key = compiler.compiler[0]
+    if cache_key not in _cache:
+
+        args = [
+            "-Wall",
+            "-Warray-bounds",
+            "-Wcast-align",
+            "-Wdeclaration-after-statement",
+            "-Wduplicated-branches",
+            "-Wextra",
+            "-Wformat=2",
+            "-Wformat-nonliteral",
+            "-Wformat-security",
+            "-Wimplicit-function-declaration",
+            "-Winit-self",
+            "-Wjump-misses-init",
+            "-Wlogical-op",
+            "-Wmissing-declarations",
+            "-Wmissing-format-attribute",
+            "-Wmissing-include-dirs",
+            "-Wmissing-noreturn",
+            "-Wmissing-prototypes",
+            "-Wnested-externs",
+            "-Wnull-dereference",
+            "-Wold-style-definition",
+            "-Wpacked",
+            "-Wpointer-arith",
+            "-Wrestrict",
+            "-Wreturn-type",
+            "-Wshadow",
+            "-Wsign-compare",
+            "-Wstrict-aliasing",
+            "-Wstrict-prototypes",
+            "-Wundef",
+            "-Wunused-but-set-variable",
+            "-Wwrite-strings",
+        ]
+
+        if sys.version_info[:2] != (3, 4):
+            args += [
+                "-Wswitch-default",
+            ]
+
+        args += [
+            "-Wno-incompatible-pointer-types-discards-qualifiers",
+            "-Wno-missing-field-initializers",
+            "-Wno-unused-parameter",
+            "-Wno-discarded-qualifiers",
+            "-Wno-sign-conversion",
+            "-Wno-cast-function-type",
+            "-Wno-int-conversion",
+        ]
+
+        # silence clang for unused gcc CFLAGS added by Debian
+        args += [
+            "-Wno-unused-command-line-argument",
+        ]
+
+        args += [
+            "-fno-strict-aliasing",
+            "-fvisibility=hidden",
+        ]
+
+        # force GCC to use colors
+        if hasattr(sys.stdout, "isatty") and sys.stdout.isatty():
+            args.append("-fdiagnostics-color")
+
+        _cache[cache_key] = filter_compiler_arguments(compiler, args)
+
+    ext.extra_compile_args += _cache[cache_key]
+
+
+du_build_ext = get_command_class("build_ext")
+
+
+class build_ext(du_build_ext):
+
+    def initialize_options(self):
+        du_build_ext.initialize_options(self)
+        self.compiler_type = None
+
+    def finalize_options(self):
+        du_build_ext.finalize_options(self)
+        self.compiler_type = new_compiler(compiler=self.compiler).compiler_type
+
+    def _write_config_h(self):
+        script_dir = get_script_dir()
+        target = os.path.join(script_dir, "config.h")
+        versions = get_versions()
+        content = u"""
+/* Configuration header created by setup.py - do not edit */
+#ifndef _CONFIG_H
+#define _CONFIG_H 1
+
+#define PYGOBJECT_MAJOR_VERSION %(PYGOBJECT_MAJOR_VERSION)s
+#define PYGOBJECT_MINOR_VERSION %(PYGOBJECT_MINOR_VERSION)s
+#define PYGOBJECT_MICRO_VERSION %(PYGOBJECT_MICRO_VERSION)s
+#define VERSION "%(VERSION)s"
+
+#endif /* _CONFIG_H */
+""" % versions
+
+        try:
+            with io.open(target, 'r', encoding="utf-8") as h:
+                if h.read() == content:
+                    return
+        except EnvironmentError:
+            pass
+
+        with io.open(target, 'w', encoding="utf-8") as h:
+            h.write(content)
+
+    def _setup_extensions(self):
+        ext = {e.name: e for e in self.extensions}
+
+        compiler = new_compiler(compiler=self.compiler)
+        customize_compiler(compiler)
+
+        def add_dependency(ext, name):
+            add_ext_pkg_config_dep(ext, compiler.compiler_type, name)
+
+        def add_pycairo(ext):
+            ext.include_dirs += [get_pycairo_include_dir()]
+
+        gi_ext = ext["gi._gi"]
+        add_dependency(gi_ext, "glib-2.0")
+        add_dependency(gi_ext, "gio-2.0")
+        add_dependency(gi_ext, "gobject-introspection-1.0")
+        add_dependency(gi_ext, "libffi")
+        add_ext_compiler_flags(gi_ext, compiler)
+
+        gi_cairo_ext = ext["gi._gi_cairo"]
+        add_dependency(gi_cairo_ext, "glib-2.0")
+        add_dependency(gi_cairo_ext, "gio-2.0")
+        add_dependency(gi_cairo_ext, "gobject-introspection-1.0")
+        add_dependency(gi_cairo_ext, "libffi")
+        add_dependency(gi_cairo_ext, "cairo")
+        add_dependency(gi_cairo_ext, "cairo-gobject")
+        add_pycairo(gi_cairo_ext)
+        add_ext_compiler_flags(gi_cairo_ext, compiler)
+
+    def run(self):
+        self._write_config_h()
+        self._setup_extensions()
+        du_build_ext.run(self)
+
+
+class install_pkgconfig(Command):
+    description = "install .pc file"
+    user_options = []
+
+    def initialize_options(self):
+        self.install_base = None
+        self.install_platbase = None
+        self.install_data = None
+        self.compiler_type = None
+        self.outfiles = []
+
+    def finalize_options(self):
+        self.set_undefined_options(
+            'install',
+            ('install_base', 'install_base'),
+            ('install_data', 'install_data'),
+            ('install_platbase', 'install_platbase'),
+        )
+
+        self.set_undefined_options(
+            'build_ext',
+            ('compiler_type', 'compiler_type'),
+        )
+
+    def get_outputs(self):
+        return self.outfiles
+
+    def get_inputs(self):
+        return []
+
+    def run(self):
+        cmd = self.distribution.get_command_obj("bdist_wheel", create=False)
+        if cmd is not None:
+            log.warn(
+                "Python wheels and pkg-config is not compatible. "
+                "No pkg-config file will be included in the wheel. Install "
+                "from source if you need one.")
+            return
+
+        if self.compiler_type == "msvc":
+            return
+
+        script_dir = get_script_dir()
+        pkgconfig_in = os.path.join(script_dir, "pygobject-3.0.pc.in")
+        with io.open(pkgconfig_in, "r", encoding="utf-8") as h:
+            content = h.read()
+
+        config = {
+            "prefix": self.install_base,
+            "exec_prefix": self.install_platbase,
+            "includedir": "${prefix}/include",
+            "datarootdir": "${prefix}/share",
+            "datadir": "${datarootdir}",
+            "VERSION": PYGOBJECT_VERISON,
+        }
+        for key, value in config.items():
+            content = content.replace("@%s@" % key, value)
+
+        libdir = os.path.dirname(get_python_lib(True, True, self.install_data))
+        pkgconfig_dir = os.path.join(libdir, "pkgconfig")
+        self.mkpath(pkgconfig_dir)
+        target = os.path.join(pkgconfig_dir, "pygobject-3.0.pc")
+        with io.open(target, "w", encoding="utf-8") as h:
+            h.write(content)
+        self.outfiles.append(target)
+
+
+du_install = get_command_class("install")
+
+
+class install(du_install):
+
+    sub_commands = du_install.sub_commands + [
+        ("install_pkgconfig", lambda self: True),
+    ]
+
+
+def main():
+    script_dir = get_script_dir()
+    pkginfo = parse_pkg_info(script_dir)
+    gi_dir = os.path.join(script_dir, "gi")
+
+    sources = [
+        os.path.join("gi", n) for n in os.listdir(gi_dir)
+        if os.path.splitext(n)[-1] == ".c"
+    ]
+    cairo_sources = [os.path.join("gi", "pygi-foreign-cairo.c")]
+    for s in cairo_sources:
+        sources.remove(s)
+
+    readme = os.path.join(script_dir, "README.rst")
+    with io.open(readme, encoding="utf-8") as h:
+        long_description = h.read()
+
+    gi_ext = Extension(
+        name='gi._gi',
+        sources=sources,
+        include_dirs=[script_dir, gi_dir],
+        depends=list_headers(script_dir) + list_headers(gi_dir),
+        define_macros=[("PY_SSIZE_T_CLEAN", None)],
+    )
+
+    gi_cairo_ext = Extension(
+        name='gi._gi_cairo',
+        sources=cairo_sources,
+        include_dirs=[script_dir, gi_dir],
+        depends=list_headers(script_dir) + list_headers(gi_dir),
+        define_macros=[("PY_SSIZE_T_CLEAN", None)],
+    )
+
+    version = pkginfo["Version"]
+    if is_dev_version():
+        # This makes it a PEP 440 pre-release and pip will only install it from
+        # PyPI in case --pre is passed.
+        version += ".dev0"
+
+    setup(
+        name=pkginfo["Name"],
+        version=version,
+        description=pkginfo["Summary"],
+        url=pkginfo["Home-page"],
+        author=pkginfo["Author"],
+        author_email=pkginfo["Author-email"],
+        maintainer=pkginfo["Maintainer"],
+        maintainer_email=pkginfo["Maintainer-email"],
+        license=pkginfo["License"],
+        long_description=long_description,
+        platforms=pkginfo.get_all("Platform"),
+        classifiers=pkginfo.get_all("Classifier"),
+        packages=[
+            "pygtkcompat",
+            "gi",
+            "gi.repository",
+            "gi.overrides",
+        ],
+        ext_modules=[
+            gi_ext,
+            gi_cairo_ext,
+        ],
+        cmdclass={
+            "build_ext": build_ext,
+            "distcheck": distcheck,
+            "sdist_gnome": sdist_gnome,
+            "build_tests": build_tests,
+            "test": test,
+            "quality": quality,
+            "install": install,
+            "install_pkgconfig": install_pkgconfig,
+        },
+        install_requires=[
+            "pycairo>=%s" % get_version_requirement(
+                get_pycairo_pkg_config_name()),
+        ],
+        data_files=[
+            ('include/pygobject-3.0', ['gi/pygobject.h']),
+        ],
+        zip_safe=False,
+    )
+
+
+if __name__ == "__main__":
+    main()
diff --git a/subprojects/glib.wrap b/subprojects/glib.wrap
new file mode 100644 (file)
index 0000000..87021ae
--- /dev/null
@@ -0,0 +1,5 @@
+[wrap-git]
+directory=glib
+url=https://gitlab.gnome.org/GNOME/glib.git
+push-url=git@gitlab.gnome.org:GNOME/glib.git
+revision=master
diff --git a/subprojects/gobject-introspection.wrap b/subprojects/gobject-introspection.wrap
new file mode 100644 (file)
index 0000000..561c20a
--- /dev/null
@@ -0,0 +1,5 @@
+[wrap-git]
+directory=gobject-introspection
+url=https://gitlab.gnome.org/GNOME/gobject-introspection.git
+push-url=git@gitlab.gnome.org:GNOME/gobject-introspection.git
+revision=master
diff --git a/subprojects/libffi.wrap b/subprojects/libffi.wrap
new file mode 100644 (file)
index 0000000..3d15e2a
--- /dev/null
@@ -0,0 +1,4 @@
+[wrap-git]
+directory=libffi
+url=https://github.com/centricular/libffi.git
+revision=meson
diff --git a/subprojects/pycairo.wrap b/subprojects/pycairo.wrap
new file mode 100644 (file)
index 0000000..87eac57
--- /dev/null
@@ -0,0 +1,4 @@
+[wrap-git]
+directory=pycairo
+url=https://github.com/pygobject/pycairo.git
+revision=master
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644 (file)
index 0000000..1ab73f0
--- /dev/null
@@ -0,0 +1,134 @@
+from __future__ import absolute_import
+
+import os
+import sys
+import unittest
+import signal
+import subprocess
+import atexit
+import warnings
+import imp
+
+
+class GIImport:
+    def find_module(self, fullname, path=None):
+        if fullname in ('gi._gi', 'gi._gi_cairo'):
+            return self
+        return None
+
+    def load_module(self, name):
+        if name in sys.modules:
+            return sys.modules[name]
+        fp, pathname, description = imp.find_module(name.split('.')[-1])
+        try:
+            module = imp.load_module(name, fp, pathname, description)
+        finally:
+            if fp:
+                fp.close()
+        sys.modules[name] = module
+        return module
+
+
+sys.meta_path.insert(0, GIImport())
+
+
+def init_test_environ():
+    # this was renamed in Python 3, provide backwards compatible name
+    if sys.version_info[:2] == (2, 7):
+        unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
+
+    if sys.version_info[0] == 3:
+        unittest.TestCase.assertRegexpMatches = unittest.TestCase.assertRegex
+        unittest.TestCase.assertRaisesRegexp = unittest.TestCase.assertRaisesRegex
+
+    def dbus_launch_session():
+        if os.name == "nt" or sys.platform == "darwin":
+            return (-1, "")
+
+        try:
+            out = subprocess.check_output([
+                "dbus-daemon", "--session", "--fork", "--print-address=1",
+                "--print-pid=1"])
+        except (subprocess.CalledProcessError, OSError):
+            return (-1, "")
+        else:
+            if sys.version_info[0] == 3:
+                out = out.decode("utf-8")
+            addr, pid = out.splitlines()
+            return int(pid), addr
+
+    pid, addr = dbus_launch_session()
+    if pid >= 0:
+        os.environ["DBUS_SESSION_BUS_ADDRESS"] = addr
+        atexit.register(os.kill, pid, signal.SIGKILL)
+    else:
+        os.environ["DBUS_SESSION_BUS_ADDRESS"] = "."
+
+    tests_builddir = os.path.abspath(os.environ.get('TESTS_BUILDDIR', os.path.dirname(__file__)))
+    builddir = os.path.dirname(tests_builddir)
+    tests_srcdir = os.path.abspath(os.path.dirname(__file__))
+    srcdir = os.path.dirname(tests_srcdir)
+
+    sys.path.insert(0, os.path.join(builddir, 'gi'))
+    sys.path.insert(0, tests_srcdir)
+    sys.path.insert(0, srcdir)
+    sys.path.insert(0, tests_builddir)
+    sys.path.insert(0, builddir)
+
+    # force untranslated messages, as we check for them in some tests
+    os.environ['LC_MESSAGES'] = 'C'
+    os.environ['G_DEBUG'] = 'fatal-warnings fatal-criticals'
+    if sys.platform == "darwin":
+        # gtk 3.22 has warnings and ciriticals on OS X, ignore for now
+        os.environ['G_DEBUG'] = ''
+
+    # make Gio able to find our gschemas.compiled in tests/. This needs to be set
+    # before importing Gio. Support a separate build tree, so look in build dir
+    # first.
+    os.environ['GSETTINGS_BACKEND'] = 'memory'
+    os.environ['GSETTINGS_SCHEMA_DIR'] = tests_builddir
+    os.environ['G_FILENAME_ENCODING'] = 'UTF-8'
+
+    # Force the default theme so broken themes don't affect the tests
+    os.environ['GTK_THEME'] = 'Adwaita'
+
+    import gi
+    gi.require_version("GIRepository", "2.0")
+    from gi.repository import GIRepository
+    repo = GIRepository.Repository.get_default()
+    repo.prepend_library_path(os.path.join(tests_builddir))
+    repo.prepend_library_path(os.path.join(tests_builddir, ".libs"))
+    repo.prepend_search_path(tests_builddir)
+
+    def try_require_version(namespace, version):
+        try:
+            gi.require_version(namespace, version)
+        except ValueError:
+            # prevent tests from running with the wrong version
+            sys.modules["gi.repository." + namespace] = None
+
+    # Optional
+    try_require_version("Gtk", os.environ.get("TEST_GTK_VERSION", "3.0"))
+    try_require_version("Gdk", os.environ.get("TEST_GTK_VERSION", "3.0"))
+    try_require_version("GdkPixbuf", "2.0")
+    try_require_version("Pango", "1.0")
+    try_require_version("PangoCairo", "1.0")
+    try_require_version("Atk", "1.0")
+
+    # Required
+    gi.require_versions({
+        "GIMarshallingTests": "1.0",
+        "Regress": "1.0",
+        "GLib": "2.0",
+        "Gio": "2.0",
+        "GObject": "2.0",
+    })
+
+    # It's disabled for stable releases by default, this makes sure it's
+    # always on for the tests.
+    warnings.simplefilter('default', gi.PyGIDeprecationWarning)
+
+
+init_test_environ()
+
+__path__ = __import__('pkgutil').extend_path(__path__, __name__)
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644 (file)
index 0000000..820210c
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import
+
+import sys
+
+import pytest
+
+from gi._compat import reraise
+
+
+@pytest.hookimpl(hookwrapper=True, tryfirst=True)
+def pytest_runtest_call(item):
+    """A pytest hook which takes over sys.excepthook and raises any uncaught
+    exception (with PyGObject this happesn often when we get called from C,
+    like any signal handler, vfuncs tc)
+    """
+
+    exceptions = []
+
+    def on_hook(type_, value, tback):
+        exceptions.append((type_, value, tback))
+
+    orig_excepthook = sys.excepthook
+    sys.excepthook = on_hook
+    try:
+        yield
+    finally:
+        sys.excepthook = orig_excepthook
+        if exceptions:
+            reraise(*exceptions[0])
diff --git a/tests/gi/overrides/Regress.py b/tests/gi/overrides/Regress.py
new file mode 100644 (file)
index 0000000..cb38631
--- /dev/null
@@ -0,0 +1,26 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2012 Martin Pitt <martinpitt@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from ..importer import modules
+
+Regress = modules['Regress']._introspection_module
+
+REGRESS_OVERRIDE = 42
+__all__ = ['REGRESS_OVERRIDE']
diff --git a/tests/gi/overrides/__init__.py b/tests/gi/overrides/__init__.py
new file mode 100644 (file)
index 0000000..3ad9513
--- /dev/null
@@ -0,0 +1,2 @@
+from pkgutil import extend_path
+__path__ = extend_path(__path__, __name__)
diff --git a/tests/gimarshallingtestsextra.c b/tests/gimarshallingtestsextra.c
new file mode 100644 (file)
index 0000000..4debaf5
--- /dev/null
@@ -0,0 +1,177 @@
+/* gimarshallingtestsextra.c
+ *
+ * Copyright (C) 2016 Thibault Saunier <tsaunier@gnome.org>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser 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/>.
+ */
+
+#include "gimarshallingtestsextra.h"
+#include <string.h>
+
+void
+gi_marshalling_tests_compare_two_gerrors_in_gvalue (GValue *v, GValue *v1)
+{
+  GError *error, * error1;
+
+  g_assert_cmpstr (g_type_name (G_VALUE_TYPE (v)), ==,
+                   g_type_name (G_TYPE_ERROR));
+  g_assert_cmpstr (g_type_name (G_VALUE_TYPE (v1)), ==,
+                   g_type_name (G_TYPE_ERROR));
+
+  error = (GError*) g_value_get_boxed (v);
+  error1 = (GError*) g_value_get_boxed (v1);
+
+  g_assert_cmpint (error->domain, ==, error1->domain);
+  g_assert_cmpint (error->code, ==, error1->code);
+  g_assert_cmpstr (error->message, ==, error1->message);
+}
+
+/**
+ * gi_marshalling_tests_ghashtable_enum_none_in:
+ * @hash_table: (element-type gint GIMarshallingTestsExtraEnum) (transfer none):
+ */
+void
+gi_marshalling_tests_ghashtable_enum_none_in (GHashTable *hash_table)
+{
+  g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (1))), ==, GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1);
+  g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (2))), ==, GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2);
+  g_assert_cmpint (GPOINTER_TO_INT (g_hash_table_lookup (hash_table, GINT_TO_POINTER (3))), ==, GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3);
+}
+
+/**
+ * gi_marshalling_tests_ghashtable_enum_none_return:
+ *
+ * Returns: (element-type gint GIMarshallingTestsExtraEnum) (transfer none):
+ */
+GHashTable *
+gi_marshalling_tests_ghashtable_enum_none_return (void)
+{
+  static GHashTable *hash_table = NULL;
+
+  if (hash_table == NULL)
+    {
+      hash_table = g_hash_table_new (NULL, NULL);
+      g_hash_table_insert (hash_table, GINT_TO_POINTER (1), GINT_TO_POINTER (GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1));
+      g_hash_table_insert (hash_table, GINT_TO_POINTER (2), GINT_TO_POINTER (GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2));
+      g_hash_table_insert (hash_table, GINT_TO_POINTER (3), GINT_TO_POINTER (GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3));
+    }
+
+  return hash_table;
+}
+
+/**
+ * gi_marshalling_tests_filename_copy:
+ * @path_in: (type filename) (nullable)
+ *
+ * Returns: (type filename) (nullable)
+ */
+gchar *
+gi_marshalling_tests_filename_copy (gchar *path_in)
+{
+  return g_strdup (path_in);
+}
+
+/**
+ * gi_marshalling_tests_filename_to_glib_repr:
+ * @path_in: (type filename) (nullable)
+ *
+ * Returns: (array length=len) (element-type guint8)
+ */
+gchar *
+gi_marshalling_tests_filename_to_glib_repr (gchar *path_in, gsize *len)
+{
+  *len = strlen(path_in);
+  return g_strdup (path_in);
+}
+
+/**
+ * gi_marshalling_tests_filename_exists:
+ * @path: (type filename)
+ */
+gboolean
+gi_marshalling_tests_filename_exists (gchar *path)
+{
+  return g_file_test (path, G_FILE_TEST_EXISTS);
+}
+
+
+/**
+ * gi_marshalling_tests_enum_array_return_type:
+ * @n_members: (out): The number of members
+ *
+ * Returns: (array length=n_members) (transfer full): An array of enum values
+ */
+GIMarshallingTestsExtraEnum *
+gi_marshalling_tests_enum_array_return_type (gsize *n_members)
+{
+  GIMarshallingTestsExtraEnum *res = g_new0(GIMarshallingTestsExtraEnum, 3);
+
+  *n_members = 3;
+
+  res[0] = GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1;
+  res[1] = GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2;
+  res[2] = GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3;
+
+  return res;
+}
+
+GType
+gi_marshalling_tests_extra_flags_get_type (void)
+{
+  static GType type = 0;
+  if (G_UNLIKELY (type == 0))
+    {
+      static const GFlagsValue values[] = {
+        {GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE1,
+         "GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE1", "value1"},
+        {GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2,
+         "GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2", "value2"},
+        {0, NULL, NULL}
+      };
+      type = g_flags_register_static (
+        g_intern_static_string ("GIMarshallingTestsExtraFlags"), values);
+    }
+
+  return type;
+}
+
+/**
+ * gi_marshalling_tests_extra_flags_large_in:
+ */
+void
+gi_marshalling_tests_extra_flags_large_in (GIMarshallingTestsExtraFlags value)
+{
+  g_assert_cmpint (value, ==, GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2);
+}
+
+
+/**
+ * gi_marshalling_tests_extra_utf8_full_return_invalid:
+ */
+gchar *
+gi_marshalling_tests_extra_utf8_full_return_invalid (void)
+{
+  return g_strdup ("invalid utf8 \xff\xfe");
+}
+
+
+/**
+ * gi_marshalling_tests_extra_utf8_full_out_invalid:
+ * @utf8: (out) (transfer full):
+ */
+void
+gi_marshalling_tests_extra_utf8_full_out_invalid (gchar **utf8)
+{
+  *utf8 = g_strdup ("invalid utf8 \xff\xfe");
+}
diff --git a/tests/gimarshallingtestsextra.h b/tests/gimarshallingtestsextra.h
new file mode 100644 (file)
index 0000000..bc5f8fe
--- /dev/null
@@ -0,0 +1,69 @@
+/* gimarshallingtestsextra.h
+ *
+ * Copyright (C) 2016 Thibault Saunier <tsaunier@gnome.org>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser 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/>.
+ */
+
+#ifndef EXTRA_TESTS
+#define EXTRA_TESTS
+
+#include <glib-object.h>
+#include <gitestmacros.h>
+
+typedef enum
+{
+  GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE1,
+  GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE2,
+  GI_MARSHALLING_TESTS_EXTRA_ENUM_VALUE3 = 42
+} GIMarshallingTestsExtraEnum;
+
+
+typedef enum
+{
+  GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE1 = 0,
+  GI_MARSHALLING_TESTS_EXTRA_FLAGS_VALUE2 = (gint)(1 << 31),
+} GIMarshallingTestsExtraFlags;
+
+
+_GI_TEST_EXTERN
+GType gi_marshalling_tests_extra_flags_get_type (void) G_GNUC_CONST;
+#define GI_MARSHALLING_TESTS_TYPE_EXTRA_FLAGS (gi_marshalling_tests_extra_flags_get_type ())
+
+_GI_TEST_EXTERN
+void gi_marshalling_tests_compare_two_gerrors_in_gvalue (GValue *v, GValue *v1);
+_GI_TEST_EXTERN
+void gi_marshalling_tests_ghashtable_enum_none_in (GHashTable *hash_table);
+_GI_TEST_EXTERN
+GHashTable * gi_marshalling_tests_ghashtable_enum_none_return (void);
+
+_GI_TEST_EXTERN
+gchar * gi_marshalling_tests_filename_copy (gchar *path_in);
+_GI_TEST_EXTERN
+gboolean gi_marshalling_tests_filename_exists (gchar *path);
+_GI_TEST_EXTERN
+gchar * gi_marshalling_tests_filename_to_glib_repr (gchar *path_in, gsize *len);
+
+_GI_TEST_EXTERN
+GIMarshallingTestsExtraEnum * gi_marshalling_tests_enum_array_return_type (gsize *n_members);
+
+_GI_TEST_EXTERN
+void gi_marshalling_tests_extra_flags_large_in (GIMarshallingTestsExtraFlags value);
+
+_GI_TEST_EXTERN
+gchar *gi_marshalling_tests_extra_utf8_full_return_invalid (void);
+_GI_TEST_EXTERN
+void gi_marshalling_tests_extra_utf8_full_out_invalid (gchar **utf8);
+
+#endif /* EXTRA_TESTS */
diff --git a/tests/helper.py b/tests/helper.py
new file mode 100644 (file)
index 0000000..1485ec3
--- /dev/null
@@ -0,0 +1,133 @@
+from __future__ import absolute_import
+
+import contextlib
+import unittest
+import inspect
+import warnings
+import functools
+import sys
+from collections import namedtuple
+
+import gi
+from gi import PyGIDeprecationWarning
+from gi.repository import GLib
+from gi._compat import StringIO
+
+
+ExceptionInfo = namedtuple("ExceptionInfo", ["type", "value", "traceback"])
+"""The type used for storing exceptions used by capture_exceptions()"""
+
+
+@contextlib.contextmanager
+def capture_exceptions():
+    """Installs a temporary sys.excepthook which records all exceptions
+    instead of printing them.
+    """
+
+    exceptions = []
+
+    def custom_excepthook(*args):
+        exceptions.append(ExceptionInfo(*args))
+
+    old_hook = sys.excepthook
+    sys.excepthook = custom_excepthook
+    try:
+        yield exceptions
+    finally:
+        sys.excepthook = old_hook
+
+
+def ignore_gi_deprecation_warnings(func_or_class):
+    """A unittest class and function decorator which makes them ignore
+    PyGIDeprecationWarning.
+    """
+
+    if inspect.isclass(func_or_class):
+        assert issubclass(func_or_class, unittest.TestCase)
+        cls = func_or_class
+        for name, value in cls.__dict__.items():
+            if callable(value) and name.startswith("test_"):
+                new_value = ignore_gi_deprecation_warnings(value)
+                setattr(cls, name, new_value)
+        return cls
+    else:
+        func = func_or_class
+
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs):
+            with capture_gi_deprecation_warnings():
+                return func(*args, **kwargs)
+
+        return wrapper
+
+
+@contextlib.contextmanager
+def capture_gi_deprecation_warnings():
+    """Temporarily suppress PyGIDeprecationWarning output and record them"""
+
+    with warnings.catch_warnings(record=True) as warn:
+        warnings.simplefilter('always', category=PyGIDeprecationWarning)
+        yield warn
+
+
+@contextlib.contextmanager
+def capture_glib_warnings(allow_warnings=False, allow_criticals=False):
+    """Temporarily suppress glib warning output and record them.
+
+    The test suite is run with G_DEBUG="fatal-warnings fatal-criticals"
+    by default. Setting allow_warnings and allow_criticals will temporarily
+    allow warnings or criticals without terminating the test run.
+    """
+
+    old_mask = GLib.log_set_always_fatal(GLib.LogLevelFlags(0))
+
+    new_mask = old_mask
+    if allow_warnings:
+        new_mask &= ~GLib.LogLevelFlags.LEVEL_WARNING
+    if allow_criticals:
+        new_mask &= ~GLib.LogLevelFlags.LEVEL_CRITICAL
+
+    GLib.log_set_always_fatal(GLib.LogLevelFlags(new_mask))
+
+    GLibWarning = gi._gi.Warning
+    try:
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.filterwarnings('always', category=GLibWarning)
+            yield warn
+    finally:
+        GLib.log_set_always_fatal(old_mask)
+
+
+@contextlib.contextmanager
+def capture_glib_deprecation_warnings():
+    """Temporarily suppress glib deprecation warning output and record them"""
+
+    GLibWarning = gi._gi.Warning
+    with warnings.catch_warnings(record=True) as warn:
+        warnings.filterwarnings(
+            'always', category=GLibWarning,
+            message=".+ is deprecated and shouldn't be used anymore\\. "
+                    "It will be removed in a future version\\.")
+        yield warn
+
+
+@contextlib.contextmanager
+def capture_output():
+    """
+    with capture_output() as (stdout, stderr):
+        some_action()
+    print(stdout.getvalue(), stderr.getvalue())
+    """
+
+    err = StringIO()
+    out = StringIO()
+    old_err = sys.stderr
+    old_out = sys.stdout
+    sys.stderr = err
+    sys.stdout = out
+
+    try:
+        yield (out, err)
+    finally:
+        sys.stderr = old_err
+        sys.stdout = old_out
diff --git a/tests/meson.build b/tests/meson.build
new file mode 100644 (file)
index 0000000..f72ead0
--- /dev/null
@@ -0,0 +1,132 @@
+gnome = import('gnome')
+
+host_system = host_machine.system()
+
+cc = meson.get_compiler('c')
+
+visibility_args = []
+if get_option('default_library') != 'static'
+  if host_system == 'windows'
+    visibility_args += ['-DDLL_EXPORT']
+    if cc.get_id() == 'msvc'
+      visibility_args += ['-D_GI_EXTERN=__declspec(dllexport) extern']
+    elif cc.has_argument('-fvisibility=hidden')
+      visibility_args += ['-D_GI_EXTERN=__attribute__((visibility("default"))) __declspec(dllexport) extern']
+      visibility_args += ['-fvisibility=hidden']
+    endif
+  elif cc.has_argument('-fvisibility=hidden')
+    visibility_args += ['-D_GI_EXTERN=__attribute__((visibility("default"))) extern']
+    visibility_args += ['-fvisibility=hidden']
+  endif
+endif
+
+if gi_dep.type_name() == 'pkgconfig'
+  gi_datadir = gi_dep.get_pkgconfig_variable('gidatadir')
+  regress_sources = [join_paths(gi_datadir, 'tests', 'regress.c')]
+  regress_headers = [join_paths(gi_datadir, 'tests', 'regress.h')]
+  regress_incdir = include_directories(join_paths(gi_datadir, 'tests'))
+  marshalling_sources = [join_paths(gi_datadir, 'tests', 'gimarshallingtests.c')]
+  marshalling_headers = [join_paths(gi_datadir, 'tests', 'gimarshallingtests.h')]
+else
+  gi_subproject = subproject('gobject-introspection')
+  regress_sources = gi_subproject.get_variable('test_regress_sources')
+  regress_headers = gi_subproject.get_variable('test_regress_headers')
+  regress_incdir = gi_subproject.get_variable('test_regress_incdirs')
+  marshalling_sources = gi_subproject.get_variable('test_marshalling_sources')
+  marshalling_headers = gi_subproject.get_variable('test_marshalling_headers')
+  gi_datadir = join_paths(meson.source_root(), 'subprojects', 'gobject-introspection', 'tests')
+endif
+
+marshalling_sources += ['gimarshallingtestsextra.c']
+
+marshalling_headers += ['gimarshallingtestsextra.h']
+
+marshalling_lib = library(
+  'gimarshallingtests',
+  sources : marshalling_sources,
+  dependencies : [glib_dep, gobject_dep, gio_dep, gmodule_dep],
+  include_directories : regress_incdir,
+  c_args: visibility_args,
+)
+
+gnome.generate_gir(
+  marshalling_lib,
+  sources : marshalling_sources + marshalling_headers,
+  nsversion : '1.0',
+  namespace : 'GIMarshallingTests',
+  dependencies : [glib_dep, gobject_dep, gio_dep, gmodule_dep],
+  symbol_prefix : 'gi_marshalling_tests',
+  includes : ['Gio-2.0'],
+  build_by_default : true,
+)
+
+regress_sources += ['regressextra.c']
+
+regress_headers += ['regressextra.h']
+
+regress_deps = [glib_dep, gobject_dep, gio_dep, gmodule_dep]
+regress_c_args = []
+
+if with_pycairo
+  regress_deps += [cairo_dep, cairo_gobject_dep]
+else
+  regress_c_args += ['-D_GI_DISABLE_CAIRO']
+endif
+
+regress_lib = library(
+  'regress',
+  sources : regress_sources,
+  dependencies : regress_deps,
+  include_directories : regress_incdir,
+  c_args: regress_c_args + visibility_args,
+)
+
+gnome.generate_gir(
+  regress_lib,
+  sources : regress_sources + regress_headers,
+  nsversion : '1.0',
+  namespace : 'Regress',
+  includes : ['Gio-2.0', 'cairo-1.0'],
+  build_by_default : true,
+  dependencies : regress_deps,
+  extra_args: regress_c_args,
+)
+
+helper_sources = [
+  'testhelpermodule.c',
+  'test-floating.c',
+  'test-thread.c',
+  'test-unknown.c']
+
+helperext = python.extension_module('testhelper', helper_sources,
+  dependencies : [python_dep, glib_dep, gobject_dep],
+  c_args: pyext_c_args + main_c_args,
+  include_directories: include_directories(join_paths('..', 'gi'))
+)
+
+schemas = gnome.compile_schemas(build_by_default: true)
+
+envdata = environment()
+envdata.append('GI_TYPELIB_PATH', meson.current_build_dir())
+if gi_dep.type_name() == 'internal'
+  envdata.append('GI_TYPELIB_PATH', join_paths(meson.build_root(), 'subprojects', 'gobject-introspection', 'gir'))
+endif
+
+if host_machine.system() == 'linux'
+    envdata.prepend('LD_LIBRARY_PATH', meson.current_build_dir())
+endif
+if host_machine.system() == 'windows'
+    envdata.prepend('PATH', join_paths(get_option('prefix'), get_option('bindir')))
+endif
+
+python_paths = [join_paths(meson.current_build_dir(), '..')]
+if with_pycairo and pycairo_dep.type_name() == 'internal'
+  python_paths += [join_paths(meson.build_root(), 'subprojects', 'pycairo')]
+endif
+envdata.append('PYTHONPATH', python_paths)
+envdata.append('TESTS_BUILDDIR', meson.current_build_dir())
+
+test('pygobject-test-suite', python,
+  args : [join_paths(meson.current_source_dir(), 'runtests.py')],
+  env : envdata,
+  timeout : 90)
diff --git a/tests/org.gnome.test.gschema.xml b/tests/org.gnome.test.gschema.xml
new file mode 100644 (file)
index 0000000..0c1e5c4
--- /dev/null
@@ -0,0 +1,38 @@
+<schemalist>
+    <enum id="org.gnome.test.FruitType">
+        <value nick="banana" value="0"/>
+        <value nick="apple" value="1"/>
+        <value nick="pear" value="2"/>
+    </enum>
+
+    <schema id="org.gnome.test" path="/tests/">
+       <key name="test-boolean" type="b">
+           <default>true</default>
+       </key>
+       <key name="test-string" type="s">
+           <default>"Hello"</default>
+       </key>
+       <key name="test-tuple" type="(ii)">
+          <default>(1,2)</default>
+       </key>
+       <key name="test-array" type="ai">
+           <default>[1,2]</default>
+       </key>
+       <key name="test-enum" enum="org.gnome.test.FruitType">
+           <default>'banana'</default>
+       </key>
+       <key name="test-range" type="i">
+           <range min="7" max="65535"/>
+           <default>123</default>
+       </key>
+    </schema>
+
+    <schema id="org.gnome.nopathtest">
+       <key name="np-int" type="i">
+           <default>42</default>
+       </key>
+    </schema>
+
+    <schema id="org.gnome.empty" path="/tests/">
+    </schema>
+</schemalist>
diff --git a/tests/regressextra.c b/tests/regressextra.c
new file mode 100644 (file)
index 0000000..a142678
--- /dev/null
@@ -0,0 +1,286 @@
+#include "regress.h"
+#include "regressextra.h"
+
+#include <glib-object.h>
+
+struct _RegressTestBoxedCWrapper
+{
+  RegressTestBoxedC * cptr;
+};
+
+RegressTestBoxedCWrapper *
+regress_test_boxed_c_wrapper_new (void)
+{
+  RegressTestBoxedCWrapper *boxed;
+  boxed = g_slice_new (RegressTestBoxedCWrapper);
+  boxed->cptr = regress_test_boxed_c_new ();
+  return boxed;
+}
+
+RegressTestBoxedCWrapper *
+regress_test_boxed_c_wrapper_copy (RegressTestBoxedCWrapper *self)
+{
+  RegressTestBoxedCWrapper *ret_boxed;
+  ret_boxed = g_slice_new (RegressTestBoxedCWrapper);
+  ret_boxed->cptr = g_boxed_copy (regress_test_boxed_c_get_type(), self->cptr);
+  return ret_boxed;
+}
+
+static void
+regress_test_boxed_c_wrapper_free (RegressTestBoxedCWrapper *boxed)
+{
+  g_boxed_free (regress_test_boxed_c_get_type(), boxed->cptr);
+  g_slice_free (RegressTestBoxedCWrapper, boxed);
+}
+
+G_DEFINE_BOXED_TYPE(RegressTestBoxedCWrapper,
+                    regress_test_boxed_c_wrapper,
+                    regress_test_boxed_c_wrapper_copy,
+                    regress_test_boxed_c_wrapper_free);
+
+/**
+ * regress_test_boxed_c_wrapper_get
+ * @self: a #RegressTestBoxedCWrapper objects
+ *
+ * Returns: (transfer none): associated #RegressTestBoxedC
+**/
+RegressTestBoxedC *
+regress_test_boxed_c_wrapper_get (RegressTestBoxedCWrapper *self)
+{
+  return self->cptr;
+}
+
+/**
+ * regress_test_array_fixed_boxed_none_out
+ * @objs: (out) (array fixed-size=2) (transfer none): An array of #RegressTestBoxedC
+**/
+void
+regress_test_array_fixed_boxed_none_out (RegressTestBoxedC ***objs)
+{
+  static RegressTestBoxedC **arr;
+
+  if (arr == NULL) {
+    arr = g_new0 (RegressTestBoxedC *, 3);
+    arr[0] = regress_test_boxed_c_new ();
+    arr[1] = regress_test_boxed_c_new ();
+  }
+
+  *objs = arr;
+}
+
+/**
+ * regress_test_glist_boxed_none_return
+ * Return value: (element-type RegressTestBoxedC) (transfer none):
+**/
+GList *
+regress_test_glist_boxed_none_return (guint count)
+{
+    static GList *list = NULL;
+    if (!list) {
+        while (count > 0) {
+            list = g_list_prepend (list, regress_test_boxed_c_new ());
+            count--;
+        }
+    }
+
+    return list;
+}
+
+/**
+ * regress_test_glist_boxed_full_return
+ * Return value: (element-type RegressTestBoxedC) (transfer full):
+**/
+GList *
+regress_test_glist_boxed_full_return (guint count)
+{
+    GList *list = NULL;
+    while (count > 0) {
+        list = g_list_prepend (list, regress_test_boxed_c_new ());
+        count--;
+    }
+    return list;
+}
+
+
+#ifndef _GI_DISABLE_CAIRO
+
+/**
+ * regress_test_cairo_context_none_return:
+ *
+ * Returns: (transfer none):
+ */
+cairo_t *
+regress_test_cairo_context_none_return (void)
+{
+    static cairo_t *cr;
+
+    if (cr == NULL) {
+        cairo_surface_t *surface;
+        surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 10, 10);
+        cr = cairo_create (surface);
+        cairo_surface_destroy (surface);
+    }
+
+    return cr;
+}
+
+/**
+ * regress_test_cairo_context_full_in:
+ * @context: (transfer full):
+ */
+void
+regress_test_cairo_context_full_in (cairo_t *context)
+{
+    cairo_destroy (context);
+}
+
+
+/**
+ * regress_test_cairo_path_full_return:
+ *
+ * Returns: (transfer full):
+ */
+cairo_path_t *
+regress_test_cairo_path_full_return (void)
+{
+    cairo_t *cr = regress_test_cairo_context_none_return ();
+
+    return cairo_copy_path (cr);
+}
+
+/**
+ * regress_test_cairo_path_none_in:
+ * @path: (transfer none):
+ */
+void
+regress_test_cairo_path_none_in (cairo_path_t *path)
+{
+    cairo_t *cr = regress_test_cairo_context_full_return ();
+    cairo_append_path (cr, path);
+    g_assert (cairo_status (cr) == CAIRO_STATUS_SUCCESS);
+    cairo_destroy (cr);
+}
+
+/**
+ * regress_test_cairo_path_full_in_full_return:
+ * @path: (transfer full):
+ *
+ * Returns: (transfer full):
+ */
+cairo_path_t *
+regress_test_cairo_path_full_in_full_return (cairo_path_t *path)
+{
+    return path;
+}
+
+/**
+ * regress_test_cairo_region_full_in:
+ * @region: (transfer full):
+ */
+void
+regress_test_cairo_region_full_in (cairo_region_t *region)
+{
+    cairo_region_destroy (region);
+}
+
+/**
+ * regress_test_cairo_surface_full_in:
+ * @surface: (transfer full):
+ */
+void
+regress_test_cairo_surface_full_in (cairo_surface_t *surface)
+{
+    g_assert (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32);
+    g_assert (cairo_image_surface_get_width (surface) == 10);
+    g_assert (cairo_image_surface_get_height (surface) == 10);
+    cairo_surface_destroy (surface);
+}
+
+/**
+ * regress_test_cairo_font_options_full_return:
+ *
+ * Returns: (transfer full):
+ */
+cairo_font_options_t *
+regress_test_cairo_font_options_full_return (void)
+{
+    return cairo_font_options_create ();
+}
+
+/**
+ * regress_test_cairo_font_options_none_return:
+ *
+ * Returns: (transfer none):
+ */
+cairo_font_options_t *
+regress_test_cairo_font_options_none_return (void)
+{
+    static cairo_font_options_t *options;
+
+    if (options == NULL)
+        options = cairo_font_options_create ();
+
+    return options;
+}
+
+/**
+ * regress_test_cairo_font_options_full_in:
+ * @options: (transfer full):
+ */
+void
+regress_test_cairo_font_options_full_in (cairo_font_options_t *options)
+{
+    cairo_font_options_destroy (options);
+}
+
+/**
+ * regress_test_cairo_font_options_none_in:
+ * @options: (transfer none):
+ */
+void
+regress_test_cairo_font_options_none_in (cairo_font_options_t *options)
+{
+}
+
+
+/**
+ * regress_test_cairo_matrix_none_in:
+ * @matrix: (transfer none):
+ */
+void
+regress_test_cairo_matrix_none_in (const cairo_matrix_t *matrix)
+{
+    cairo_matrix_t m = *matrix;
+    g_assert (m.x0 == 0);
+    g_assert (m.y0 == 0);
+    g_assert (m.xx == 1);
+    g_assert (m.xy == 0);
+    g_assert (m.yy == 1);
+    g_assert (m.yx == 0);
+}
+
+/**
+ * regress_test_cairo_matrix_none_return:
+ * Returns: (transfer none):
+ */
+cairo_matrix_t *
+regress_test_cairo_matrix_none_return (void)
+{
+    static cairo_matrix_t matrix;
+    cairo_matrix_init_identity (&matrix);
+    return &matrix;
+}
+
+/**
+ * regress_test_cairo_matrix_out_caller_allocates:
+ * @matrix: (out):
+ */
+void
+regress_test_cairo_matrix_out_caller_allocates (cairo_matrix_t *matrix)
+{
+    cairo_matrix_t m;
+    cairo_matrix_init_identity (&m);
+    *matrix = m;
+}
+
+#endif
diff --git a/tests/regressextra.h b/tests/regressextra.h
new file mode 100644 (file)
index 0000000..6f5d2f5
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef REGRESS_EXTRA_H
+#define REGRESS_EXTRA_H
+
+typedef struct _RegressTestBoxedC RegressTestBoxedC;
+typedef struct _RegressTestBoxedCWrapper RegressTestBoxedCWrapper;
+
+_GI_TEST_EXTERN
+GType regress_test_boxed_c_wrapper_get_type (void);
+
+_GI_TEST_EXTERN
+RegressTestBoxedCWrapper *regress_test_boxed_c_wrapper_new (void);
+_GI_TEST_EXTERN
+RegressTestBoxedCWrapper * regress_test_boxed_c_wrapper_copy (RegressTestBoxedCWrapper *self);
+_GI_TEST_EXTERN
+RegressTestBoxedC *regress_test_boxed_c_wrapper_get (RegressTestBoxedCWrapper *self);
+
+_GI_TEST_EXTERN
+void regress_test_array_fixed_boxed_none_out (RegressTestBoxedC ***objs);
+_GI_TEST_EXTERN
+GList *regress_test_glist_boxed_none_return (guint count);
+_GI_TEST_EXTERN
+GList *regress_test_glist_boxed_full_return (guint count);
+
+#ifndef _GI_DISABLE_CAIRO
+
+_GI_TEST_EXTERN
+cairo_t *regress_test_cairo_context_none_return (void);
+_GI_TEST_EXTERN
+void regress_test_cairo_context_full_in (cairo_t *context);
+_GI_TEST_EXTERN
+cairo_path_t *regress_test_cairo_path_full_return (void);
+_GI_TEST_EXTERN
+void regress_test_cairo_path_none_in (cairo_path_t *path);
+_GI_TEST_EXTERN
+cairo_path_t * regress_test_cairo_path_full_in_full_return (cairo_path_t *path);
+_GI_TEST_EXTERN
+cairo_font_options_t *regress_test_cairo_font_options_full_return (void);
+_GI_TEST_EXTERN
+cairo_font_options_t *regress_test_cairo_font_options_none_return (void);
+_GI_TEST_EXTERN
+void regress_test_cairo_font_options_full_in (cairo_font_options_t *options);
+_GI_TEST_EXTERN
+void regress_test_cairo_font_options_none_in (cairo_font_options_t *options);
+_GI_TEST_EXTERN
+void regress_test_cairo_region_full_in (cairo_region_t *region);
+_GI_TEST_EXTERN
+void regress_test_cairo_surface_full_in (cairo_surface_t *surface);
+_GI_TEST_EXTERN
+void regress_test_cairo_matrix_none_in (const cairo_matrix_t *matrix);
+_GI_TEST_EXTERN
+cairo_matrix_t *regress_test_cairo_matrix_none_return (void);
+_GI_TEST_EXTERN
+void regress_test_cairo_matrix_out_caller_allocates (cairo_matrix_t *matrix);
+
+#endif
+
+#endif /* REGRESS_EXTRA_H */
diff --git a/tests/runtests.py b/tests/runtests.py
new file mode 100755 (executable)
index 0000000..c05702a
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+# -*- Mode: Python -*-
+
+from __future__ import absolute_import
+
+import os
+import sys
+
+import pytest
+
+
+def main(argv):
+    if '--help' in argv:
+        print("Usage: ./runtests.py <testfiles>")
+        return
+
+    mydir = os.path.dirname(os.path.abspath(__file__))
+
+    verbosity_args = []
+
+    if 'PYGI_TEST_VERBOSE' in os.environ:
+        verbosity_args += ['--capture=no']
+
+    if 'TEST_NAMES' in os.environ:
+        names = os.environ['TEST_NAMES'].split()
+    elif 'TEST_FILES' in os.environ:
+        names = []
+        for filename in os.environ['TEST_FILES'].split():
+            names.append(filename[:-3])
+    elif len(argv) > 1:
+        names = []
+        for filename in argv[1:]:
+            names.append(filename.replace('.py', ''))
+    else:
+        return pytest.main([mydir] + verbosity_args)
+
+    def unittest_to_pytest_name(name):
+        parts = name.split(".")
+        parts[0] = os.path.join(mydir, parts[0] + ".py")
+        return "::".join(parts)
+
+    return pytest.main([unittest_to_pytest_name(n) for n in names] + verbosity_args)
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
diff --git a/tests/test-floating.c b/tests/test-floating.c
new file mode 100644 (file)
index 0000000..18e4a46
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * test-floating.c - Source for TestFloating
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "test-floating.h"
+
+/* TestFloating */
+
+G_DEFINE_TYPE(TestFloating, test_floating, G_TYPE_INITIALLY_UNOWNED)
+
+static void
+test_floating_finalize (GObject *gobject)
+{
+  TestFloating *object = TEST_FLOATING (gobject);
+
+  if (g_object_is_floating (object))
+    {
+      g_warning ("A floating object was finalized. This means that someone\n"
+                "called g_object_unref() on an object that had only a floating\n"
+                "reference; the initial floating reference is not owned by anyone\n"
+                "and must be removed without g_object_ref_sink().");
+    }
+
+  G_OBJECT_CLASS (test_floating_parent_class)->finalize (gobject);
+}
+
+static void
+test_floating_class_init (TestFloatingClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize = test_floating_finalize;
+}
+
+static void
+test_floating_init (TestFloating *self)
+{
+}
+
+/* TestOwnedByLibrary */
+
+G_DEFINE_TYPE(TestOwnedByLibrary, test_owned_by_library, G_TYPE_OBJECT)
+
+static GSList *obl_instance_list = NULL;
+
+static void
+test_owned_by_library_class_init (TestOwnedByLibraryClass *klass)
+{
+}
+
+static void
+test_owned_by_library_init (TestOwnedByLibrary *self)
+{
+    g_object_ref (self);
+    obl_instance_list = g_slist_prepend (obl_instance_list, self);
+}
+
+void
+test_owned_by_library_release (TestOwnedByLibrary *self)
+{
+    obl_instance_list = g_slist_remove (obl_instance_list, self);
+    g_object_unref (self);
+}
+
+GSList *
+test_owned_by_library_get_instance_list (void)
+{
+    return obl_instance_list;
+}
+
+/* TestFloatingAndSunk
+ * This object is mimicking the GtkWindow behaviour, ie a GInitiallyUnowned subclass
+ * whose floating reference has already been sunk when g_object_new() returns it.
+ * The reference is already sunk because the instance is already owned by the instance
+ * list.
+ */
+
+G_DEFINE_TYPE(TestFloatingAndSunk, test_floating_and_sunk, G_TYPE_INITIALLY_UNOWNED)
+
+static GSList *fas_instance_list = NULL;
+
+static void
+test_floating_and_sunk_class_init (TestFloatingAndSunkClass *klass)
+{
+}
+
+static void
+test_floating_and_sunk_init (TestFloatingAndSunk *self)
+{
+    g_object_ref_sink (self);
+    fas_instance_list = g_slist_prepend (fas_instance_list, self);
+}
+
+void
+test_floating_and_sunk_release (TestFloatingAndSunk *self)
+{
+    fas_instance_list = g_slist_remove (fas_instance_list, self);
+    g_object_unref (self);
+}
+
+GSList *
+test_floating_and_sunk_get_instance_list (void)
+{
+    return fas_instance_list;
+}
diff --git a/tests/test-floating.h b/tests/test-floating.h
new file mode 100644 (file)
index 0000000..ecf2462
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * test-floating.h - Header for TestFloating
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <glib-object.h>
+
+/* TestFloating */
+
+typedef struct {
+  GInitiallyUnowned parent;
+} TestFloating;
+
+typedef struct {
+  GInitiallyUnownedClass parent_class;
+} TestFloatingClass;
+
+#define TEST_TYPE_FLOATING            (test_floating_get_type())
+#define TEST_FLOATING(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_FLOATING, TestFloating))
+#define TEST_FLOATING_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_FLOATING, TestFloatingClass))
+#define TEST_IS_FLOATING(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_FLOATING))
+#define TEST_IS_FLOATING_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), TEST_TYPE_FLOATING))
+#define TEST_FLOATING_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), TEST_TYPE_FLOATING, TestFloatingClass))
+
+GType test_floating_get_type (void);
+
+/* TestOwnedByLibrary */
+
+typedef struct {
+  GObject parent;
+} TestOwnedByLibrary;
+
+typedef struct {
+  GObjectClass parent_class;
+} TestOwnedByLibraryClass;
+
+#define TEST_TYPE_OWNED_BY_LIBRARY            (test_owned_by_library_get_type())
+#define TEST_OWNED_BY_LIBRARY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_OWNED_BY_LIBRARY, TestOwnedByLibrary))
+#define TEST_OWNED_BY_LIBRARY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_OWNED_BY_LIBRARY, TestOwnedByLibraryClass))
+#define TEST_IS_OWNED_BY_LIBRARY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_OWNED_BY_LIBRARY))
+#define TEST_IS_OWNED_BY_LIBRARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), TEST_TYPE_OWNED_BY_LIBRARY))
+#define TEST_OWNED_BY_LIBRARY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), TEST_TYPE_OWNED_BY_LIBRARY, TestOwnedByLibraryClass))
+
+GType test_owned_by_library_get_type (void);
+void test_owned_by_library_release (TestOwnedByLibrary *self);
+GSList *test_owned_by_library_get_instance_list (void);
+
+/* TestFloatingAndSunk */
+
+typedef struct {
+  GInitiallyUnowned parent;
+} TestFloatingAndSunk;
+
+typedef struct {
+  GInitiallyUnownedClass parent_class;
+} TestFloatingAndSunkClass;
+
+#define TEST_TYPE_FLOATING_AND_SUNK            (test_floating_and_sunk_get_type())
+#define TEST_FLOATING_AND_SUNK(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_FLOATING_AND_SUNK, TestFloatingAndSunk))
+#define TEST_FLOATING_AND_SUNK_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_FLOATING_AND_SUNK, TestFloatingAndSunkClass))
+#define TEST_IS_FLOATING_AND_SUNK(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_FLOATING_AND_SUNK))
+#define TEST_IS_FLOATING_AND_SUNK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), TEST_TYPE_FLOATING_AND_SUNK))
+#define TEST_FLOATING_AND_SUNK_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), TEST_TYPE_FLOATING_AND_SUNK, TestFloatingAndSunkClass))
+
+GType test_floating_and_sunk_get_type (void);
+void test_floating_and_sunk_release (TestFloatingAndSunk *self);
+GSList *test_floating_and_sunk_get_instance_list (void);
diff --git a/tests/test-thread.c b/tests/test-thread.c
new file mode 100644 (file)
index 0000000..438cb97
--- /dev/null
@@ -0,0 +1,63 @@
+#include "test-thread.h"
+
+enum
+{
+  /* methods */
+  SIGNAL_EMIT_SIGNAL,
+  SIGNAL_FROM_THREAD,
+  LAST_SIGNAL
+};
+
+static guint test_thread_signals[LAST_SIGNAL] = { 0 };
+
+typedef enum {
+  TEST_THREAD_A,
+  TEST_THREAD_B
+} ThreadEnumType;
+
+static GType
+test_thread_enum_get_type (void)
+{
+  static GType enum_type = 0;
+  static GEnumValue enum_values[] = {
+    {TEST_THREAD_A, "TEST_THREAD_A", "a as in apple"},
+    {0, NULL, NULL},
+  };
+
+  if (!enum_type) {
+    enum_type =
+        g_enum_register_static ("TestThreadEnum", enum_values);
+  }
+  return enum_type;
+}
+
+G_DEFINE_TYPE(TestThread, test_thread, G_TYPE_OBJECT);
+
+static void
+other_thread_cb (TestThread *self)
+{
+  g_signal_emit_by_name (self, "from-thread", 0, NULL);
+  g_thread_exit (0);
+}
+
+static void
+test_thread_emit_signal (TestThread *self)
+{
+  self->thread = g_thread_new ("t", (GThreadFunc)other_thread_cb, self);
+}
+
+static void test_thread_init (TestThread *self) {}
+static void test_thread_class_init (TestThreadClass *klass)
+{
+  test_thread_signals[SIGNAL_EMIT_SIGNAL] =
+    g_signal_new ("emit-signal", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (TestThreadClass, emit_signal),
+                 NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+  test_thread_signals[SIGNAL_FROM_THREAD] =
+    g_signal_new ("from-thread", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (TestThreadClass, from_thread),
+                 NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1,
+                 test_thread_enum_get_type ());
+
+  klass->emit_signal = test_thread_emit_signal;
+}
diff --git a/tests/test-thread.h b/tests/test-thread.h
new file mode 100644 (file)
index 0000000..db12fe4
--- /dev/null
@@ -0,0 +1,22 @@
+#include <glib-object.h>
+
+typedef struct {
+  GObject parent;
+  GThread *thread;
+} TestThread;
+
+typedef struct {
+  GObjectClass parent_class;
+  void (*emit_signal) (TestThread *sink);
+  void (*from_thread)  (TestThread *sink);
+} TestThreadClass;
+
+GType        test_thread_get_type   (void);
+
+#define TEST_TYPE_THREAD            (test_thread_get_type())
+#define TEST_THREAD(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_THREAD, TestTHREAD))
+#define TEST_THREAD_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_THREAD, TestTHREADClass))
+#define TEST_IS_THREAD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_THREAD))
+#define TEST_IS_THREAD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), TEST_TYPE_THREAD))
+#define TEST_THREAD_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), TEST_TYPE_THREAD, TestTHREADClass))
+
diff --git a/tests/test-unknown.c b/tests/test-unknown.c
new file mode 100644 (file)
index 0000000..f1c3139
--- /dev/null
@@ -0,0 +1,113 @@
+#include "test-unknown.h"
+
+enum {
+  PROP_SOME_PROPERTY = 1,
+};
+
+
+static void
+test_interface_base_init (gpointer g_iface)
+{
+  static gboolean initialized = FALSE;
+  
+  if (!initialized)
+    {
+      g_object_interface_install_property (g_iface,
+                                          g_param_spec_string ("some-property",
+                                                               "some-property",
+                                                               "A simple test property",
+                                                               NULL,
+                                                               G_PARAM_READWRITE));
+      initialized = TRUE;
+    }
+}
+
+
+GType
+test_interface_get_type (void)
+{
+  static GType gtype = 0;
+
+  if (!gtype)
+    {
+      static const GTypeInfo info =
+      {
+        sizeof (TestInterfaceIface), /* class_size */
+       test_interface_base_init,   /* base_init */
+        NULL,           /* base_finalize */
+        NULL,
+        NULL,           /* class_finalize */
+        NULL,           /* class_data */
+        0,
+        0,              /* n_preallocs */
+        NULL
+      };
+
+      gtype =
+        g_type_register_static (G_TYPE_INTERFACE, "TestInterface",
+                                &info, 0);
+
+      g_type_interface_add_prerequisite (gtype, G_TYPE_OBJECT);
+    }
+
+  return gtype;
+}
+
+static void test_unknown_iface_method (TestInterface *iface)
+{
+}
+
+static void
+test_unknown_test_interface_init (TestInterfaceIface *iface)
+{
+  iface->iface_method = test_unknown_iface_method;
+}
+
+G_DEFINE_TYPE_WITH_CODE (TestUnknown, test_unknown, G_TYPE_OBJECT,
+                        G_IMPLEMENT_INTERFACE (TEST_TYPE_INTERFACE,
+                                               test_unknown_test_interface_init));
+
+static void test_unknown_init (TestUnknown *self) {}
+
+
+static void
+test_unknown_get_property (GObject              *object,
+                           guint                 prop_id,
+                           GValue               *value,
+                           GParamSpec           *pspec)
+{
+
+}
+
+static void
+test_unknown_set_property (GObject              *object,
+                           guint                 prop_id,
+                           const GValue         *value,
+                           GParamSpec           *pspec)
+{
+
+}
+
+static void test_unknown_class_init (TestUnknownClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  gobject_class->get_property = test_unknown_get_property;
+  gobject_class->set_property = test_unknown_set_property;
+
+
+  g_object_class_install_property (G_OBJECT_CLASS (klass),
+                                   PROP_SOME_PROPERTY,
+                                   g_param_spec_string ("some-property",
+                                                        "some-property",
+                                                        "A simple test property",
+                                                        NULL,
+                                                        G_PARAM_READWRITE));
+}
+
+void test_interface_iface_method (TestInterface *instance)
+{
+  TestInterfaceIface *iface = TEST_INTERFACE_GET_IFACE (instance);
+
+  (* iface->iface_method) (instance);
+}
diff --git a/tests/test-unknown.h b/tests/test-unknown.h
new file mode 100644 (file)
index 0000000..e0f51a2
--- /dev/null
@@ -0,0 +1,40 @@
+#include <glib-object.h>
+
+/* TestUnknown */
+
+typedef struct {
+  GObject parent;
+} TestUnknown;
+
+typedef struct {
+  GObjectClass parent_class;
+} TestUnknownClass;
+
+#define TEST_TYPE_UNKNOWN            (test_unknown_get_type())
+#define TEST_UNKNOWN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_UNKNOWN, TestUnknown))
+#define TEST_UNKNOWN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_UNKNOWN, TestUnknownClass))
+#define TEST_IS_UNKNOWN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_UNKNOWN))
+#define TEST_IS_UNKNOWN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), TEST_TYPE_UNKNOWN))
+#define TEST_UNKNOWN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), TEST_TYPE_UNKNOWN, TestUnknownClass))
+
+GType test_unknown_get_type (void);
+
+/* TestInterface */
+typedef struct _TestInterface TestInterface;
+typedef struct _TestInterfaceIface TestInterfaceIface;
+
+struct _TestInterfaceIface
+{
+  GTypeInterface g_iface;
+  /* VTable */
+  void (* iface_method) (TestInterface *iface);
+};
+
+#define TEST_TYPE_INTERFACE            (test_interface_get_type ())
+#define TEST_INTERFACE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_INTERFACE, TestInterface))
+#define TEST_IS_INTERFACE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_INTERFACE))
+#define TEST_INTERFACE_GET_IFACE(obj)  (G_TYPE_INSTANCE_GET_INTERFACE ((obj), TEST_TYPE_INTERFACE, TestInterfaceIface))
+
+GType test_interface_get_type (void);
+
+void test_interface_iface_method (TestInterface *iface);
diff --git a/tests/test_atoms.py b/tests/test_atoms.py
new file mode 100644 (file)
index 0000000..0081a02
--- /dev/null
@@ -0,0 +1,100 @@
+from __future__ import absolute_import
+
+import os
+import unittest
+
+try:
+    from gi.repository import Gtk, Atk, Gdk
+except ImportError:
+    Gdk = None
+    Atk = None
+    Gtk = None
+
+from .helper import capture_glib_deprecation_warnings
+
+
+def is_X11():
+    try:
+        from gi.repository import Gdk, GdkX11
+    except ImportError:
+        return False
+
+    display = Gdk.Display.get_default()
+    return isinstance(display, GdkX11.X11Display)
+
+
+@unittest.skipUnless(Gdk, 'Gdk not available')
+class TestGdkAtom(unittest.TestCase):
+    def test_create(self):
+        atom = Gdk.Atom.intern('my_string', False)
+        self.assertEqual(atom.name(), 'my_string')
+
+    def test_str(self):
+        atom = Gdk.Atom.intern('my_string', False)
+        self.assertEqual(str(atom), 'my_string')
+
+        self.assertEqual(str(Gdk.SELECTION_CLIPBOARD), 'CLIPBOARD')
+
+    def test_repr(self):
+        # __repr__ should generate a string which is parsable when possible
+        # http://docs.python.org/2/reference/datamodel.html#object.__repr__
+        atom = Gdk.Atom.intern('my_string', False)
+        self.assertEqual(repr(atom), 'Gdk.Atom.intern("my_string", False)')
+        self.assertEqual(eval(repr(atom)), atom)
+
+        self.assertEqual(repr(Gdk.SELECTION_CLIPBOARD), 'Gdk.Atom.intern("CLIPBOARD", False)')
+
+    @unittest.skipUnless(os.name != "nt", "not on Windows")
+    def test_in_single(self):
+        a_selection = Gdk.Atom.intern('test_clipboard', False)
+        clipboard = Gtk.Clipboard.get(a_selection)
+        clipboard.set_text('hello', 5)
+
+        # needs a Gdk.Atom, not a string
+        self.assertRaises(TypeError, Gtk.Clipboard.get, 'CLIPBOARD')
+
+    def test_in_array(self):
+        a_plain = Gdk.Atom.intern('text/plain', False)
+        a_html = Gdk.Atom.intern('text/html', False)
+        a_jpeg = Gdk.Atom.intern('image/jpeg', False)
+
+        self.assertFalse(Gtk.targets_include_text([]))
+        self.assertTrue(Gtk.targets_include_text([a_plain, a_html]))
+        self.assertFalse(Gtk.targets_include_text([a_jpeg]))
+        self.assertTrue(Gtk.targets_include_text([a_jpeg, a_plain]))
+
+        self.assertFalse(Gtk.targets_include_image([], False))
+        self.assertFalse(Gtk.targets_include_image([a_plain, a_html], False))
+        self.assertTrue(Gtk.targets_include_image([a_jpeg], False))
+        self.assertTrue(Gtk.targets_include_image([a_jpeg, a_plain], False))
+
+    @unittest.skipUnless(is_X11(), "only on X11")
+    def test_out_array(self):
+        a_selection = Gdk.Atom.intern('my_clipboard', False)
+        clipboard = Gtk.Clipboard.get(a_selection)
+
+        # empty
+        (res, targets) = clipboard.wait_for_targets()
+        self.assertEqual(res, False)
+        self.assertEqual(targets, [])
+
+        # text
+        clipboard.set_text('hello', 5)
+        (res, targets) = clipboard.wait_for_targets()
+        self.assertEqual(res, True)
+        self.assertNotEqual(targets, [])
+        self.assertEqual(type(targets[0]), Gdk.Atom)
+        names = [t.name() for t in targets]
+        self.assertFalse(None in names, names)
+        self.assertTrue('TEXT' in names, names)
+
+    @unittest.skipUnless(is_X11(), "only on X11")
+    @unittest.skipIf(not Gdk or Gdk._version == "4.0", "not in gdk4")
+    def test_out_glist(self):
+        display = Gdk.Display.get_default()
+        with capture_glib_deprecation_warnings():
+            dm = display.get_device_manager()
+            device = dm.get_client_pointer()
+        axes = device.list_axes()
+        axes_names = [atom.name() for atom in axes]
+        self.assertNotEqual(axes_names, [])
diff --git a/tests/test_cairo.py b/tests/test_cairo.py
new file mode 100644 (file)
index 0000000..18e5d62
--- /dev/null
@@ -0,0 +1,260 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# coding=utf-8
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import unittest
+import pytest
+
+import gi
+
+try:
+    gi.require_foreign('cairo')
+    import cairo
+    has_cairo = True
+except ImportError:
+    has_cairo = False
+
+has_region = has_cairo and hasattr(cairo, "Region")
+
+try:
+    from gi.repository import Gtk, Gdk
+    Gtk, Gdk  # pyflakes
+except:
+    Gtk = None
+    Gdk = None
+
+from gi.repository import GObject, Regress
+
+
+@unittest.skipUnless(has_cairo, 'built without cairo support')
+class Test(unittest.TestCase):
+    def test_cairo_context(self):
+        context = Regress.test_cairo_context_full_return()
+        self.assertTrue(isinstance(context, cairo.Context))
+
+        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10)
+        context = cairo.Context(surface)
+        Regress.test_cairo_context_none_in(context)
+
+    def test_cairo_context_full_in(self):
+        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10)
+        context = cairo.Context(surface)
+        Regress.test_cairo_context_full_in(context)
+
+        with pytest.raises(TypeError):
+            Regress.test_cairo_context_full_in(object())
+
+    def test_cairo_context_none_return(self):
+        context = Regress.test_cairo_context_none_return()
+        self.assertTrue(isinstance(context, cairo.Context))
+
+    def test_cairo_path_full_return(self):
+        path = Regress.test_cairo_path_full_return()
+        if hasattr(cairo, "Path"):  # pycairo 1.15.1+
+            assert isinstance(path, cairo.Path)
+
+    def test_cairo_path_none_in(self):
+        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10)
+        context = cairo.Context(surface)
+        path = context.copy_path()
+        Regress.test_cairo_path_none_in(path)
+        surface.finish()
+
+        with pytest.raises(TypeError):
+            Regress.test_cairo_path_none_in(object())
+
+    def test_cairo_path_full_in_full_return(self):
+        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10)
+        context = cairo.Context(surface)
+        context.move_to(10, 10)
+        context.curve_to(10, 10, 3, 4, 5, 6)
+        path = context.copy_path()
+        new_path = Regress.test_cairo_path_full_in_full_return(path)
+        assert list(path) == list(new_path)
+        surface.finish()
+
+    def test_cairo_font_options_full_return(self):
+        options = Regress.test_cairo_font_options_full_return()
+        assert isinstance(options, cairo.FontOptions)
+
+    def test_cairo_font_options_none_return(self):
+        options = Regress.test_cairo_font_options_none_return()
+        assert isinstance(options, cairo.FontOptions)
+
+    def test_cairo_font_options_full_in(self):
+        options = cairo.FontOptions()
+        Regress.test_cairo_font_options_full_in(options)
+
+        with pytest.raises(TypeError):
+            Regress.test_cairo_font_options_full_in(object())
+
+    def test_cairo_font_options_none_in(self):
+        options = cairo.FontOptions()
+        Regress.test_cairo_font_options_none_in(options)
+
+    def test_cairo_region_full_in(self):
+        region = cairo.Region()
+        Regress.test_cairo_region_full_in(region)
+
+        with pytest.raises(TypeError):
+            Regress.test_cairo_region_full_in(object())
+
+    def test_cairo_matrix_none_in(self):
+        matrix = cairo.Matrix()
+        Regress.test_cairo_matrix_none_in(matrix)
+
+        with pytest.raises(TypeError):
+            Regress.test_cairo_matrix_none_in(object())
+
+    def test_cairo_matrix_none_return(self):
+        matrix = Regress.test_cairo_matrix_none_return()
+        assert matrix == cairo.Matrix()
+
+    def test_cairo_matrix_out_caller_allocates(self):
+        matrix = Regress.test_cairo_matrix_out_caller_allocates()
+        assert matrix == cairo.Matrix()
+
+    def test_cairo_surface(self):
+        surface = Regress.test_cairo_surface_none_return()
+        self.assertTrue(isinstance(surface, cairo.ImageSurface))
+        self.assertTrue(isinstance(surface, cairo.Surface))
+        self.assertEqual(surface.get_format(), cairo.FORMAT_ARGB32)
+        self.assertEqual(surface.get_width(), 10)
+        self.assertEqual(surface.get_height(), 10)
+
+        surface = Regress.test_cairo_surface_full_return()
+        self.assertTrue(isinstance(surface, cairo.ImageSurface))
+        self.assertTrue(isinstance(surface, cairo.Surface))
+        self.assertEqual(surface.get_format(), cairo.FORMAT_ARGB32)
+        self.assertEqual(surface.get_width(), 10)
+        self.assertEqual(surface.get_height(), 10)
+
+        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10)
+        Regress.test_cairo_surface_none_in(surface)
+
+        surface = Regress.test_cairo_surface_full_out()
+        self.assertTrue(isinstance(surface, cairo.ImageSurface))
+        self.assertTrue(isinstance(surface, cairo.Surface))
+        self.assertEqual(surface.get_format(), cairo.FORMAT_ARGB32)
+        self.assertEqual(surface.get_width(), 10)
+        self.assertEqual(surface.get_height(), 10)
+
+    def test_cairo_surface_full_in(self):
+        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10)
+        Regress.test_cairo_surface_full_in(surface)
+
+        with pytest.raises(TypeError):
+            Regress.test_cairo_surface_full_in(object())
+
+    def test_require_foreign(self):
+        self.assertEqual(gi.require_foreign('cairo'), None)
+        self.assertEqual(gi.require_foreign('cairo', 'Context'), None)
+        self.assertRaises(ImportError, gi.require_foreign, 'invalid_module')
+        self.assertRaises(ImportError, gi.require_foreign, 'invalid_module', 'invalid_symbol')
+        self.assertRaises(ImportError, gi.require_foreign, 'cairo', 'invalid_symbol')
+
+
+@unittest.skipUnless(has_cairo, 'built without cairo support')
+@unittest.skipUnless(has_region, 'built without cairo.Region support')
+@unittest.skipUnless(Gdk, 'Gdk not available')
+class TestRegion(unittest.TestCase):
+
+    def test_region_to_py(self):
+        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10)
+        context = cairo.Context(surface)
+        context.paint()
+        region = Gdk.cairo_region_create_from_surface(surface)
+        r = region.get_extents()
+        self.assertEqual((r.height, r.width), (10, 10))
+
+    def test_region_from_py(self):
+        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10)
+        context = cairo.Context(surface)
+        region = cairo.Region(cairo.RectangleInt(0, 0, 42, 42))
+        Gdk.cairo_region(context, region)
+        self.assertTrue("42" in repr(list(context.copy_path())))
+
+
+@unittest.skipUnless(has_cairo, 'built without cairo support')
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestPango(unittest.TestCase):
+    def test_cairo_font_options(self):
+        screen = Gtk.Window().get_screen()
+        font_opts = screen.get_font_options()
+        self.assertTrue(isinstance(font_opts.get_subpixel_order(), int))
+
+
+if has_cairo:
+    from gi.repository import cairo as CairoGObject
+
+    # Use PyGI signals to test non-introspected foreign marshaling.
+    class CairoSignalTester(GObject.Object):
+        sig_context = GObject.Signal(arg_types=[CairoGObject.Context])
+        sig_surface = GObject.Signal(arg_types=[CairoGObject.Surface])
+        sig_font_face = GObject.Signal(arg_types=[CairoGObject.FontFace])
+        sig_scaled_font = GObject.Signal(arg_types=[CairoGObject.ScaledFont])
+        sig_pattern = GObject.Signal(arg_types=[CairoGObject.Pattern])
+
+
+@unittest.skipUnless(has_cairo, 'built without cairo support')
+class TestSignalMarshaling(unittest.TestCase):
+    # Tests round tripping of cairo objects through non-introspected signals.
+
+    def setUp(self):
+        self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 10, 10)
+        self.context = cairo.Context(self.surface)
+        self.tester = CairoSignalTester()
+
+    def pass_object_through_signal(self, obj, signal):
+        """Pass the given `obj` through the `signal` emission storing the
+        `obj` passed through the signal and returning it."""
+        passthrough_result = []
+
+        def callback(instance, passthrough):
+            passthrough_result.append(passthrough)
+
+        signal.connect(callback)
+        signal.emit(obj)
+
+        return passthrough_result[0]
+
+    def test_context(self):
+        result = self.pass_object_through_signal(self.context, self.tester.sig_context)
+        self.assertTrue(isinstance(result, cairo.Context))
+
+        with pytest.raises(TypeError):
+            self.pass_object_through_signal(object(), self.tester.sig_context)
+
+    def test_surface(self):
+        result = self.pass_object_through_signal(self.surface, self.tester.sig_surface)
+        self.assertTrue(isinstance(result, cairo.Surface))
+
+    def test_font_face(self):
+        font_face = self.context.get_font_face()
+        result = self.pass_object_through_signal(font_face, self.tester.sig_font_face)
+        self.assertTrue(isinstance(result, cairo.FontFace))
+
+        with pytest.raises(TypeError):
+            self.pass_object_through_signal(object(), self.tester.sig_font_face)
+
+    def test_scaled_font(self):
+        scaled_font = cairo.ScaledFont(self.context.get_font_face(),
+                                       cairo.Matrix(),
+                                       cairo.Matrix(),
+                                       self.context.get_font_options())
+        result = self.pass_object_through_signal(scaled_font, self.tester.sig_scaled_font)
+        self.assertTrue(isinstance(result, cairo.ScaledFont))
+
+        with pytest.raises(TypeError):
+            result = self.pass_object_through_signal(object(), self.tester.sig_scaled_font)
+
+    def test_pattern(self):
+        pattern = cairo.SolidPattern(1, 1, 1, 1)
+        result = self.pass_object_through_signal(pattern, self.tester.sig_pattern)
+        self.assertTrue(isinstance(result, cairo.Pattern))
+        self.assertTrue(isinstance(result, cairo.SolidPattern))
+
+        with pytest.raises(TypeError):
+            result = self.pass_object_through_signal(object(), self.tester.sig_pattern)
diff --git a/tests/test_docstring.py b/tests/test_docstring.py
new file mode 100644 (file)
index 0000000..adee174
--- /dev/null
@@ -0,0 +1,134 @@
+from __future__ import absolute_import
+
+import unittest
+
+import gi.docstring
+
+from gi.repository import Regress
+from gi.repository import GIMarshallingTests
+from gi.repository import Gio
+from gi.repository import GObject
+from gi.repository import GLib
+
+try:
+    from gi.repository import Gtk
+except ImportError:
+    Gtk = None
+
+
+class Test(unittest.TestCase):
+    def test_api(self):
+        new_func = lambda info: 'docstring test'
+        old_func = gi.docstring.get_doc_string_generator()
+
+        gi.docstring.set_doc_string_generator(new_func)
+        self.assertEqual(gi.docstring.get_doc_string_generator(),
+                         new_func)
+        self.assertEqual(gi.docstring.generate_doc_string(None),
+                         'docstring test')
+
+        # Set back to original generator
+        gi.docstring.set_doc_string_generator(old_func)
+        self.assertEqual(gi.docstring.get_doc_string_generator(),
+                         old_func)
+
+    def test_final_signature_with_full_inout(self):
+        self.assertEqual(GIMarshallingTests.Object.full_inout.__doc__,
+                         'full_inout(object:GIMarshallingTests.Object) -> object:GIMarshallingTests.Object')
+
+    def test_overridden_doc_is_not_clobbered(self):
+        self.assertEqual(GIMarshallingTests.OverridesObject.method.__doc__,
+                         'Overridden doc string.')
+
+    def test_allow_none_with_user_data_defaults(self):
+        g_file_copy_doc = 'copy(self, destination:Gio.File, ' \
+                          'flags:Gio.FileCopyFlags, ' \
+                          'cancellable:Gio.Cancellable=None, ' \
+                          'progress_callback:Gio.FileProgressCallback=None, ' \
+                          'progress_callback_data=None) -> bool'
+
+        self.assertEqual(Gio.File.copy.__doc__, g_file_copy_doc)
+
+    def test_array_length_arg(self):
+        self.assertEqual(GIMarshallingTests.array_in.__doc__,
+                         'array_in(ints:list)')
+
+    def test_init_function(self):
+        # This tests implicit array length args along with skipping a
+        # boolean return
+        self.assertEqual(GIMarshallingTests.init_function.__doc__,
+                         'init_function(argv:list=None) -> bool, argv:list')
+
+    def test_boolean_return(self):
+        self.assertEqual(GIMarshallingTests.boolean_return_true.__doc__,
+                         'boolean_return_true() -> bool')
+
+    @unittest.skipUnless((GLib.MAJOR_VERSION, GLib.MINOR_VERSION) >= (2, 42),
+                         "nullable was added in newer glib/gi")
+    # https://bugzilla.gnome.org/show_bug.cgi?id=740301
+    def test_may_return_none(self):
+        self.assertEqual(Gio.File.get_basename.__doc__,
+                         'get_basename(self) -> str or None')
+
+    def test_class_doc_constructors(self):
+        doc = GIMarshallingTests.Object.__doc__
+        self.assertTrue('new(int_:int)' in doc)
+
+    def test_struct_doc_constructors(self):
+        doc = GIMarshallingTests.BoxedStruct.__doc__
+        self.assertTrue('new()' in doc)
+        self.assertTrue('BoxedStruct()' in doc)
+
+    def test_private_struct_constructors(self):
+        # Structs without a size or constructor should have no constructor docs.
+        doc = Regress.TestBoxedPrivate.__doc__
+        self.assertEqual(doc, '')
+
+    def test_array_inout_etc(self):
+        self.assertEqual(GIMarshallingTests.array_inout_etc.__doc__,
+                         'array_inout_etc(first:int, ints:list, last:int) -> ints:list, sum:int')
+
+    def test_array_out_etc(self):
+        self.assertEqual(GIMarshallingTests.array_out_etc.__doc__,
+                         'array_out_etc(first:int, last:int) -> ints:list, sum:int')
+
+    @unittest.skipUnless(Gtk, 'no Gtk')
+    def test_shared_array_length_with_prior_out_arg(self):
+        # Test the 'iter' out argument does not effect length argument skipping.
+        self.assertEqual(Gtk.ListStore.insert_with_valuesv.__doc__,
+                         'insert_with_valuesv(self, position:int, columns:list, values:list) -> iter:Gtk.TreeIter')
+
+    def test_sub_class_doc(self):
+        class A(GObject.Object):
+            """first doc"""
+            pass
+
+        class B(A):
+            """second doc"""
+            pass
+
+        self.assertEqual(A.__doc__, "first doc")
+        self.assertEqual(B.__doc__, "second doc")
+
+    def test_sub_class_no_doc(self):
+        class A(GObject.Object):
+            pass
+
+        class B(A):
+            """sub-class doc"""
+
+        self.assertEqual(A.__doc__, None)
+        self.assertEqual(B.__doc__, "sub-class doc")
+
+    @unittest.expectedFailure  # https://bugzilla.gnome.org/show_bug.cgi?id=734926
+    def test_sub_class_doc_setattr(self):
+        class A(GObject.Object):
+            pass
+
+        class B(A):
+            pass
+
+        A.__doc__ = 'custom doc'
+
+        self.assertEqual(A.__doc__, "custom doc")
+        self.assertEqual(B.__doc__, "custom doc")
diff --git a/tests/test_error.py b/tests/test_error.py
new file mode 100644 (file)
index 0000000..a15c7d3
--- /dev/null
@@ -0,0 +1,159 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# test_error.py: Tests for GError wrapper implementation
+#
+# Copyright (C) 2012 Will Thompson
+# Copyright (C) 2013 Martin Pitt
+# Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from __future__ import absolute_import
+
+import unittest
+import pickle
+
+from gi.repository import GLib
+from gi.repository import GIMarshallingTests
+
+
+class TestType(unittest.TestCase):
+    def test_attributes(self):
+        e = GLib.Error('test message', 'mydomain', 42)
+        self.assertEqual(e.message, 'test message')
+        self.assertEqual(e.domain, 'mydomain')
+        self.assertEqual(e.code, 42)
+
+    def test_new_literal(self):
+        mydomain = GLib.quark_from_string('mydomain')
+        e = GLib.Error.new_literal(mydomain, 'test message', 42)
+        self.assertEqual(e.message, 'test message')
+        self.assertEqual(e.domain, 'mydomain')
+        self.assertEqual(e.code, 42)
+
+    def test_matches(self):
+        mydomain = GLib.quark_from_string('mydomain')
+        notmydomain = GLib.quark_from_string('notmydomain')
+        e = GLib.Error('test message', 'mydomain', 42)
+        self.assertTrue(e.matches(mydomain, 42))
+        self.assertFalse(e.matches(notmydomain, 42))
+        self.assertFalse(e.matches(mydomain, 40))
+
+    def test_str(self):
+        e = GLib.Error('test message', 'mydomain', 42)
+        self.assertEqual(str(e),
+                         'mydomain: test message (42)')
+
+    def test_repr(self):
+        e = GLib.Error('test message', 'mydomain', 42)
+        self.assertEqual(repr(e),
+                         "GLib.Error('test message', 'mydomain', 42)")
+
+    def test_inheritance(self):
+        self.assertTrue(issubclass(GLib.Error, RuntimeError))
+
+    def test_pickle(self):
+
+        def check_pickle(e):
+            assert isinstance(e, GLib.Error)
+            new_e = pickle.loads(pickle.dumps(e))
+            assert type(new_e) is type(e)
+            assert repr(e) == repr(new_e)
+
+        e = GLib.Error('test message', 'mydomain', 42)
+        check_pickle(e)
+
+        try:
+            GLib.file_get_contents("")
+        except Exception as e:
+            check_pickle(e)
+
+
+class ObjectWithVFuncException(GIMarshallingTests.Object):
+    def do_vfunc_meth_with_err(self, x):
+        if x == 42:
+            return True
+
+        raise GLib.Error('unexpected value %d' % x, 'mydomain', 42)
+
+
+class TestMarshalling(unittest.TestCase):
+    def test_array_in_crash(self):
+        # Previously there was a bug in invoke, in which C arrays were unwrapped
+        # from inside GArrays to be passed to the C function. But when a GError was
+        # set, invoke would attempt to free the C array as if it were a GArray.
+        # This crash is only for C arrays. It does not happen for C functions which
+        # take in GArrays. See https://bugzilla.gnome.org/show_bug.cgi?id=642708
+        self.assertRaises(GLib.Error, GIMarshallingTests.gerror_array_in, [1, 2, 3])
+
+    def test_out(self):
+        # See https://bugzilla.gnome.org/show_bug.cgi?id=666098
+        error, debug = GIMarshallingTests.gerror_out()
+
+        self.assertIsInstance(error, GLib.Error)
+        self.assertEqual(error.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN)
+        self.assertEqual(error.code, GIMarshallingTests.CONSTANT_GERROR_CODE)
+        self.assertEqual(error.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE)
+        self.assertEqual(debug, GIMarshallingTests.CONSTANT_GERROR_DEBUG_MESSAGE)
+
+    def test_out_transfer_none(self):
+        # See https://bugzilla.gnome.org/show_bug.cgi?id=666098
+        error, debug = GIMarshallingTests.gerror_out_transfer_none()
+
+        self.assertIsInstance(error, GLib.Error)
+        self.assertEqual(error.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN)
+        self.assertEqual(error.code, GIMarshallingTests.CONSTANT_GERROR_CODE)
+        self.assertEqual(error.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE)
+        self.assertEqual(GIMarshallingTests.CONSTANT_GERROR_DEBUG_MESSAGE, debug)
+
+    def test_return(self):
+        # See https://bugzilla.gnome.org/show_bug.cgi?id=666098
+        error = GIMarshallingTests.gerror_return()
+
+        self.assertIsInstance(error, GLib.Error)
+        self.assertEqual(error.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN)
+        self.assertEqual(error.code, GIMarshallingTests.CONSTANT_GERROR_CODE)
+        self.assertEqual(error.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE)
+
+    def test_exception(self):
+        with self.assertRaises(GLib.Error) as context:
+            GIMarshallingTests.gerror()
+
+        e = context.exception
+        self.assertEqual(e.domain, GIMarshallingTests.CONSTANT_GERROR_DOMAIN)
+        self.assertEqual(e.code, GIMarshallingTests.CONSTANT_GERROR_CODE)
+        self.assertEqual(e.message, GIMarshallingTests.CONSTANT_GERROR_MESSAGE)
+
+    def test_vfunc_no_exception(self):
+        obj = ObjectWithVFuncException()
+        self.assertTrue(obj.vfunc_meth_with_error(42))
+
+    def test_vfunc_gerror_exception(self):
+        obj = ObjectWithVFuncException()
+        with self.assertRaises(GLib.Error) as context:
+            obj.vfunc_meth_with_error(-1)
+
+        e = context.exception
+        self.assertEqual(e.message, 'unexpected value -1')
+        self.assertEqual(e.domain, 'mydomain')
+        self.assertEqual(e.code, 42)
+
+    def tests_compare_two_gerrors_in_gvalue(self):
+        error = GLib.Error.new_literal(1, "error", 1)
+        error1 = GLib.Error.new_literal(1, "error", 1)
+
+        GIMarshallingTests.compare_two_gerrors_in_gvalue(error, error1)
diff --git a/tests/test_everything.py b/tests/test_everything.py
new file mode 100644 (file)
index 0000000..68e8765
--- /dev/null
@@ -0,0 +1,1490 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# coding=utf-8
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import unittest
+import traceback
+import ctypes
+import warnings
+import sys
+import os
+import re
+import platform
+import gc
+import timeit
+
+import pytest
+
+from gi.repository import Regress as Everything
+from gi.repository import GObject
+from gi.repository import GLib
+from gi.repository import Gio
+from gi._compat import PY3, PY2
+
+try:
+    from gi.repository import Gtk
+    Gtk  # pyflakes
+except:
+    Gtk = None
+
+from .helper import capture_exceptions
+
+
+const_str = b'const \xe2\x99\xa5 utf8'
+if PY3:
+    const_str = const_str.decode('UTF-8')
+noconst_str = 'non' + const_str
+
+
+class RawGList(ctypes.Structure):
+    _fields_ = [('data', ctypes.c_void_p),
+                ('next', ctypes.c_void_p),
+                ('prev', ctypes.c_void_p)]
+
+    @classmethod
+    def from_wrapped(cls, obj):
+        offset = sys.getsizeof(object())  # size of PyObject_HEAD
+        return ctypes.POINTER(cls).from_address(id(obj) + offset)
+
+
+class TestInstanceTransfer(unittest.TestCase):
+
+    def test_main(self):
+        obj = Everything.TestObj()
+        for _ in range(10):
+            obj.instance_method_full()
+
+
+class TestEverything(unittest.TestCase):
+
+    def test_bool(self):
+        self.assertEqual(Everything.test_boolean(False), False)
+        self.assertEqual(Everything.test_boolean(True), True)
+        self.assertEqual(Everything.test_boolean('hello'), True)
+        self.assertEqual(Everything.test_boolean(''), False)
+
+        self.assertEqual(Everything.test_boolean_true(True), True)
+        self.assertEqual(Everything.test_boolean_false(False), False)
+
+    def test_int8(self):
+        self.assertEqual(Everything.test_int8(GLib.MAXINT8),
+                         GLib.MAXINT8)
+        self.assertEqual(Everything.test_int8(GLib.MININT8),
+                         GLib.MININT8)
+        self.assertRaises(OverflowError, Everything.test_int8, GLib.MAXINT8 + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXINT8 + 1, GLib.MININT8, GLib.MAXINT8)):
+            Everything.test_int8(GLib.MAXINT8 + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MININT8, GLib.MAXINT8)):
+            Everything.test_int8(GLib.MAXUINT64 * 2)
+
+    def test_uint8(self):
+        self.assertEqual(Everything.test_uint8(GLib.MAXUINT8),
+                         GLib.MAXUINT8)
+        self.assertEqual(Everything.test_uint8(0), 0)
+        self.assertRaises(OverflowError, Everything.test_uint8, -1)
+        self.assertRaises(OverflowError, Everything.test_uint8, GLib.MAXUINT8 + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT8 + 1, GLib.MAXUINT8)):
+            Everything.test_uint8(GLib.MAXUINT8 + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MAXUINT8)):
+            Everything.test_uint8(GLib.MAXUINT64 * 2)
+
+    def test_int16(self):
+        self.assertEqual(Everything.test_int16(GLib.MAXINT16),
+                         GLib.MAXINT16)
+        self.assertEqual(Everything.test_int16(GLib.MININT16),
+                         GLib.MININT16)
+
+        with pytest.raises(
+                OverflowError,
+                match="32768 not in range -32768 to 32767"):
+            Everything.test_int16(GLib.MAXINT16 + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="36893488147419103230 not in range -32768 to 32767"):
+            Everything.test_int16(GLib.MAXUINT64 * 2)
+
+    def test_uint16(self):
+        self.assertEqual(Everything.test_uint16(GLib.MAXUINT16),
+                         GLib.MAXUINT16)
+        self.assertEqual(Everything.test_uint16(0), 0)
+        self.assertRaises(OverflowError, Everything.test_uint16, -1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT16 + 1, GLib.MAXUINT16)):
+            Everything.test_uint16(GLib.MAXUINT16 + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MAXUINT16)):
+            Everything.test_uint16(GLib.MAXUINT64 * 2)
+
+    def test_int32(self):
+        self.assertEqual(Everything.test_int32(GLib.MAXINT32),
+                         GLib.MAXINT32)
+        self.assertEqual(Everything.test_int32(GLib.MININT32),
+                         GLib.MININT32)
+
+        with pytest.raises(
+                OverflowError,
+                match="2147483648 not in range -2147483648 to 2147483647"):
+            Everything.test_int32(GLib.MAXINT32 + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range -2147483648 to 2147483647" % (
+                    GLib.MAXINT64 + 1,)):
+            Everything.test_int32(GLib.MAXINT64 + 1)
+
+    def test_uint32(self):
+        self.assertEqual(Everything.test_uint32(GLib.MAXUINT32),
+                         GLib.MAXUINT32)
+        self.assertEqual(Everything.test_uint32(0), 0)
+        self.assertRaises(OverflowError, Everything.test_uint32, -1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT32 + 1, GLib.MAXUINT32)):
+            Everything.test_uint32(GLib.MAXUINT32 + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MAXUINT32)):
+            Everything.test_uint32(GLib.MAXUINT64 * 2)
+
+    def test_int64(self):
+        self.assertEqual(Everything.test_int64(GLib.MAXINT64),
+                         GLib.MAXINT64)
+        self.assertEqual(Everything.test_int64(GLib.MININT64),
+                         GLib.MININT64)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXINT64 + 1, GLib.MININT64, GLib.MAXINT64)):
+            Everything.test_int64(GLib.MAXINT64 + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MININT64, GLib.MAXINT64)):
+            Everything.test_int64(GLib.MAXUINT64 * 2)
+
+    def test_uint64(self):
+        self.assertEqual(Everything.test_uint64(GLib.MAXUINT64),
+                         GLib.MAXUINT64)
+        self.assertEqual(Everything.test_uint64(0), 0)
+        self.assertRaises(OverflowError, Everything.test_uint64, -1)
+        self.assertRaises(OverflowError, Everything.test_uint64, GLib.MAXUINT64 + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT64 + 1, GLib.MAXUINT64)):
+            Everything.test_uint64(GLib.MAXUINT64 + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MAXUINT64)):
+            Everything.test_uint64(GLib.MAXUINT64 * 2)
+
+    def test_int(self):
+        self.assertEqual(Everything.test_int(GLib.MAXINT),
+                         GLib.MAXINT)
+        self.assertEqual(Everything.test_int(GLib.MININT),
+                         GLib.MININT)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXINT + 1, GLib.MININT, GLib.MAXINT)):
+            Everything.test_int(GLib.MAXINT + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MININT, GLib.MAXINT)):
+            Everything.test_int(GLib.MAXUINT64 * 2)
+
+    def test_uint(self):
+        self.assertEqual(Everything.test_uint(GLib.MAXUINT),
+                         GLib.MAXUINT)
+        self.assertEqual(Everything.test_uint(0), 0)
+        self.assertRaises(OverflowError, Everything.test_uint, -1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT + 1, GLib.MAXUINT)):
+            Everything.test_uint(GLib.MAXUINT + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MAXUINT)):
+            Everything.test_uint(GLib.MAXUINT64 * 2)
+
+    def test_short(self):
+        self.assertEqual(Everything.test_short(GLib.MAXSHORT),
+                         GLib.MAXSHORT)
+        self.assertEqual(Everything.test_short(GLib.MINSHORT),
+                         GLib.MINSHORT)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXSHORT + 1, GLib.MINSHORT, GLib.MAXSHORT)):
+            Everything.test_short(GLib.MAXSHORT + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MINSHORT, GLib.MAXSHORT)):
+            Everything.test_short(GLib.MAXUINT64 * 2)
+
+    def test_ushort(self):
+        self.assertEqual(Everything.test_ushort(GLib.MAXUSHORT),
+                         GLib.MAXUSHORT)
+        self.assertEqual(Everything.test_ushort(0), 0)
+        self.assertRaises(OverflowError, Everything.test_ushort, -1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUSHORT + 1, GLib.MAXUSHORT)):
+            Everything.test_ushort(GLib.MAXUSHORT + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MAXUSHORT)):
+            Everything.test_ushort(GLib.MAXUINT64 * 2)
+
+    def test_long(self):
+        self.assertEqual(Everything.test_long(GLib.MAXLONG),
+                         GLib.MAXLONG)
+        self.assertEqual(Everything.test_long(GLib.MINLONG),
+                         GLib.MINLONG)
+        self.assertRaises(OverflowError, Everything.test_long, GLib.MAXLONG + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXLONG + 1, GLib.MINLONG, GLib.MAXLONG)):
+            Everything.test_long(GLib.MAXLONG + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MINLONG, GLib.MAXLONG)):
+            Everything.test_long(GLib.MAXUINT64 * 2)
+
+    def test_ulong(self):
+        self.assertEqual(Everything.test_ulong(GLib.MAXULONG),
+                         GLib.MAXULONG)
+        self.assertEqual(Everything.test_ulong(0), 0)
+        self.assertRaises(OverflowError, Everything.test_ulong, -1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXULONG + 1, GLib.MAXULONG)):
+            Everything.test_ulong(GLib.MAXULONG + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MAXULONG)):
+            Everything.test_ulong(GLib.MAXUINT64 * 2)
+
+    def test_ssize(self):
+        self.assertEqual(Everything.test_ssize(GLib.MAXSSIZE),
+                         GLib.MAXSSIZE)
+        self.assertEqual(Everything.test_ssize(GLib.MINSSIZE),
+                         GLib.MINSSIZE)
+        self.assertRaises(OverflowError, Everything.test_ssize, GLib.MAXSSIZE + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXSSIZE + 1, GLib.MINSSIZE, GLib.MAXSSIZE)):
+            Everything.test_ssize(GLib.MAXSSIZE + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range %s to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MINSSIZE, GLib.MAXSSIZE)):
+            Everything.test_ssize(GLib.MAXUINT64 * 2)
+
+    def test_size(self):
+        self.assertEqual(Everything.test_size(GLib.MAXSIZE),
+                         GLib.MAXSIZE)
+        self.assertEqual(Everything.test_size(0), 0)
+        self.assertRaises(OverflowError, Everything.test_size, -1)
+        self.assertRaises(OverflowError, Everything.test_size, GLib.MAXSIZE + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXSIZE + 1, GLib.MAXSIZE)):
+            Everything.test_size(GLib.MAXSIZE + 1)
+
+        with pytest.raises(
+                OverflowError,
+                match="%s not in range 0 to %s" % (
+                    GLib.MAXUINT64 * 2, GLib.MAXSIZE)):
+            Everything.test_size(GLib.MAXUINT64 * 2)
+
+    def test_timet(self):
+        self.assertEqual(Everything.test_timet(42), 42)
+        self.assertRaises(OverflowError, Everything.test_timet, GLib.MAXUINT64 + 1)
+
+    def test_unichar(self):
+        self.assertEqual("c", Everything.test_unichar("c"))
+
+        if PY2:
+            self.assertEqual(b"\xe2\x99\xa5", Everything.test_unichar(u"♥"))
+            self.assertEqual(b"\xe2\x99\xa5", Everything.test_unichar(b"\xe2\x99\xa5"))
+        else:
+            self.assertEqual(u"♥", Everything.test_unichar(u"♥"))
+        self.assertRaises(TypeError, Everything.test_unichar, "")
+        self.assertRaises(TypeError, Everything.test_unichar, "morethanonechar")
+
+    def test_float(self):
+        self.assertEqual(Everything.test_float(GLib.MAXFLOAT),
+                         GLib.MAXFLOAT)
+        self.assertEqual(Everything.test_float(GLib.MINFLOAT),
+                         GLib.MINFLOAT)
+        self.assertRaises(OverflowError, Everything.test_float, GLib.MAXFLOAT * 2)
+
+        with pytest.raises(
+                OverflowError,
+                match=re.escape("%s not in range %s to %s" % (
+                    GLib.MAXFLOAT * 2, -GLib.MAXFLOAT, GLib.MAXFLOAT))):
+            Everything.test_float(GLib.MAXFLOAT * 2)
+
+    def test_double(self):
+        self.assertEqual(Everything.test_double(GLib.MAXDOUBLE),
+                         GLib.MAXDOUBLE)
+        self.assertEqual(Everything.test_double(GLib.MINDOUBLE),
+                         GLib.MINDOUBLE)
+
+        (two, three) = Everything.test_multi_double_args(2.5)
+        self.assertAlmostEqual(two, 5.0)
+        self.assertAlmostEqual(three, 7.5)
+
+    def test_value(self):
+        self.assertEqual(Everything.test_int_value_arg(GLib.MAXINT), GLib.MAXINT)
+        self.assertEqual(Everything.test_value_return(GLib.MAXINT), GLib.MAXINT)
+
+    def test_variant(self):
+        v = Everything.test_gvariant_i()
+        self.assertEqual(v.get_type_string(), 'i')
+        self.assertEqual(v.get_int32(), 1)
+
+        v = Everything.test_gvariant_s()
+        self.assertEqual(v.get_type_string(), 's')
+        self.assertEqual(v.get_string(), 'one')
+
+        v = Everything.test_gvariant_v()
+        self.assertEqual(v.get_type_string(), 'v')
+        vi = v.get_variant()
+        self.assertEqual(vi.get_type_string(), 's')
+        self.assertEqual(vi.get_string(), 'contents')
+
+        v = Everything.test_gvariant_as()
+        self.assertEqual(v.get_type_string(), 'as')
+        self.assertEqual(v.get_strv(), ['one', 'two', 'three'])
+
+        v = Everything.test_gvariant_asv()
+        self.assertEqual(v.get_type_string(), 'a{sv}')
+        self.assertEqual(v.lookup_value('nosuchkey', None), None)
+        name = v.lookup_value('name', None)
+        self.assertEqual(name.get_string(), 'foo')
+        timeout = v.lookup_value('timeout', None)
+        self.assertEqual(timeout.get_int32(), 10)
+
+    def test_utf8_const_return(self):
+        self.assertEqual(Everything.test_utf8_const_return(), const_str)
+
+    def test_utf8_nonconst_return(self):
+        self.assertEqual(Everything.test_utf8_nonconst_return(), noconst_str)
+
+    def test_utf8_out(self):
+        self.assertEqual(Everything.test_utf8_out(), noconst_str)
+
+    def test_utf8_const_in(self):
+        Everything.test_utf8_const_in(const_str)
+
+    def test_utf8_inout(self):
+        self.assertEqual(Everything.test_utf8_inout(const_str), noconst_str)
+
+    def test_filename_return(self):
+        if PY3 and os.name != "nt":
+            result = [os.fsdecode(b'\xc3\xa5\xc3\xa4\xc3\xb6'), '/etc/fstab']
+        else:
+            result = ['åäö', '/etc/fstab']
+        self.assertEqual(Everything.test_filename_return(), result)
+
+    def test_int_out_utf8(self):
+        # returns g_utf8_strlen() in out argument
+        self.assertEqual(Everything.test_int_out_utf8(''), 0)
+        self.assertEqual(Everything.test_int_out_utf8('hello world'), 11)
+        self.assertEqual(Everything.test_int_out_utf8('åäö'), 3)
+
+    def test_utf8_out_out(self):
+        self.assertEqual(Everything.test_utf8_out_out(), ('first', 'second'))
+
+    def test_utf8_out_nonconst_return(self):
+        self.assertEqual(Everything.test_utf8_out_nonconst_return(), ('first', 'second'))
+
+    def test_enum(self):
+        self.assertEqual(Everything.test_enum_param(Everything.TestEnum.VALUE1), 'value1')
+        self.assertEqual(Everything.test_enum_param(Everything.TestEnum.VALUE3), 'value3')
+        self.assertRaises(TypeError, Everything.test_enum_param, 'hello')
+
+    # FIXME: ValueError: invalid enum value: 2147483648
+    @unittest.expectedFailure
+    def test_enum_unsigned(self):
+        self.assertEqual(Everything.test_unsigned_enum_param(Everything.TestEnumUnsigned.VALUE1), 'value1')
+        self.assertEqual(Everything.test_unsigned_enum_param(Everything.TestEnumUnsigned.VALUE3), 'value3')
+        self.assertRaises(TypeError, Everything.test_unsigned_enum_param, 'hello')
+
+    def test_flags(self):
+        result = Everything.global_get_flags_out()
+        # assert that it's not an int
+        self.assertEqual(type(result), Everything.TestFlags)
+        self.assertEqual(result, Everything.TestFlags.FLAG1 | Everything.TestFlags.FLAG3)
+
+    def test_floating(self):
+        e = Everything.TestFloating()
+        self.assertEqual(e.__grefcount__, 1)
+
+        e = GObject.new(Everything.TestFloating)
+        self.assertEqual(e.__grefcount__, 1)
+
+        e = Everything.TestFloating.new()
+        self.assertEqual(e.__grefcount__, 1)
+
+    def test_caller_allocates(self):
+        struct_a = Everything.TestStructA()
+        struct_a.some_int = 10
+        struct_a.some_int8 = 21
+        struct_a.some_double = 3.14
+        struct_a.some_enum = Everything.TestEnum.VALUE3
+
+        struct_a_clone = struct_a.clone()
+        self.assertTrue(struct_a != struct_a_clone)
+        self.assertEqual(struct_a.some_int, struct_a_clone.some_int)
+        self.assertEqual(struct_a.some_int8, struct_a_clone.some_int8)
+        self.assertEqual(struct_a.some_double, struct_a_clone.some_double)
+        self.assertEqual(struct_a.some_enum, struct_a_clone.some_enum)
+
+        struct_b = Everything.TestStructB()
+        struct_b.some_int8 = 8
+        struct_b.nested_a.some_int = 20
+        struct_b.nested_a.some_int8 = 12
+        struct_b.nested_a.some_double = 333.3333
+        struct_b.nested_a.some_enum = Everything.TestEnum.VALUE2
+
+        struct_b_clone = struct_b.clone()
+        self.assertTrue(struct_b != struct_b_clone)
+        self.assertEqual(struct_b.some_int8, struct_b_clone.some_int8)
+        self.assertEqual(struct_b.nested_a.some_int, struct_b_clone.nested_a.some_int)
+        self.assertEqual(struct_b.nested_a.some_int8, struct_b_clone.nested_a.some_int8)
+        self.assertEqual(struct_b.nested_a.some_double, struct_b_clone.nested_a.some_double)
+        self.assertEqual(struct_b.nested_a.some_enum, struct_b_clone.nested_a.some_enum)
+
+        struct_a = Everything.test_struct_a_parse('ignored')
+        self.assertEqual(struct_a.some_int, 23)
+
+    def test_wrong_type_of_arguments(self):
+        try:
+            Everything.test_int8()
+        except TypeError:
+            (e_type, e) = sys.exc_info()[:2]
+            self.assertEqual(e.args, ("Regress.test_int8() takes exactly 1 argument (0 given)",))
+
+    def test_gtypes(self):
+        gchararray_gtype = GObject.type_from_name('gchararray')
+        gtype = Everything.test_gtype(str)
+        self.assertEqual(gchararray_gtype, gtype)
+        gtype = Everything.test_gtype('gchararray')
+        self.assertEqual(gchararray_gtype, gtype)
+        gobject_gtype = GObject.GObject.__gtype__
+        gtype = Everything.test_gtype(GObject.GObject)
+        self.assertEqual(gobject_gtype, gtype)
+        gtype = Everything.test_gtype('GObject')
+        self.assertEqual(gobject_gtype, gtype)
+        self.assertRaises(TypeError, Everything.test_gtype, 'invalidgtype')
+
+        class NotARegisteredClass(object):
+            pass
+
+        self.assertRaises(TypeError, Everything.test_gtype, NotARegisteredClass)
+
+        class ARegisteredClass(GObject.GObject):
+            __gtype_name__ = 'EverythingTestsARegisteredClass'
+
+        gtype = Everything.test_gtype('EverythingTestsARegisteredClass')
+        self.assertEqual(ARegisteredClass.__gtype__, gtype)
+        gtype = Everything.test_gtype(ARegisteredClass)
+        self.assertEqual(ARegisteredClass.__gtype__, gtype)
+        self.assertRaises(TypeError, Everything.test_gtype, 'ARegisteredClass')
+
+    def test_dir(self):
+        attr_list = dir(Everything)
+
+        # test that typelib attributes are listed
+        self.assertTrue('TestStructA' in attr_list)
+
+        # test that class attributes and methods are listed
+        self.assertTrue('__class__' in attr_list)
+        self.assertTrue('__dir__' in attr_list)
+        self.assertTrue('__repr__' in attr_list)
+
+        # test that instance members are listed
+        self.assertTrue('_namespace' in attr_list)
+        self.assertTrue('_version' in attr_list)
+
+        # test that there are no duplicates returned
+        self.assertEqual(len(attr_list), len(set(attr_list)))
+
+    def test_array_int_in_empty(self):
+        self.assertEqual(Everything.test_array_int_in([]), 0)
+
+    def test_array_int_in(self):
+        self.assertEqual(Everything.test_array_int_in([1, 5, -2]), 4)
+
+    def test_array_int_out(self):
+        self.assertEqual(Everything.test_array_int_out(), [0, 1, 2, 3, 4])
+
+    def test_array_int_full_out(self):
+        self.assertEqual(Everything.test_array_int_full_out(), [0, 1, 2, 3, 4])
+
+    def test_array_int_none_out(self):
+        self.assertEqual(Everything.test_array_int_none_out(), [1, 2, 3, 4, 5])
+
+    def test_array_int_inout(self):
+        self.assertEqual(Everything.test_array_int_inout([1, 5, 42, -8]), [6, 43, -7])
+
+    def test_array_int_inout_empty(self):
+        self.assertEqual(Everything.test_array_int_inout([]), [])
+
+    def test_array_gint8_in(self):
+        if PY3:
+            self.assertEqual(Everything.test_array_gint8_in(b'\x01\x03\x05'), 9)
+        self.assertEqual(Everything.test_array_gint8_in([1, 3, 5, -50]), -41)
+
+    def test_array_gint16_in(self):
+        self.assertEqual(Everything.test_array_gint16_in([256, 257, -1000, 10000]), 9513)
+
+    def test_array_gint32_in(self):
+        self.assertEqual(Everything.test_array_gint32_in([30000, 1, -2]), 29999)
+
+    def test_array_gint64_in(self):
+        self.assertEqual(Everything.test_array_gint64_in([2 ** 33, 2 ** 34]), 2 ** 33 + 2 ** 34)
+
+    def test_array_gtype_in(self):
+        self.assertEqual(Everything.test_array_gtype_in(
+            [GObject.TYPE_STRING, GObject.TYPE_UINT64, GObject.TYPE_VARIANT]),
+            '[gchararray,guint64,GVariant,]')
+
+    def test_array_fixed_size_int_in(self):
+        # fixed length of 5
+        self.assertEqual(Everything.test_array_fixed_size_int_in([1, 2, -10, 5, 3]), 1)
+
+    def test_array_fixed_size_int_in_error(self):
+        self.assertRaises(ValueError, Everything.test_array_fixed_size_int_in, [1, 2, 3, 4])
+        self.assertRaises(ValueError, Everything.test_array_fixed_size_int_in, [1, 2, 3, 4, 5, 6])
+
+    def test_array_fixed_size_int_out(self):
+        self.assertEqual(Everything.test_array_fixed_size_int_out(), [0, 1, 2, 3, 4])
+
+    def test_array_fixed_size_int_return(self):
+        self.assertEqual(Everything.test_array_fixed_size_int_return(), [0, 1, 2, 3, 4])
+
+    def test_garray_container_return(self):
+        # GPtrArray transfer container
+        result = Everything.test_garray_container_return()
+        self.assertEqual(result, ['regress'])
+        result = None
+
+    def test_garray_full_return(self):
+        # GPtrArray transfer full
+        result = Everything.test_garray_full_return()
+        self.assertEqual(result, ['regress'])
+        result = None
+
+    def test_strv_out(self):
+        self.assertEqual(Everything.test_strv_out(), ['thanks', 'for', 'all', 'the', 'fish'])
+
+    def test_strv_out_c(self):
+        self.assertEqual(Everything.test_strv_out_c(), ['thanks', 'for', 'all', 'the', 'fish'])
+
+    def test_strv_out_container(self):
+        self.assertEqual(Everything.test_strv_out_container(), ['1', '2', '3'])
+
+    def test_strv_outarg(self):
+        self.assertEqual(Everything.test_strv_outarg(), ['1', '2', '3'])
+
+    def test_strv_in_gvalue(self):
+        self.assertEqual(Everything.test_strv_in_gvalue(), ['one', 'two', 'three'])
+
+    def test_strv_in(self):
+        Everything.test_strv_in(['1', '2', '3'])
+
+    def test_glist(self):
+        self.assertEqual(Everything.test_glist_nothing_return(), ['1', '2', '3'])
+        self.assertEqual(Everything.test_glist_nothing_return2(), ['1', '2', '3'])
+        self.assertEqual(Everything.test_glist_container_return(), ['1', '2', '3'])
+        self.assertEqual(Everything.test_glist_everything_return(), ['1', '2', '3'])
+
+        Everything.test_glist_nothing_in(['1', '2', '3'])
+        Everything.test_glist_nothing_in2(['1', '2', '3'])
+
+    @unittest.skipUnless(hasattr(Everything, 'test_glist_gtype_container_in'),
+                         'Requires newer version of GI')
+    def test_glist_gtype(self):
+        Everything.test_glist_gtype_container_in(
+            [Everything.TestObj, Everything.TestSubObj])
+
+    def test_gslist(self):
+        self.assertEqual(Everything.test_gslist_nothing_return(), ['1', '2', '3'])
+        self.assertEqual(Everything.test_gslist_nothing_return2(), ['1', '2', '3'])
+        self.assertEqual(Everything.test_gslist_container_return(), ['1', '2', '3'])
+        self.assertEqual(Everything.test_gslist_everything_return(), ['1', '2', '3'])
+
+        Everything.test_gslist_nothing_in(['1', '2', '3'])
+        Everything.test_gslist_nothing_in2(['1', '2', '3'])
+
+    def test_hash_return(self):
+        expected = {'foo': 'bar', 'baz': 'bat', 'qux': 'quux'}
+
+        self.assertEqual(Everything.test_ghash_null_return(), None)
+        self.assertEqual(Everything.test_ghash_nothing_return(), expected)
+        self.assertEqual(Everything.test_ghash_nothing_return(), expected)
+        self.assertEqual(Everything.test_ghash_container_return(), expected)
+        self.assertEqual(Everything.test_ghash_everything_return(), expected)
+
+        result = Everything.test_ghash_gvalue_return()
+        self.assertEqual(result['integer'], 12)
+        self.assertEqual(result['boolean'], True)
+        self.assertEqual(result['string'], 'some text')
+        self.assertEqual(result['strings'], ['first', 'second', 'third'])
+        self.assertEqual(result['flags'], Everything.TestFlags.FLAG1 | Everything.TestFlags.FLAG3)
+        self.assertEqual(result['enum'], Everything.TestEnum.VALUE2)
+        result = None
+
+    # FIXME: CRITICAL **: Unsupported type ghash
+    def disabled_test_hash_return_nested(self):
+        self.assertEqual(Everything.test_ghash_nested_everything_return(), {})
+        self.assertEqual(Everything.test_ghash_nested_everything_return2(), {})
+
+    def test_hash_in(self):
+        expected = {'foo': 'bar', 'baz': 'bat', 'qux': 'quux'}
+
+        Everything.test_ghash_nothing_in(expected)
+        Everything.test_ghash_nothing_in2(expected)
+
+    def test_hash_in_with_typed_strv(self):
+        class GStrv(list):
+            __gtype__ = GObject.TYPE_STRV
+
+        data = {'integer': 12,
+                'boolean': True,
+                'string': 'some text',
+                'strings': GStrv(['first', 'second', 'third']),
+                'flags': Everything.TestFlags.FLAG1 | Everything.TestFlags.FLAG3,
+                'enum': Everything.TestEnum.VALUE2,
+               }
+        Everything.test_ghash_gvalue_in(data)
+        data = None
+
+    def test_hash_in_with_gvalue_strv(self):
+        data = {'integer': 12,
+                'boolean': True,
+                'string': 'some text',
+                'strings': GObject.Value(GObject.TYPE_STRV, ['first', 'second', 'third']),
+                'flags': Everything.TestFlags.FLAG1 | Everything.TestFlags.FLAG3,
+                'enum': Everything.TestEnum.VALUE2,
+               }
+        Everything.test_ghash_gvalue_in(data)
+        data = None
+
+    @unittest.skipIf(platform.python_implementation() == "PyPy", "CPython only")
+    def test_struct_gpointer(self):
+        glist = GLib.List()
+        raw = RawGList.from_wrapped(glist)
+
+        # Note that pointer fields use 0 for NULL in PyGObject and None in ctypes
+        self.assertEqual(glist.data, 0)
+        self.assertEqual(raw.contents.data, None)
+
+        glist.data = 123
+        self.assertEqual(glist.data, 123)
+        self.assertEqual(raw.contents.data, 123)
+
+        glist.data = None
+        self.assertEqual(glist.data, 0)
+        self.assertEqual(raw.contents.data, None)
+
+        # Setting to anything other than an int should raise
+        self.assertRaises(TypeError, setattr, glist.data, 'nan')
+        self.assertRaises(TypeError, setattr, glist.data, object())
+        self.assertRaises(TypeError, setattr, glist.data, 123.321)
+
+    def test_struct_opaque(self):
+        # we should get a sensible error message
+        try:
+            Everything.TestBoxedPrivate()
+            self.fail('allocating disguised struct without default constructor unexpectedly succeeded')
+        except TypeError:
+            (e_type, e_value, e_tb) = sys.exc_info()
+            self.assertEqual(e_type, TypeError)
+            self.assertTrue('TestBoxedPrivate' in str(e_value), str(e_value))
+            self.assertTrue('constructor' in str(e_value), str(e_value))
+            tb = ''.join(traceback.format_exception(e_type, e_value, e_tb))
+            self.assertTrue('test_everything.py", line' in tb, tb)
+
+
+class TestNullableArgs(unittest.TestCase):
+    def test_in_nullable_hash(self):
+        Everything.test_ghash_null_in(None)
+
+    def test_in_nullable_list(self):
+        Everything.test_gslist_null_in(None)
+        Everything.test_glist_null_in(None)
+        Everything.test_gslist_null_in([])
+        Everything.test_glist_null_in([])
+
+    def test_in_nullable_array(self):
+        Everything.test_array_int_null_in(None)
+        Everything.test_array_int_null_in([])
+
+    def test_in_nullable_string(self):
+        Everything.test_utf8_null_in(None)
+
+    def test_in_nullable_object(self):
+        Everything.func_obj_null_in(None)
+
+    def test_out_nullable_hash(self):
+        self.assertEqual(None, Everything.test_ghash_null_out())
+
+    def test_out_nullable_list(self):
+        self.assertEqual([], Everything.test_gslist_null_out())
+        self.assertEqual([], Everything.test_glist_null_out())
+
+    def test_out_nullable_array(self):
+        self.assertEqual([], Everything.test_array_int_null_out())
+
+    def test_out_nullable_string(self):
+        self.assertEqual(None, Everything.test_utf8_null_out())
+
+    def test_out_nullable_object(self):
+        self.assertEqual(None, Everything.TestObj.null_out())
+
+
+class TestCallbacks(unittest.TestCase):
+    called = False
+    main_loop = GLib.MainLoop()
+
+    def test_callback(self):
+        TestCallbacks.called = False
+
+        def callback():
+            TestCallbacks.called = True
+
+        Everything.test_simple_callback(callback)
+        self.assertTrue(TestCallbacks.called)
+
+    def test_callback_exception(self):
+        """
+        This test ensures that we get errors from callbacks correctly
+        and in particular that we do not segv when callbacks fail
+        """
+        def callback():
+            x = 1 / 0
+            self.fail('unexpected surviving zero divsion:' + str(x))
+
+        # note that we do NOT expect the ZeroDivisionError to be propagated
+        # through from the callback, as it crosses the Python<->C boundary
+        # twice. (See GNOME #616279)
+        with capture_exceptions() as exc:
+            Everything.test_simple_callback(callback)
+        self.assertTrue(exc)
+        self.assertEqual(exc[0].type, ZeroDivisionError)
+
+    def test_double_callback_exception(self):
+        """
+        This test ensures that we get errors from callbacks correctly
+        and in particular that we do not segv when callbacks fail
+        """
+        def badcallback():
+            x = 1 / 0
+            self.fail('unexpected surviving zero divsion:' + str(x))
+
+        def callback():
+            Everything.test_boolean(True)
+            Everything.test_boolean(False)
+            Everything.test_simple_callback(badcallback())
+
+        # note that we do NOT expect the ZeroDivisionError to be propagated
+        # through from the callback, as it crosses the Python<->C boundary
+        # twice. (See GNOME #616279)
+        with capture_exceptions() as exc:
+            Everything.test_simple_callback(callback)
+        self.assertTrue(exc)
+        self.assertEqual(exc[0].type, ZeroDivisionError)
+
+    def test_return_value_callback(self):
+        TestCallbacks.called = False
+
+        def callback():
+            TestCallbacks.called = True
+            return 44
+
+        self.assertEqual(Everything.test_callback(callback), 44)
+        self.assertTrue(TestCallbacks.called)
+
+    def test_callback_scope_async(self):
+        TestCallbacks.called = False
+        ud = 'Test Value 44'
+
+        def callback(user_data):
+            self.assertEqual(user_data, ud)
+            TestCallbacks.called = True
+            return 44
+
+        if hasattr(sys, "getrefcount"):
+            ud_refcount = sys.getrefcount(ud)
+            callback_refcount = sys.getrefcount(callback)
+
+        self.assertEqual(Everything.test_callback_async(callback, ud), None)
+        # Callback should not have run and the ref count is increased by 1
+        self.assertEqual(TestCallbacks.called, False)
+
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(callback), callback_refcount + 1)
+            self.assertEqual(sys.getrefcount(ud), ud_refcount + 1)
+
+        # test_callback_thaw_async will run the callback previously supplied.
+        # references should be auto decremented after this call.
+        self.assertEqual(Everything.test_callback_thaw_async(), 44)
+        self.assertTrue(TestCallbacks.called)
+
+        # Make sure refcounts are returned to normal
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(callback), callback_refcount)
+            self.assertEqual(sys.getrefcount(ud), ud_refcount)
+
+    def test_callback_scope_call_multi(self):
+        # This tests a callback that gets called multiple times from a
+        # single scope call in python.
+        TestCallbacks.called = 0
+
+        def callback():
+            TestCallbacks.called += 1
+            return TestCallbacks.called
+
+        if hasattr(sys, "getrefcount"):
+            refcount = sys.getrefcount(callback)
+        result = Everything.test_multi_callback(callback)
+        # first callback should give 1, second 2, and the function sums them up
+        self.assertEqual(result, 3)
+        self.assertEqual(TestCallbacks.called, 2)
+
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(callback), refcount)
+
+    def test_callback_scope_call_array(self):
+        # This tests a callback that gets called multiple times from a
+        # single scope call in python with array arguments
+        TestCallbacks.callargs = []
+
+        # FIXME: would be cleaner without the explicit length args:
+        # def callback(one, two):
+        def callback(one, one_length, two, two_length):
+            TestCallbacks.callargs.append((one, two))
+            return len(TestCallbacks.callargs)
+
+        if hasattr(sys, "getrefcount"):
+            refcount = sys.getrefcount(callback)
+        result = Everything.test_array_callback(callback)
+        # first callback should give 1, second 2, and the function sums them up
+        self.assertEqual(result, 3)
+        self.assertEqual(TestCallbacks.callargs,
+                         [([-1, 0, 1, 2], ['one', 'two', 'three'])] * 2)
+
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(callback), refcount)
+
+    @unittest.skipUnless(hasattr(Everything, 'test_array_inout_callback'),
+                         'Requires newer version of GI')
+    def test_callback_scope_call_array_inout(self):
+        # This tests a callback that gets called multiple times from a
+        # single scope call in python with inout array arguments
+        TestCallbacks.callargs = []
+
+        def callback(ints, ints_length):
+            TestCallbacks.callargs.append(ints)
+            return ints[1:], len(ints[1:])
+
+        if hasattr(sys, "getrefcount"):
+            refcount = sys.getrefcount(callback)
+        result = Everything.test_array_inout_callback(callback)
+        self.assertEqual(TestCallbacks.callargs,
+                         [[-2, -1, 0, 1, 2], [-1, 0, 1, 2]])
+        # first callback should give 4, second 3
+        self.assertEqual(result, 3)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(callback), refcount)
+
+    def test_callback_userdata(self):
+        TestCallbacks.called = 0
+
+        def callback(userdata):
+            self.assertEqual(userdata, "Test%d" % TestCallbacks.called)
+            TestCallbacks.called += 1
+            return TestCallbacks.called
+
+        for i in range(100):
+            val = Everything.test_callback_user_data(callback, "Test%d" % i)
+            self.assertEqual(val, i + 1)
+
+        self.assertEqual(TestCallbacks.called, 100)
+
+    def test_callback_userdata_no_user_data(self):
+        TestCallbacks.called = 0
+
+        def callback():
+            TestCallbacks.called += 1
+            return TestCallbacks.called
+
+        for i in range(100):
+            val = Everything.test_callback_user_data(callback)
+            self.assertEqual(val, i + 1)
+
+        self.assertEqual(TestCallbacks.called, 100)
+
+    def test_callback_userdata_varargs(self):
+        TestCallbacks.called = 0
+        collected_user_data = []
+
+        def callback(a, b):
+            collected_user_data.extend([a, b])
+            TestCallbacks.called += 1
+            return TestCallbacks.called
+
+        for i in range(10):
+            val = Everything.test_callback_user_data(callback, 1, 2)
+            self.assertEqual(val, i + 1)
+
+        self.assertEqual(TestCallbacks.called, 10)
+        self.assertSequenceEqual(collected_user_data, [1, 2] * 10)
+
+    def test_callback_userdata_as_kwarg_tuple(self):
+        TestCallbacks.called = 0
+        collected_user_data = []
+
+        def callback(user_data):
+            collected_user_data.extend(user_data)
+            TestCallbacks.called += 1
+            return TestCallbacks.called
+
+        for i in range(10):
+            val = Everything.test_callback_user_data(callback, user_data=(1, 2))
+            self.assertEqual(val, i + 1)
+
+        self.assertEqual(TestCallbacks.called, 10)
+        self.assertSequenceEqual(collected_user_data, [1, 2] * 10)
+
+    def test_callback_user_data_middle_none(self):
+        cb_info = {}
+
+        def callback(userdata):
+            cb_info['called'] = True
+            cb_info['userdata'] = userdata
+            return 1
+
+        (y, z, q) = Everything.test_torture_signature_2(
+            42, callback, None, 'some string', 3)
+        self.assertEqual(y, 42)
+        self.assertEqual(z, 84)
+        self.assertEqual(q, 14)
+        self.assertTrue(cb_info['called'])
+        self.assertEqual(cb_info['userdata'], None)
+
+    def test_callback_user_data_middle_single(self):
+        cb_info = {}
+
+        def callback(userdata):
+            cb_info['called'] = True
+            cb_info['userdata'] = userdata
+            return 1
+
+        (y, z, q) = Everything.test_torture_signature_2(
+            42, callback, 'User Data', 'some string', 3)
+        self.assertEqual(y, 42)
+        self.assertEqual(z, 84)
+        self.assertEqual(q, 14)
+        self.assertTrue(cb_info['called'])
+        self.assertEqual(cb_info['userdata'], 'User Data')
+
+    def test_callback_user_data_middle_tuple(self):
+        cb_info = {}
+
+        def callback(userdata):
+            cb_info['called'] = True
+            cb_info['userdata'] = userdata
+            return 1
+
+        (y, z, q) = Everything.test_torture_signature_2(
+            42, callback, (-5, 'User Data'), 'some string', 3)
+        self.assertEqual(y, 42)
+        self.assertEqual(z, 84)
+        self.assertEqual(q, 14)
+        self.assertTrue(cb_info['called'])
+        self.assertEqual(cb_info['userdata'], (-5, 'User Data'))
+
+    def test_async_ready_callback(self):
+        TestCallbacks.called = False
+        TestCallbacks.main_loop = GLib.MainLoop()
+
+        def callback(obj, result, user_data):
+            TestCallbacks.main_loop.quit()
+            TestCallbacks.called = True
+
+        Everything.test_async_ready_callback(callback)
+
+        TestCallbacks.main_loop.run()
+
+        self.assertTrue(TestCallbacks.called)
+
+    def test_callback_scope_notified_with_destroy(self):
+        TestCallbacks.called = 0
+        ud = 'Test scope notified data 33'
+
+        def callback(user_data):
+            self.assertEqual(user_data, ud)
+            TestCallbacks.called += 1
+            return 33
+
+        if hasattr(sys, "getrefcount"):
+            value_refcount = sys.getrefcount(ud)
+            callback_refcount = sys.getrefcount(callback)
+
+        # Callback is immediately called.
+        for i in range(100):
+            res = Everything.test_callback_destroy_notify(callback, ud)
+            self.assertEqual(res, 33)
+
+        self.assertEqual(TestCallbacks.called, 100)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(callback), callback_refcount + 100)
+            self.assertEqual(sys.getrefcount(ud), value_refcount + 100)
+
+        # thaw will call the callback again, this time resources should be freed
+        self.assertEqual(Everything.test_callback_thaw_notifications(), 33 * 100)
+        self.assertEqual(TestCallbacks.called, 200)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(callback), callback_refcount)
+            self.assertEqual(sys.getrefcount(ud), value_refcount)
+
+    def test_callback_scope_notified_with_destroy_no_user_data(self):
+        TestCallbacks.called = 0
+
+        def callback(user_data):
+            self.assertEqual(user_data, None)
+            TestCallbacks.called += 1
+            return 34
+
+        if hasattr(sys, "getrefcount"):
+            callback_refcount = sys.getrefcount(callback)
+
+        # Run with warning as exception
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("error")
+            self.assertRaises(RuntimeWarning,
+                              Everything.test_callback_destroy_notify_no_user_data,
+                              callback)
+
+        self.assertEqual(TestCallbacks.called, 0)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(callback), callback_refcount)
+
+        # Run with warning as warning
+        with warnings.catch_warnings(record=True) as w:
+            # Cause all warnings to always be triggered.
+            warnings.simplefilter("default")
+            # Trigger a warning.
+            res = Everything.test_callback_destroy_notify_no_user_data(callback)
+            # Verify some things
+            self.assertEqual(len(w), 1)
+            self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
+            self.assertTrue('Callables passed to' in str(w[-1].message))
+
+        self.assertEqual(res, 34)
+        self.assertEqual(TestCallbacks.called, 1)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(callback), callback_refcount + 1)
+
+        # thaw will call the callback again,
+        # refcount will not go down without user_data parameter
+        self.assertEqual(Everything.test_callback_thaw_notifications(), 34)
+        self.assertEqual(TestCallbacks.called, 2)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(callback), callback_refcount + 1)
+
+    def test_callback_in_methods(self):
+        object_ = Everything.TestObj()
+
+        def callback():
+            TestCallbacks.called = True
+            return 42
+
+        TestCallbacks.called = False
+        object_.instance_method_callback(callback)
+        self.assertTrue(TestCallbacks.called)
+
+        TestCallbacks.called = False
+        Everything.TestObj.static_method_callback(callback)
+        self.assertTrue(TestCallbacks.called)
+
+        def callbackWithUserData(user_data):
+            TestCallbacks.called += 1
+            return 42
+
+        TestCallbacks.called = 0
+        Everything.TestObj.new_callback(callbackWithUserData, None)
+        self.assertEqual(TestCallbacks.called, 1)
+        # Note: using "new_callback" adds the notification to the same global
+        # list as Everything.test_callback_destroy_notify, so thaw the list
+        # so we don't get confusion between tests.
+        self.assertEqual(Everything.test_callback_thaw_notifications(), 42)
+        self.assertEqual(TestCallbacks.called, 2)
+
+    def test_callback_none(self):
+        # make sure this doesn't assert or crash
+        Everything.test_simple_callback(None)
+
+    def test_callback_gerror(self):
+        def callback(error):
+            self.assertEqual(error.message, 'regression test error')
+            self.assertTrue('g-io' in error.domain)
+            self.assertEqual(error.code, Gio.IOErrorEnum.NOT_SUPPORTED)
+            TestCallbacks.called = True
+
+        TestCallbacks.called = False
+        Everything.test_gerror_callback(callback)
+        self.assertTrue(TestCallbacks.called)
+
+    def test_callback_null_gerror(self):
+        def callback(error):
+            self.assertEqual(error, None)
+            TestCallbacks.called = True
+
+        TestCallbacks.called = False
+        Everything.test_null_gerror_callback(callback)
+        self.assertTrue(TestCallbacks.called)
+
+    def test_callback_owned_gerror(self):
+        def callback(error):
+            self.assertEqual(error.message, 'regression test owned error')
+            self.assertTrue('g-io' in error.domain)
+            self.assertEqual(error.code, Gio.IOErrorEnum.PERMISSION_DENIED)
+            TestCallbacks.called = True
+
+        TestCallbacks.called = False
+        Everything.test_owned_gerror_callback(callback)
+        self.assertTrue(TestCallbacks.called)
+
+    def test_callback_hashtable(self):
+        def callback(data):
+            self.assertEqual(data, mydict)
+            mydict['new'] = 42
+            TestCallbacks.called = True
+
+        mydict = {'foo': 1, 'bar': 2}
+        TestCallbacks.called = False
+        Everything.test_hash_table_callback(mydict, callback)
+        self.assertTrue(TestCallbacks.called)
+        self.assertEqual(mydict, {'foo': 1, 'bar': 2, 'new': 42})
+
+
+class TestClosures(unittest.TestCase):
+    def test_no_arg(self):
+        def callback():
+            self.called = True
+            return 42
+
+        self.called = False
+        result = Everything.test_closure(callback)
+        self.assertTrue(self.called)
+        self.assertEqual(result, 42)
+
+    def test_int_arg(self):
+        def callback(num):
+            self.called = True
+            return num + 1
+
+        self.called = False
+        result = Everything.test_closure_one_arg(callback, 42)
+        self.assertTrue(self.called)
+        self.assertEqual(result, 43)
+
+    def test_variant(self):
+        def callback(variant):
+            self.called = True
+            if variant is None:
+                return None
+            self.assertEqual(variant.get_type_string(), 'i')
+            return GLib.Variant('i', variant.get_int32() + 1)
+
+        self.called = False
+        result = Everything.test_closure_variant(callback, GLib.Variant('i', 42))
+        self.assertTrue(self.called)
+        self.assertEqual(result.get_type_string(), 'i')
+        self.assertEqual(result.get_int32(), 43)
+
+        self.called = False
+        result = Everything.test_closure_variant(callback, None)
+        self.assertTrue(self.called)
+        self.assertEqual(result, None)
+
+        self.called = False
+        self.assertRaises(TypeError, Everything.test_closure_variant, callback, 'foo')
+        self.assertFalse(self.called)
+
+    def test_variant_wrong_return_type(self):
+        def callback(variant):
+            return 'no_variant'
+
+        with capture_exceptions() as exc:
+            # this does not directly raise an exception (see
+            # https://bugzilla.gnome.org/show_bug.cgi?id=616279)
+            result = Everything.test_closure_variant(callback, GLib.Variant('i', 42))
+        # ... but the result shouldn't be a string
+        self.assertEqual(result, None)
+        # and the error should be shown
+        self.assertEqual(len(exc), 1)
+        self.assertEqual(exc[0].type, TypeError)
+        self.assertTrue('return value' in str(exc[0].value), exc[0].value)
+
+
+class TestBoxed(unittest.TestCase):
+    def test_boxed(self):
+        object_ = Everything.TestObj()
+        self.assertEqual(object_.props.boxed, None)
+
+        boxed = Everything.TestBoxed()
+        boxed.some_int8 = 42
+        object_.props.boxed = boxed
+
+        self.assertTrue(isinstance(object_.props.boxed, Everything.TestBoxed))
+        self.assertEqual(object_.props.boxed.some_int8, 42)
+
+    def test_boxed_alternative_constructor(self):
+        boxed = Everything.TestBoxed.new_alternative_constructor1(5)
+        self.assertEqual(boxed.some_int8, 5)
+
+        boxed = Everything.TestBoxed.new_alternative_constructor2(5, 3)
+        self.assertEqual(boxed.some_int8, 8)
+
+        boxed = Everything.TestBoxed.new_alternative_constructor3("-3")
+        self.assertEqual(boxed.some_int8, -3)
+
+    def test_boxed_equality(self):
+        boxed42 = Everything.TestBoxed.new_alternative_constructor1(42)
+        boxed5 = Everything.TestBoxed.new_alternative_constructor1(5)
+        boxed42_2 = Everything.TestBoxed.new_alternative_constructor2(41, 1)
+
+        self.assertFalse(boxed42.equals(boxed5))
+        self.assertTrue(boxed42.equals(boxed42_2))
+        self.assertTrue(boxed42_2.equals(boxed42))
+        self.assertTrue(boxed42.equals(boxed42))
+
+    def test_boxed_b_constructor(self):
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            boxed = Everything.TestBoxedB(42, 47)
+            self.assertTrue(issubclass(warn[0].category, DeprecationWarning))
+
+        self.assertEqual(boxed.some_int8, 0)
+        self.assertEqual(boxed.some_long, 0)
+
+    def test_boxed_c_equality(self):
+        boxed = Everything.TestBoxedC()
+        # TestBoxedC uses refcounting, so we know that
+        # the pointer is the same when copied
+        copy = boxed.copy()
+        self.assertEqual(boxed, copy)
+        self.assertNotEqual(id(boxed), id(copy))
+
+    def test_boxed_c_wrapper(self):
+        wrapper = Everything.TestBoxedCWrapper()
+        obj = wrapper.get()
+
+        # TestBoxedC uses refcounting, so we know that
+        # it should be 2 at this point:
+        # - one owned by @wrapper
+        # - another owned by @obj
+        self.assertEqual(obj.refcount, 2)
+        del wrapper
+        gc.collect()
+        gc.collect()
+        self.assertEqual(obj.refcount, 1)
+
+    def test_boxed_c_wrapper_copy(self):
+        wrapper = Everything.TestBoxedCWrapper()
+        wrapper_copy = wrapper.copy()
+        obj = wrapper.get()
+
+        # TestBoxedC uses refcounting, so we know that
+        # it should be 3 at this point:
+        # - one owned by @wrapper
+        # - one owned by @wrapper_copy
+        # - another owned by @obj
+        self.assertEqual(obj.refcount, 3)
+        del wrapper
+        gc.collect()
+        gc.collect()
+        self.assertEqual(obj.refcount, 2)
+        del wrapper_copy
+        gc.collect()
+        gc.collect()
+        self.assertEqual(obj.refcount, 1)
+        del obj
+        gc.collect()
+        gc.collect()
+
+    def test_array_fixed_boxed_none_out(self):
+        arr = Everything.test_array_fixed_boxed_none_out()
+        assert len(arr) == 2
+        assert arr[0].refcount == 2
+        assert arr[1].refcount == 2
+
+    def test_glist_boxed_none_return(self):
+        assert len(Everything.test_glist_boxed_none_return(0)) == 0
+
+        list_ = Everything.test_glist_boxed_none_return(2)
+        assert len(list_) == 2
+        assert list_[0].refcount == 2
+        assert list_[1].refcount == 2
+
+    def test_glist_boxed_full_return(self):
+        assert len(Everything.test_glist_boxed_full_return(0)) == 0
+
+        list_ = Everything.test_glist_boxed_full_return(2)
+        assert len(list_) == 2
+        assert list_[0].refcount == 1
+        assert list_[1].refcount == 1
+
+
+class TestTortureProfile(unittest.TestCase):
+    def test_torture_profile(self):
+        total_time = 0
+        print("")
+        object_ = Everything.TestObj()
+        sys.stdout.write("\ttorture test 1 (10000 iterations): ")
+
+        start_time = timeit.default_timer()
+        for i in range(10000):
+            (y, z, q) = object_.torture_signature_0(5000,
+                                                    "Torture Test 1",
+                                                    12345)
+
+        end_time = timeit.default_timer()
+        delta_time = end_time - start_time
+        total_time += delta_time
+        print("%f secs" % delta_time)
+
+        sys.stdout.write("\ttorture test 2 (10000 iterations): ")
+
+        start_time = timeit.default_timer()
+        for i in range(10000):
+            (y, z, q) = Everything.TestObj().torture_signature_0(
+                5000, "Torture Test 2", 12345)
+
+        end_time = timeit.default_timer()
+        delta_time = end_time - start_time
+        total_time += delta_time
+        print("%f secs" % delta_time)
+
+        sys.stdout.write("\ttorture test 3 (10000 iterations): ")
+        start_time = timeit.default_timer()
+        for i in range(10000):
+            try:
+                (y, z, q) = object_.torture_signature_1(
+                    5000, "Torture Test 3", 12345)
+            except:
+                pass
+        end_time = timeit.default_timer()
+        delta_time = end_time - start_time
+        total_time += delta_time
+        print("%f secs" % delta_time)
+
+        sys.stdout.write("\ttorture test 4 (10000 iterations): ")
+
+        def callback(userdata):
+            return 0
+
+        userdata = [1, 2, 3, 4]
+        start_time = timeit.default_timer()
+        for i in range(10000):
+            (y, z, q) = Everything.test_torture_signature_2(
+                5000, callback, userdata, "Torture Test 4", 12345)
+        end_time = timeit.default_timer()
+        delta_time = end_time - start_time
+        total_time += delta_time
+        print("%f secs" % delta_time)
+        print("\t====")
+        print("\tTotal: %f sec" % total_time)
+
+
+class TestAdvancedInterfaces(unittest.TestCase):
+    def test_array_objs(self):
+        obj1, obj2 = Everything.test_array_fixed_out_objects()
+        self.assertTrue(isinstance(obj1, Everything.TestObj))
+        self.assertTrue(isinstance(obj2, Everything.TestObj))
+        self.assertNotEqual(obj1, obj2)
+
+    def test_obj_skip_return_val(self):
+        obj = Everything.TestObj()
+        ret = obj.skip_return_val(50, 42.0, 60, 2, 3)
+        self.assertEqual(len(ret), 3)
+        self.assertEqual(ret[0], 51)
+        self.assertEqual(ret[1], 61)
+        self.assertEqual(ret[2], 32)
+
+    def test_obj_skip_return_val_no_out(self):
+        obj = Everything.TestObj()
+        # raises an error for 0, succeeds for any other value
+        self.assertRaises(GLib.GError, obj.skip_return_val_no_out, 0)
+
+        ret = obj.skip_return_val_no_out(1)
+        self.assertEqual(ret, None)
diff --git a/tests/test_fields.py b/tests/test_fields.py
new file mode 100644 (file)
index 0000000..0181d28
--- /dev/null
@@ -0,0 +1,188 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# coding=utf-8
+
+from __future__ import absolute_import
+
+import math
+import unittest
+
+from gi.repository import GLib
+from gi.repository import Regress
+from gi.repository import GIMarshallingTests
+
+
+class Number(object):
+
+    def __init__(self, value):
+        self.value = value
+
+    def __int__(self):
+        return int(self.value)
+
+    def __float__(self):
+        return float(self.value)
+
+
+class TestFields(unittest.TestCase):
+
+    def test_int8(self):
+        s = Regress.TestStructA()
+        s.some_int8 = 21
+        self.assertEqual(s.some_int8, 21)
+
+        s.some_int8 = b"\x42"
+        self.assertEqual(s.some_int8, 0x42)
+
+        self.assertRaises(TypeError, setattr, s, "some_int8", b"ab")
+        self.assertRaises(TypeError, setattr, s, "some_int8", None)
+        self.assertRaises(OverflowError, setattr, s, "some_int8", 128)
+        self.assertRaises(OverflowError, setattr, s, "some_int8", -129)
+
+        s.some_int8 = 3.6
+        self.assertEqual(s.some_int8, 3)
+
+        s.some_int8 = Number(55)
+        self.assertEqual(s.some_int8, 55)
+
+    def test_int(self):
+        s = Regress.TestStructA()
+        s.some_int = GLib.MAXINT
+        self.assertEqual(s.some_int, GLib.MAXINT)
+
+        self.assertRaises(TypeError, setattr, s, "some_int", b"a")
+        self.assertRaises(TypeError, setattr, s, "some_int", None)
+        self.assertRaises(
+            OverflowError, setattr, s, "some_int", GLib.MAXINT + 1)
+        self.assertRaises(
+            OverflowError, setattr, s, "some_int", GLib.MININT - 1)
+
+        s.some_int = 3.6
+        self.assertEqual(s.some_int, 3)
+
+        s.some_int = Number(GLib.MININT)
+        self.assertEqual(s.some_int, GLib.MININT)
+
+    def test_long(self):
+        s = GIMarshallingTests.SimpleStruct()
+        s.long_ = GLib.MAXLONG
+        self.assertEqual(s.long_, GLib.MAXLONG)
+
+        self.assertRaises(TypeError, setattr, s, "long_", b"a")
+        self.assertRaises(TypeError, setattr, s, "long_", None)
+        self.assertRaises(OverflowError, setattr, s, "long_", GLib.MAXLONG + 1)
+        self.assertRaises(OverflowError, setattr, s, "long_", GLib.MINLONG - 1)
+
+        s.long_ = 3.6
+        self.assertEqual(s.long_, 3)
+
+        s.long_ = Number(GLib.MINLONG)
+        self.assertEqual(s.long_, GLib.MINLONG)
+
+    def test_double(self):
+        s = Regress.TestStructA()
+        s.some_double = GLib.MAXDOUBLE
+        self.assertEqual(s.some_double, GLib.MAXDOUBLE)
+        s.some_double = GLib.MINDOUBLE
+        self.assertEqual(s.some_double, GLib.MINDOUBLE)
+
+        s.some_double = float("nan")
+        self.assertTrue(math.isnan(s.some_double))
+
+        self.assertRaises(TypeError, setattr, s, "some_double", b"a")
+        self.assertRaises(TypeError, setattr, s, "some_double", None)
+
+    def test_gtype(self):
+        s = Regress.TestStructE()
+
+        s.some_type = Regress.TestObj
+        self.assertEqual(s.some_type, Regress.TestObj.__gtype__)
+
+        self.assertRaises(TypeError, setattr, s, "some_type", 42)
+
+    def test_unichar(self):
+        # I can't find a unichar field..
+        pass
+
+    def test_utf8(self):
+        s = GIMarshallingTests.BoxedStruct()
+        s.string_ = "hello"
+        self.assertEqual(s.string_, "hello")
+
+        s.string_ = u"hello"
+        self.assertEqual(s.string_, u"hello")
+
+        s.string_ = None
+        self.assertEqual(s.string_, None)
+
+        self.assertRaises(TypeError, setattr, s, "string_", 42)
+
+    def test_array_of_structs(self):
+        s = Regress.TestStructD()
+        self.assertEqual(s.array1, [])
+        self.assertEqual(s.array2, [])
+
+    def test_interface(self):
+        s = Regress.TestStructC()
+
+        obj = Regress.TestObj()
+        s.obj = obj
+        self.assertTrue(s.obj is obj)
+
+        s.obj = None
+        self.assertTrue(s.obj is None)
+
+        self.assertRaises(TypeError, setattr, s, "obj", object())
+
+    def test_glist(self):
+        s = Regress.TestStructD()
+        self.assertEqual(s.list, [])
+
+        self.assertRaises(TypeError, setattr, s, "list", [object()])
+
+    def test_gpointer(self):
+        glist = GLib.List()
+
+        glist.data = 123
+        self.assertEqual(glist.data, 123)
+
+        glist.data = None
+        self.assertEqual(glist.data, 0)
+
+    def test_gptrarray(self):
+        s = Regress.TestStructD()
+        self.assertEqual(s.garray, [])
+
+        self.assertRaises(TypeError, setattr, s, "garray", [object()])
+
+    def test_enum(self):
+        s = Regress.TestStructA()
+
+        s.some_enum = Regress.TestEnum.VALUE3
+        self.assertEqual(s.some_enum, Regress.TestEnum.VALUE3)
+
+        self.assertRaises(TypeError, setattr, s, "some_enum", object())
+
+        s.some_enum = 0
+        self.assertEqual(s.some_enum, Regress.TestEnum.VALUE1)
+
+    def test_union(self):
+        s = Regress.TestStructE()
+        self.assertEqual(s.some_union, [None, None])
+
+    def test_struct(self):
+        s = GIMarshallingTests.NestedStruct()
+
+        # FIXME: segfaults
+        # https://bugzilla.gnome.org/show_bug.cgi?id=747002
+        # s.simple_struct = None
+
+        self.assertRaises(TypeError, setattr, s, "simple_struct", object())
+
+        sub = GIMarshallingTests.SimpleStruct()
+        sub.long_ = 42
+        s.simple_struct = sub
+        self.assertEqual(s.simple_struct.long_, 42)
+
+    def test_ghashtable(self):
+        obj = Regress.TestObj()
+        self.assertTrue(obj.hash_table is None)
diff --git a/tests/test_gdbus.py b/tests/test_gdbus.py
new file mode 100644 (file)
index 0000000..18315af
--- /dev/null
@@ -0,0 +1,250 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import unittest
+
+from gi.repository import GLib
+from gi.repository import Gio
+
+
+try:
+    Gio.bus_get_sync(Gio.BusType.SESSION, None)
+except GLib.Error:
+    has_dbus = False
+else:
+    has_dbus = True
+
+
+class TestDBusNodeInfo(unittest.TestCase):
+
+    def test_new_for_xml(self):
+        info = Gio.DBusNodeInfo.new_for_xml("""
+<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
+    'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
+<node>
+    <interface name='org.freedesktop.DBus.Introspectable'>
+        <method name='Introspect'>
+            <arg name='data' direction='out' type='s'/>
+        </method>
+    </interface>
+</node>
+""")
+
+        interfaces = info.interfaces
+        del info
+        assert len(interfaces) == 1
+        assert interfaces[0].name == "org.freedesktop.DBus.Introspectable"
+        methods = interfaces[0].methods
+        del interfaces
+        assert len(methods) == 1
+        assert methods[0].name == "Introspect"
+        out_args = methods[0].out_args
+        assert len(out_args)
+        del methods
+        assert out_args[0].name == "data"
+
+
+@unittest.skipUnless(has_dbus, "no dbus running")
+class TestGDBusClient(unittest.TestCase):
+    def setUp(self):
+        self.bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
+
+        self.dbus_proxy = Gio.DBusProxy.new_sync(self.bus,
+                                                 Gio.DBusProxyFlags.NONE,
+                                                 None,
+                                                 'org.freedesktop.DBus',
+                                                 '/org/freedesktop/DBus',
+                                                 'org.freedesktop.DBus',
+                                                 None)
+
+    def test_native_calls_sync(self):
+        result = self.dbus_proxy.call_sync('ListNames', None,
+                                           Gio.DBusCallFlags.NO_AUTO_START,
+                                           500, None)
+        self.assertTrue(isinstance(result, GLib.Variant))
+        result = result.unpack()[0]  # result is always a tuple
+        self.assertTrue(len(result) > 1)
+        self.assertTrue('org.freedesktop.DBus' in result)
+
+        result = self.dbus_proxy.call_sync('GetNameOwner',
+                                           GLib.Variant('(s)', ('org.freedesktop.DBus',)),
+                                           Gio.DBusCallFlags.NO_AUTO_START, 500, None)
+        self.assertTrue(isinstance(result, GLib.Variant))
+        self.assertEqual(type(result.unpack()[0]), type(''))
+
+    def test_native_calls_sync_errors(self):
+        # error case: invalid argument types
+        try:
+            self.dbus_proxy.call_sync('GetConnectionUnixProcessID', None,
+                                      Gio.DBusCallFlags.NO_AUTO_START, 500, None)
+            self.fail('call with invalid arguments should raise an exception')
+        except Exception as e:
+            self.assertTrue('InvalidArgs' in str(e))
+
+        # error case: invalid argument
+        try:
+            self.dbus_proxy.call_sync('GetConnectionUnixProcessID',
+                                      GLib.Variant('(s)', (' unknown',)),
+                                      Gio.DBusCallFlags.NO_AUTO_START, 500, None)
+            self.fail('call with invalid arguments should raise an exception')
+        except Exception as e:
+            self.assertTrue('NameHasNoOwner' in str(e))
+
+        # error case: unknown method
+        try:
+            self.dbus_proxy.call_sync('UnknownMethod', None,
+                                      Gio.DBusCallFlags.NO_AUTO_START, 500, None)
+            self.fail('call for unknown method should raise an exception')
+        except Exception as e:
+            self.assertTrue('UnknownMethod' in str(e))
+
+    def test_native_calls_async(self):
+        def call_done(obj, result, user_data):
+            try:
+                user_data['result'] = obj.call_finish(result)
+            finally:
+                user_data['main_loop'].quit()
+
+        main_loop = GLib.MainLoop()
+        data = {'main_loop': main_loop}
+        self.dbus_proxy.call('ListNames', None,
+                             Gio.DBusCallFlags.NO_AUTO_START, 500, None,
+                             call_done, data)
+        main_loop.run()
+
+        self.assertTrue(isinstance(data['result'], GLib.Variant))
+        result = data['result'].unpack()[0]  # result is always a tuple
+        self.assertTrue(len(result) > 1)
+        self.assertTrue('org.freedesktop.DBus' in result)
+
+    def test_native_calls_async_errors(self):
+        def call_done(obj, result, user_data):
+            try:
+                obj.call_finish(result)
+                self.fail('call_finish() for unknown method should raise an exception')
+            except Exception as e:
+                self.assertTrue('UnknownMethod' in str(e))
+            finally:
+                user_data['main_loop'].quit()
+
+        main_loop = GLib.MainLoop()
+        data = {'main_loop': main_loop}
+        self.dbus_proxy.call('UnknownMethod', None,
+                             Gio.DBusCallFlags.NO_AUTO_START, 500, None,
+                             call_done, data)
+        main_loop.run()
+
+    def test_python_calls_sync(self):
+        # single value return tuples get unboxed to the one element
+        result = self.dbus_proxy.ListNames('()')
+        self.assertTrue(isinstance(result, list))
+        self.assertTrue(len(result) > 1)
+        self.assertTrue('org.freedesktop.DBus' in result)
+
+        result = self.dbus_proxy.GetNameOwner('(s)', 'org.freedesktop.DBus')
+        self.assertEqual(type(result), type(''))
+
+        # empty return tuples get unboxed to None
+        self.assertEqual(self.dbus_proxy.ReloadConfig('()'), None)
+
+        # multiple return values remain a tuple; unfortunately D-BUS itself
+        # does not have any method returning multiple results, so try talking
+        # to notification-daemon (and don't fail the test if it does not exist)
+        try:
+            nd = Gio.DBusProxy.new_sync(self.bus,
+                                        Gio.DBusProxyFlags.NONE, None,
+                                        'org.freedesktop.Notifications',
+                                        '/org/freedesktop/Notifications',
+                                        'org.freedesktop.Notifications',
+                                        None)
+            result = nd.GetServerInformation('()')
+            self.assertTrue(isinstance(result, tuple))
+            self.assertEqual(len(result), 4)
+            for i in result:
+                self.assertEqual(type(i), type(''))
+        except Exception as e:
+            if 'Error.ServiceUnknown' not in str(e):
+                raise
+
+        # test keyword argument; timeout=0 will fail immediately
+        try:
+            self.dbus_proxy.GetConnectionUnixProcessID('(s)', '1', timeout=0)
+            self.fail('call with timeout=0 should raise an exception')
+        except Exception as e:
+            # FIXME: this is not very precise, but in some environments we
+            # do not always get an actual timeout
+            self.assertTrue(isinstance(e, GLib.GError), str(e))
+
+    def test_python_calls_sync_noargs(self):
+        # methods without arguments don't need an explicit signature
+        result = self.dbus_proxy.ListNames()
+        self.assertTrue(isinstance(result, list))
+        self.assertTrue(len(result) > 1)
+        self.assertTrue('org.freedesktop.DBus' in result)
+
+    def test_python_calls_sync_errors(self):
+        # error case: invalid argument types
+        try:
+            self.dbus_proxy.GetConnectionUnixProcessID('()')
+            self.fail('call with invalid arguments should raise an exception')
+        except Exception as e:
+            self.assertTrue('InvalidArgs' in str(e), str(e))
+
+        try:
+            self.dbus_proxy.GetConnectionUnixProcessID(None, 'foo')
+            self.fail('call with None signature should raise an exception')
+        except TypeError as e:
+            self.assertTrue('signature' in str(e), str(e))
+
+    def test_python_calls_async(self):
+        def call_done(obj, result, user_data):
+            user_data['result'] = result
+            user_data['main_loop'].quit()
+
+        main_loop = GLib.MainLoop()
+        data = {'main_loop': main_loop}
+        self.dbus_proxy.ListNames('()', result_handler=call_done, user_data=data)
+        main_loop.run()
+
+        result = data['result']
+        self.assertEqual(type(result), type([]))
+        self.assertTrue(len(result) > 1)
+        self.assertTrue('org.freedesktop.DBus' in result)
+
+    def test_python_calls_async_error_result(self):
+        # when only specifying a result handler, this will get the error
+        def call_done(obj, result, user_data):
+            user_data['result'] = result
+            user_data['main_loop'].quit()
+
+        main_loop = GLib.MainLoop()
+        data = {'main_loop': main_loop}
+        self.dbus_proxy.ListNames('(s)', 'invalid_argument',
+                                  result_handler=call_done, user_data=data)
+        main_loop.run()
+
+        self.assertTrue(isinstance(data['result'], Exception))
+        self.assertTrue('InvalidArgs' in str(data['result']), str(data['result']))
+
+    def test_python_calls_async_error(self):
+        # when specifying an explicit error handler, this will get the error
+        def call_done(obj, result, user_data):
+            user_data['main_loop'].quit()
+            self.fail('result handler should not be called')
+
+        def call_error(obj, error, user_data):
+            user_data['error'] = error
+            user_data['main_loop'].quit()
+
+        main_loop = GLib.MainLoop()
+        data = {'main_loop': main_loop}
+        self.dbus_proxy.ListNames('(s)', 'invalid_argument',
+                                  result_handler=call_done,
+                                  error_handler=call_error,
+                                  user_data=data)
+        main_loop.run()
+
+        self.assertTrue(isinstance(data['error'], Exception))
+        self.assertTrue('InvalidArgs' in str(data['error']), str(data['error']))
diff --git a/tests/test_generictreemodel.py b/tests/test_generictreemodel.py
new file mode 100644 (file)
index 0000000..5e9d716
--- /dev/null
@@ -0,0 +1,434 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# test_generictreemodel - Tests for GenericTreeModel
+# Copyright (C) 2013 Simon Feltman
+#
+#   test_generictreemodel.py: Tests for GenericTreeModel
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import
+
+# system
+import gc
+import sys
+import weakref
+import unittest
+import platform
+
+# pygobject
+from gi.repository import GObject
+
+try:
+    from gi.repository import Gtk
+    from pygtkcompat.generictreemodel import GenericTreeModel
+    from pygtkcompat.generictreemodel import _get_user_data_as_pyobject
+    has_gtk = True
+except ImportError:
+    GenericTreeModel = object
+    has_gtk = False
+
+
+class Node(object):
+    """Represents a generic node with name, value, and children."""
+    def __init__(self, name, value, *children):
+        self.name = name
+        self.value = value
+        self.children = list(children)
+        self.parent = None
+        self.next = None
+
+        for i, child in enumerate(children):
+            child.parent = weakref.ref(self)
+            if i < len(children) - 1:
+                child.next = weakref.ref(children[i + 1])
+
+    def __repr__(self):
+        return 'Node("%s", %s)' % (self.name, self.value)
+
+
+class ATesterModel(GenericTreeModel):
+    def __init__(self):
+        super(ATesterModel, self).__init__()
+        self.root = Node('root', 0,
+                         Node('spam', 1,
+                              Node('sushi', 2),
+                              Node('bread', 3)
+                         ),
+                         Node('eggs', 4)
+                        )
+
+    def on_get_flags(self):
+        return 0
+
+    def on_get_n_columns(self):
+        return 2
+
+    def on_get_column_type(self, n):
+        return (str, int)[n]
+
+    def on_get_iter(self, path):
+        node = self.root
+        path = list(path)
+        idx = path.pop(0)
+        while path:
+            idx = path.pop(0)
+            node = node.children[idx]
+        return node
+
+    def on_get_path(self, node):
+        def rec_get_path(n):
+            for i, child in enumerate(n.children):
+                if child == node:
+                    return [i]
+                else:
+                    res = rec_get_path(child)
+                    if res:
+                        res.insert(0, i)
+
+        return rec_get_path(self.root)
+
+    def on_get_value(self, node, column):
+        if column == 0:
+            return node.name
+        elif column == 1:
+            return node.value
+
+    def on_iter_has_child(self, node):
+        return bool(node.children)
+
+    def on_iter_next(self, node):
+        if node.next:
+            return node.next()
+
+    def on_iter_children(self, node):
+        if node:
+            return node.children[0]
+        else:
+            return self.root
+
+    def on_iter_n_children(self, node):
+        if node is None:
+            return 1
+        return len(node.children)
+
+    def on_iter_nth_child(self, node, n):
+        if node is None:
+            assert n == 0
+            return self.root
+        return node.children[n]
+
+    def on_iter_parent(self, child):
+        if child.parent:
+            return child.parent()
+
+
+@unittest.skipUnless(has_gtk, 'Gtk not available')
+class TestReferences(unittest.TestCase):
+    def setUp(self):
+        pass
+
+    @unittest.skipIf(platform.python_implementation() == "PyPy", "not with PyPy")
+    def test_c_tree_iter_user_data_as_pyobject(self):
+        obj = object()
+        obj_id = id(obj)
+        ref_count = sys.getrefcount(obj)
+
+        # This is essentially a stolen ref in the context of _CTreeIter.get_user_data_as_pyobject
+        it = Gtk.TreeIter()
+        it.user_data = obj_id
+
+        obj2 = _get_user_data_as_pyobject(it)
+        self.assertEqual(obj, obj2)
+        self.assertEqual(sys.getrefcount(obj), ref_count + 1)
+
+    def test_leak_references_on(self):
+        model = ATesterModel()
+        obj_ref = weakref.ref(model.root)
+        # Initial refcount is 1 for model.root + the temporary
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(model.root), 2)
+
+        # Iter increases by 1 do to assignment to iter.user_data
+        res, it = model.do_get_iter([0])
+        self.assertEqual(id(model.root), it.user_data)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(model.root), 3)
+
+        # Verify getting a TreeIter more then once does not further increase
+        # the ref count.
+        res2, it2 = model.do_get_iter([0])
+        self.assertEqual(id(model.root), it2.user_data)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(model.root), 3)
+
+        # Deleting the iter does not decrease refcount because references
+        # leak by default (they are stored in the held_refs pool)
+        del it
+        gc.collect()
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(model.root), 3)
+
+        # Deleting a model should free all held references to user data
+        # stored by TreeIters
+        del model
+        gc.collect()
+        self.assertEqual(obj_ref(), None)
+
+    def test_row_deleted_frees_refs(self):
+        model = ATesterModel()
+        obj_ref = weakref.ref(model.root)
+        # Initial refcount is 1 for model.root + the temporary
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(model.root), 2)
+
+        # Iter increases by 1 do to assignment to iter.user_data
+        res, it = model.do_get_iter([0])
+        self.assertEqual(id(model.root), it.user_data)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(model.root), 3)
+
+        # Notifying the underlying model of a row_deleted should decrease the
+        # ref count.
+        model.row_deleted(Gtk.TreePath('0'), model.root)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(model.root), 2)
+
+        # Finally deleting the actual object should collect it completely
+        del model.root
+        gc.collect()
+        self.assertEqual(obj_ref(), None)
+
+    def test_leak_references_off(self):
+        model = ATesterModel()
+        model.leak_references = False
+
+        obj_ref = weakref.ref(model.root)
+        # Initial refcount is 1 for model.root + the temporary
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(model.root), 2)
+
+        # Iter does not increas count by 1 when leak_references is false
+        res, it = model.do_get_iter([0])
+        self.assertEqual(id(model.root), it.user_data)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(model.root), 2)
+
+        # Deleting the iter does not decrease refcount because assigning user_data
+        # eats references and does not release them.
+        del it
+        gc.collect()
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(model.root), 2)
+
+        # Deleting the model decreases the final ref, and the object is collected
+        del model
+        gc.collect()
+        self.assertEqual(obj_ref(), None)
+
+    def test_iteration_refs(self):
+        # Pull iterators off the model using the wrapped C API which will
+        # then call back into the python overrides.
+        model = ATesterModel()
+        nodes = [node for node in model.iter_depth_first()]
+        values = [node.value for node in nodes]
+
+        # Verify depth first ordering
+        self.assertEqual(values, [0, 1, 2, 3, 4])
+
+        # Verify ref counts for each of the nodes.
+        # 5 refs for each node at this point:
+        #   1 - ref held in getrefcount function
+        #   2 - ref held by "node" var during iteration
+        #   3 - ref held by local "nodes" var
+        #   4 - ref held by the root/children graph itself
+        #   5 - ref held by the model "held_refs" instance var
+        for node in nodes:
+            if hasattr(sys, "getrefcount"):
+                self.assertEqual(sys.getrefcount(node), 5)
+
+        # A second iteration and storage of the nodes in a new list
+        # should only increase refcounts by 1 even though new
+        # iterators are created and assigned.
+        nodes2 = [node for node in model.iter_depth_first()]
+        for node in nodes2:
+            if hasattr(sys, "getrefcount"):
+                self.assertEqual(sys.getrefcount(node), 6)
+
+        # Hold weak refs and start verifying ref collection.
+        node_refs = [weakref.ref(node) for node in nodes]
+
+        # First round of collection
+        del nodes2
+        gc.collect()
+        for node in nodes:
+            if hasattr(sys, "getrefcount"):
+                self.assertEqual(sys.getrefcount(node), 5)
+
+        # Second round of collection, no more local lists of nodes.
+        del nodes
+        gc.collect()
+        for ref in node_refs:
+            node = ref()
+            if hasattr(sys, "getrefcount"):
+                self.assertEqual(sys.getrefcount(node), 4)
+
+        # Using invalidate_iters or row_deleted(path, node) will clear out
+        # the pooled refs held internal to the GenericTreeModel implementation.
+        model.invalidate_iters()
+        self.assertEqual(len(model._held_refs), 0)
+        gc.collect()
+        for ref in node_refs:
+            node = ref()
+            if hasattr(sys, "getrefcount"):
+                self.assertEqual(sys.getrefcount(node), 3)
+
+        # Deleting the root node at this point should allow all nodes to be collected
+        # as there is no longer a way to reach the children
+        del node  # node still in locals() from last iteration
+        del model.root
+        gc.collect()
+        for ref in node_refs:
+            self.assertEqual(ref(), None)
+
+
+@unittest.skipUnless(has_gtk, 'Gtk not available')
+class TestIteration(unittest.TestCase):
+    def test_iter_next_root(self):
+        model = ATesterModel()
+        it = model.get_iter([0])
+        self.assertEqual(it.user_data, id(model.root))
+        self.assertEqual(model.root.next, None)
+
+        it = model.iter_next(it)
+        self.assertEqual(it, None)
+
+    def test_iter_next_multiple(self):
+        model = ATesterModel()
+        it = model.get_iter([0, 0])
+        self.assertEqual(it.user_data, id(model.root.children[0]))
+
+        it = model.iter_next(it)
+        self.assertEqual(it.user_data, id(model.root.children[1]))
+
+        it = model.iter_next(it)
+        self.assertEqual(it, None)
+
+
+class ErrorModel(GenericTreeModel):
+    # All on_* methods will raise a NotImplementedError by default
+    pass
+
+
+@unittest.skipUnless(has_gtk, 'Gtk not available')
+class ExceptHook(object):
+    """
+    Temporarily installs an exception hook in a context which
+    expects the given exc_type to be raised. This allows verification
+    of exceptions that occur within python gi callbacks but
+    are never bubbled through from python to C back to python.
+    This works because exception hooks are called in PyErr_Print.
+    """
+    def __init__(self, *expected_exc_types):
+        self._expected_exc_types = expected_exc_types
+        self._exceptions = []
+
+    def _excepthook(self, exc_type, value, traceback):
+        self._exceptions.append((exc_type, value))
+
+    def __enter__(self):
+        self._oldhook = sys.excepthook
+        sys.excepthook = self._excepthook
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        sys.excepthook = self._oldhook
+        error_message = 'Expecting the following exceptions: %s, got: %s' % \
+            (str(self._expected_exc_types), '\n'.join([str(item) for item in self._exceptions]))
+
+        assert len(self._expected_exc_types) == len(self._exceptions), error_message
+
+        for expected, got in zip(self._expected_exc_types, [exc[0] for exc in self._exceptions]):
+            assert issubclass(got, expected), error_message
+
+
+@unittest.skipUnless(has_gtk, 'Gtk not available')
+class TestReturnsAfterError(unittest.TestCase):
+    def setUp(self):
+        self.model = ErrorModel()
+
+    def test_get_flags(self):
+        with ExceptHook(NotImplementedError):
+            flags = self.model.get_flags()
+        self.assertEqual(flags, 0)
+
+    def test_get_n_columns(self):
+        with ExceptHook(NotImplementedError):
+            count = self.model.get_n_columns()
+        self.assertEqual(count, 0)
+
+    def test_get_column_type(self):
+        with ExceptHook(NotImplementedError, TypeError):
+            col_type = self.model.get_column_type(0)
+        self.assertEqual(col_type, GObject.TYPE_INVALID)
+
+    def test_get_iter(self):
+        with ExceptHook(NotImplementedError):
+            self.assertRaises(ValueError, self.model.get_iter, Gtk.TreePath(0))
+
+    def test_get_path(self):
+        it = self.model.create_tree_iter('foo')
+        with ExceptHook(NotImplementedError):
+            path = self.model.get_path(it)
+        self.assertEqual(path, None)
+
+    def test_get_value(self):
+        it = self.model.create_tree_iter('foo')
+        with ExceptHook(NotImplementedError):
+            try:
+                self.model.get_value(it, 0)
+            except TypeError:
+                pass  # silence TypeError converting None to GValue
+
+    def test_iter_has_child(self):
+        it = self.model.create_tree_iter('foo')
+        with ExceptHook(NotImplementedError):
+            res = self.model.iter_has_child(it)
+        self.assertEqual(res, False)
+
+    def test_iter_next(self):
+        it = self.model.create_tree_iter('foo')
+        with ExceptHook(NotImplementedError):
+            res = self.model.iter_next(it)
+        self.assertEqual(res, None)
+
+    def test_iter_children(self):
+        with ExceptHook(NotImplementedError):
+            res = self.model.iter_children(None)
+        self.assertEqual(res, None)
+
+    def test_iter_n_children(self):
+        with ExceptHook(NotImplementedError):
+            res = self.model.iter_n_children(None)
+        self.assertEqual(res, 0)
+
+    def test_iter_nth_child(self):
+        with ExceptHook(NotImplementedError):
+            res = self.model.iter_nth_child(None, 0)
+        self.assertEqual(res, None)
+
+    def test_iter_parent(self):
+        child = self.model.create_tree_iter('foo')
+        with ExceptHook(NotImplementedError):
+            res = self.model.iter_parent(child)
+        self.assertEqual(res, None)
diff --git a/tests/test_gi.py b/tests/test_gi.py
new file mode 100644 (file)
index 0000000..2b6f3c0
--- /dev/null
@@ -0,0 +1,3356 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# coding=utf-8
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import sys
+
+import unittest
+import tempfile
+import shutil
+import os
+import gc
+import weakref
+import warnings
+import pickle
+import platform
+
+import gi
+import gi.overrides
+from gi import PyGIWarning
+from gi import PyGIDeprecationWarning
+from gi.repository import GObject, GLib, Gio
+from gi.repository import GIMarshallingTests
+from gi._compat import PY2, PY3
+import pytest
+
+from .helper import capture_exceptions, capture_output
+
+
+CONSTANT_UTF8 = "const â™¥ utf8"
+CONSTANT_UCS4 = u"const â™¥ utf8"
+
+
+class Number(object):
+
+    def __init__(self, value):
+        self.value = value
+
+    def __int__(self):
+        return int(self.value)
+
+    def __float__(self):
+        return float(self.value)
+
+
+class Sequence(object):
+
+    def __init__(self, sequence):
+        self.sequence = sequence
+
+    def __len__(self):
+        return len(self.sequence)
+
+    def __getitem__(self, key):
+        return self.sequence[key]
+
+
+class TestConstant(unittest.TestCase):
+
+    def test_constant_utf8(self):
+        self.assertEqual(CONSTANT_UTF8, GIMarshallingTests.CONSTANT_UTF8)
+
+    def test_constant_number(self):
+        self.assertEqual(42, GIMarshallingTests.CONSTANT_NUMBER)
+
+    def test_min_max_int(self):
+        self.assertEqual(GLib.MAXINT32, 2 ** 31 - 1)
+        self.assertEqual(GLib.MININT32, -2 ** 31)
+        self.assertEqual(GLib.MAXUINT32, 2 ** 32 - 1)
+
+        self.assertEqual(GLib.MAXINT64, 2 ** 63 - 1)
+        self.assertEqual(GLib.MININT64, -2 ** 63)
+        self.assertEqual(GLib.MAXUINT64, 2 ** 64 - 1)
+
+
+class TestBoolean(unittest.TestCase):
+
+    def test_boolean_return(self):
+        self.assertEqual(True, GIMarshallingTests.boolean_return_true())
+        self.assertEqual(False, GIMarshallingTests.boolean_return_false())
+
+    def test_boolean_in(self):
+        GIMarshallingTests.boolean_in_true(True)
+        GIMarshallingTests.boolean_in_false(False)
+
+        GIMarshallingTests.boolean_in_true(1)
+        GIMarshallingTests.boolean_in_false(0)
+
+    def test_boolean_out(self):
+        self.assertEqual(True, GIMarshallingTests.boolean_out_true())
+        self.assertEqual(False, GIMarshallingTests.boolean_out_false())
+
+    def test_boolean_inout(self):
+        self.assertEqual(False, GIMarshallingTests.boolean_inout_true_false(True))
+        self.assertEqual(True, GIMarshallingTests.boolean_inout_false_true(False))
+
+
+class TestInt8(unittest.TestCase):
+
+    MAX = GLib.MAXINT8
+    MIN = GLib.MININT8
+
+    def test_int8_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.int8_return_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.int8_return_min())
+
+    def test_int8_in(self):
+        max = Number(self.MAX)
+        min = Number(self.MIN)
+
+        GIMarshallingTests.int8_in_max(max)
+        GIMarshallingTests.int8_in_min(min)
+
+        max.value += 1
+        min.value -= 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.int8_in_max, max)
+        self.assertRaises(OverflowError, GIMarshallingTests.int8_in_min, min)
+
+        self.assertRaises(TypeError, GIMarshallingTests.int8_in_max, "self.MAX")
+
+    def test_int8_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.int8_out_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.int8_out_min())
+
+    def test_int8_inout(self):
+        self.assertEqual(self.MIN, GIMarshallingTests.int8_inout_max_min(Number(self.MAX)))
+        self.assertEqual(self.MAX, GIMarshallingTests.int8_inout_min_max(Number(self.MIN)))
+
+
+class TestUInt8(unittest.TestCase):
+
+    MAX = GLib.MAXUINT8
+
+    def test_uint8_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.uint8_return())
+
+    def test_uint8_in(self):
+        number = Number(self.MAX)
+
+        GIMarshallingTests.uint8_in(number)
+        GIMarshallingTests.uint8_in(b'\xff')
+
+        number.value += 1
+        self.assertRaises(OverflowError, GIMarshallingTests.uint8_in, number)
+        self.assertRaises(OverflowError, GIMarshallingTests.uint8_in, Number(-1))
+
+        self.assertRaises(TypeError, GIMarshallingTests.uint8_in, "self.MAX")
+
+    def test_uint8_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.uint8_out())
+
+    def test_uint8_inout(self):
+        self.assertEqual(0, GIMarshallingTests.uint8_inout(Number(self.MAX)))
+
+
+class TestInt16(unittest.TestCase):
+
+    MAX = GLib.MAXINT16
+    MIN = GLib.MININT16
+
+    def test_int16_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.int16_return_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.int16_return_min())
+
+    def test_int16_in(self):
+        max = Number(self.MAX)
+        min = Number(self.MIN)
+
+        GIMarshallingTests.int16_in_max(max)
+        GIMarshallingTests.int16_in_min(min)
+
+        max.value += 1
+        min.value -= 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.int16_in_max, max)
+        self.assertRaises(OverflowError, GIMarshallingTests.int16_in_min, min)
+
+        self.assertRaises(TypeError, GIMarshallingTests.int16_in_max, "self.MAX")
+
+    def test_int16_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.int16_out_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.int16_out_min())
+
+    def test_int16_inout(self):
+        self.assertEqual(self.MIN, GIMarshallingTests.int16_inout_max_min(Number(self.MAX)))
+        self.assertEqual(self.MAX, GIMarshallingTests.int16_inout_min_max(Number(self.MIN)))
+
+
+class TestUInt16(unittest.TestCase):
+
+    MAX = GLib.MAXUINT16
+
+    def test_uint16_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.uint16_return())
+
+    def test_uint16_in(self):
+        number = Number(self.MAX)
+
+        GIMarshallingTests.uint16_in(number)
+
+        number.value += 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.uint16_in, number)
+        self.assertRaises(OverflowError, GIMarshallingTests.uint16_in, Number(-1))
+
+        self.assertRaises(TypeError, GIMarshallingTests.uint16_in, "self.MAX")
+
+    def test_uint16_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.uint16_out())
+
+    def test_uint16_inout(self):
+        self.assertEqual(0, GIMarshallingTests.uint16_inout(Number(self.MAX)))
+
+
+class TestInt32(unittest.TestCase):
+
+    MAX = GLib.MAXINT32
+    MIN = GLib.MININT32
+
+    def test_int32_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.int32_return_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.int32_return_min())
+
+    def test_int32_in(self):
+        max = Number(self.MAX)
+        min = Number(self.MIN)
+
+        GIMarshallingTests.int32_in_max(max)
+        GIMarshallingTests.int32_in_min(min)
+
+        max.value += 1
+        min.value -= 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.int32_in_max, max)
+        self.assertRaises(OverflowError, GIMarshallingTests.int32_in_min, min)
+
+        self.assertRaises(TypeError, GIMarshallingTests.int32_in_max, "self.MAX")
+
+    def test_int32_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.int32_out_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.int32_out_min())
+
+    def test_int32_inout(self):
+        self.assertEqual(self.MIN, GIMarshallingTests.int32_inout_max_min(Number(self.MAX)))
+        self.assertEqual(self.MAX, GIMarshallingTests.int32_inout_min_max(Number(self.MIN)))
+
+
+class TestUInt32(unittest.TestCase):
+
+    MAX = GLib.MAXUINT32
+
+    def test_uint32_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.uint32_return())
+
+    def test_uint32_in(self):
+        number = Number(self.MAX)
+
+        GIMarshallingTests.uint32_in(number)
+
+        number.value += 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.uint32_in, number)
+        self.assertRaises(OverflowError, GIMarshallingTests.uint32_in, Number(-1))
+
+        self.assertRaises(TypeError, GIMarshallingTests.uint32_in, "self.MAX")
+
+    def test_uint32_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.uint32_out())
+
+    def test_uint32_inout(self):
+        self.assertEqual(0, GIMarshallingTests.uint32_inout(Number(self.MAX)))
+
+
+class TestInt64(unittest.TestCase):
+
+    MAX = 2 ** 63 - 1
+    MIN = - (2 ** 63)
+
+    def test_int64_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.int64_return_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.int64_return_min())
+
+    def test_int64_in(self):
+        max = Number(self.MAX)
+        min = Number(self.MIN)
+
+        GIMarshallingTests.int64_in_max(max)
+        GIMarshallingTests.int64_in_min(min)
+
+        max.value += 1
+        min.value -= 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.int64_in_max, max)
+        self.assertRaises(OverflowError, GIMarshallingTests.int64_in_min, min)
+
+        self.assertRaises(TypeError, GIMarshallingTests.int64_in_max, "self.MAX")
+
+    def test_int64_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.int64_out_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.int64_out_min())
+
+    def test_int64_inout(self):
+        self.assertEqual(self.MIN, GIMarshallingTests.int64_inout_max_min(Number(self.MAX)))
+        self.assertEqual(self.MAX, GIMarshallingTests.int64_inout_min_max(Number(self.MIN)))
+
+
+class TestUInt64(unittest.TestCase):
+
+    MAX = 2 ** 64 - 1
+
+    def test_uint64_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.uint64_return())
+
+    def test_uint64_in(self):
+        number = Number(self.MAX)
+
+        GIMarshallingTests.uint64_in(number)
+
+        number.value += 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.uint64_in, number)
+        self.assertRaises(OverflowError, GIMarshallingTests.uint64_in, Number(-1))
+
+        self.assertRaises(TypeError, GIMarshallingTests.uint64_in, "self.MAX")
+
+    def test_uint64_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.uint64_out())
+
+    def test_uint64_inout(self):
+        self.assertEqual(0, GIMarshallingTests.uint64_inout(Number(self.MAX)))
+
+
+class TestShort(unittest.TestCase):
+
+    MAX = GLib.MAXSHORT
+    MIN = GLib.MINSHORT
+
+    def test_short_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.short_return_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.short_return_min())
+
+    def test_short_in(self):
+        max = Number(self.MAX)
+        min = Number(self.MIN)
+
+        GIMarshallingTests.short_in_max(max)
+        GIMarshallingTests.short_in_min(min)
+
+        max.value += 1
+        min.value -= 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.short_in_max, max)
+        self.assertRaises(OverflowError, GIMarshallingTests.short_in_min, min)
+
+        self.assertRaises(TypeError, GIMarshallingTests.short_in_max, "self.MAX")
+
+    def test_short_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.short_out_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.short_out_min())
+
+    def test_short_inout(self):
+        self.assertEqual(self.MIN, GIMarshallingTests.short_inout_max_min(Number(self.MAX)))
+        self.assertEqual(self.MAX, GIMarshallingTests.short_inout_min_max(Number(self.MIN)))
+
+
+class TestUShort(unittest.TestCase):
+
+    MAX = GLib.MAXUSHORT
+
+    def test_ushort_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.ushort_return())
+
+    def test_ushort_in(self):
+        number = Number(self.MAX)
+
+        GIMarshallingTests.ushort_in(number)
+
+        number.value += 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.ushort_in, number)
+        self.assertRaises(OverflowError, GIMarshallingTests.ushort_in, Number(-1))
+
+        self.assertRaises(TypeError, GIMarshallingTests.ushort_in, "self.MAX")
+
+    def test_ushort_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.ushort_out())
+
+    def test_ushort_inout(self):
+        self.assertEqual(0, GIMarshallingTests.ushort_inout(Number(self.MAX)))
+
+
+class TestInt(unittest.TestCase):
+
+    MAX = GLib.MAXINT
+    MIN = GLib.MININT
+
+    def test_int_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.int_return_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.int_return_min())
+
+    def test_int_in(self):
+        max = Number(self.MAX)
+        min = Number(self.MIN)
+
+        GIMarshallingTests.int_in_max(max)
+        GIMarshallingTests.int_in_min(min)
+
+        max.value += 1
+        min.value -= 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.int_in_max, max)
+        self.assertRaises(OverflowError, GIMarshallingTests.int_in_min, min)
+
+        self.assertRaises(TypeError, GIMarshallingTests.int_in_max, "self.MAX")
+
+    def test_int_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.int_out_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.int_out_min())
+
+    def test_int_inout(self):
+        self.assertEqual(self.MIN, GIMarshallingTests.int_inout_max_min(Number(self.MAX)))
+        self.assertEqual(self.MAX, GIMarshallingTests.int_inout_min_max(Number(self.MIN)))
+        self.assertRaises(TypeError, GIMarshallingTests.int_inout_min_max, Number(self.MIN), 42)
+
+
+class TestUInt(unittest.TestCase):
+
+    MAX = GLib.MAXUINT
+
+    def test_uint_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.uint_return())
+
+    def test_uint_in(self):
+        number = Number(self.MAX)
+
+        GIMarshallingTests.uint_in(number)
+
+        number.value += 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.uint_in, number)
+        self.assertRaises(OverflowError, GIMarshallingTests.uint_in, Number(-1))
+
+        self.assertRaises(TypeError, GIMarshallingTests.uint_in, "self.MAX")
+
+    def test_uint_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.uint_out())
+
+    def test_uint_inout(self):
+        self.assertEqual(0, GIMarshallingTests.uint_inout(Number(self.MAX)))
+
+
+class TestLong(unittest.TestCase):
+
+    MAX = GLib.MAXLONG
+    MIN = GLib.MINLONG
+
+    def test_long_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.long_return_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.long_return_min())
+
+    def test_long_in(self):
+        max = Number(self.MAX)
+        min = Number(self.MIN)
+
+        GIMarshallingTests.long_in_max(max)
+        GIMarshallingTests.long_in_min(min)
+
+        max.value += 1
+        min.value -= 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.long_in_max, max)
+        self.assertRaises(OverflowError, GIMarshallingTests.long_in_min, min)
+
+        self.assertRaises(TypeError, GIMarshallingTests.long_in_max, "self.MAX")
+
+    def test_long_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.long_out_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.long_out_min())
+
+    def test_long_inout(self):
+        self.assertEqual(self.MIN, GIMarshallingTests.long_inout_max_min(Number(self.MAX)))
+        self.assertEqual(self.MAX, GIMarshallingTests.long_inout_min_max(Number(self.MIN)))
+
+
+class TestULong(unittest.TestCase):
+
+    MAX = GLib.MAXULONG
+
+    def test_ulong_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.ulong_return())
+
+    def test_ulong_in(self):
+        number = Number(self.MAX)
+
+        GIMarshallingTests.ulong_in(number)
+
+        number.value += 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.ulong_in, number)
+        self.assertRaises(OverflowError, GIMarshallingTests.ulong_in, Number(-1))
+
+        self.assertRaises(TypeError, GIMarshallingTests.ulong_in, "self.MAX")
+
+    def test_ulong_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.ulong_out())
+
+    def test_ulong_inout(self):
+        self.assertEqual(0, GIMarshallingTests.ulong_inout(Number(self.MAX)))
+
+
+class TestSSize(unittest.TestCase):
+
+    MAX = GLib.MAXSSIZE
+    MIN = GLib.MINSSIZE
+
+    def test_ssize_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.ssize_return_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.ssize_return_min())
+
+    def test_ssize_in(self):
+        max = Number(self.MAX)
+        min = Number(self.MIN)
+
+        GIMarshallingTests.ssize_in_max(max)
+        GIMarshallingTests.ssize_in_min(min)
+
+        max.value += 1
+        min.value -= 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.ssize_in_max, max)
+        self.assertRaises(OverflowError, GIMarshallingTests.ssize_in_min, min)
+
+        self.assertRaises(TypeError, GIMarshallingTests.ssize_in_max, "self.MAX")
+
+    def test_ssize_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.ssize_out_max())
+        self.assertEqual(self.MIN, GIMarshallingTests.ssize_out_min())
+
+    def test_ssize_inout(self):
+        self.assertEqual(self.MIN, GIMarshallingTests.ssize_inout_max_min(Number(self.MAX)))
+        self.assertEqual(self.MAX, GIMarshallingTests.ssize_inout_min_max(Number(self.MIN)))
+
+
+class TestSize(unittest.TestCase):
+
+    MAX = GLib.MAXSIZE
+
+    def test_size_return(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.size_return())
+
+    def test_size_in(self):
+        number = Number(self.MAX)
+
+        GIMarshallingTests.size_in(number)
+
+        number.value += 1
+
+        self.assertRaises(OverflowError, GIMarshallingTests.size_in, number)
+        self.assertRaises(OverflowError, GIMarshallingTests.size_in, Number(-1))
+
+        self.assertRaises(TypeError, GIMarshallingTests.size_in, "self.MAX")
+
+    def test_size_out(self):
+        self.assertEqual(self.MAX, GIMarshallingTests.size_out())
+
+    def test_size_inout(self):
+        self.assertEqual(0, GIMarshallingTests.size_inout(Number(self.MAX)))
+
+
+class TestTimet(unittest.TestCase):
+
+    def test_time_t_return(self):
+        self.assertEqual(1234567890, GIMarshallingTests.time_t_return())
+
+    def test_time_t_in(self):
+        GIMarshallingTests.time_t_in(1234567890)
+        self.assertRaises(TypeError, GIMarshallingTests.time_t_in, "hello")
+
+    def test_time_t_out(self):
+        self.assertEqual(1234567890, GIMarshallingTests.time_t_out())
+
+    def test_time_t_inout(self):
+        self.assertEqual(0, GIMarshallingTests.time_t_inout(1234567890))
+
+
+class TestFloat(unittest.TestCase):
+
+    MAX = GLib.MAXFLOAT
+    MIN = GLib.MINFLOAT
+
+    def test_float_return(self):
+        self.assertAlmostEqual(self.MAX, GIMarshallingTests.float_return())
+
+    def test_float_in(self):
+        GIMarshallingTests.float_in(Number(self.MAX))
+
+        self.assertRaises(TypeError, GIMarshallingTests.float_in, "self.MAX")
+
+    def test_float_out(self):
+        self.assertAlmostEqual(self.MAX, GIMarshallingTests.float_out())
+
+    def test_float_inout(self):
+        self.assertAlmostEqual(self.MIN, GIMarshallingTests.float_inout(Number(self.MAX)))
+
+
+class TestDouble(unittest.TestCase):
+
+    MAX = GLib.MAXDOUBLE
+    MIN = GLib.MINDOUBLE
+
+    def test_double_return(self):
+        self.assertAlmostEqual(self.MAX, GIMarshallingTests.double_return())
+
+    def test_double_in(self):
+        GIMarshallingTests.double_in(Number(self.MAX))
+
+        self.assertRaises(TypeError, GIMarshallingTests.double_in, "self.MAX")
+
+    def test_double_out(self):
+        self.assertAlmostEqual(self.MAX, GIMarshallingTests.double_out())
+
+    def test_double_inout(self):
+        self.assertAlmostEqual(self.MIN, GIMarshallingTests.double_inout(Number(self.MAX)))
+
+
+class TestGType(unittest.TestCase):
+
+    def test_gtype_name(self):
+        self.assertEqual("void", GObject.TYPE_NONE.name)
+        self.assertEqual("gchararray", GObject.TYPE_STRING.name)
+
+        def check_readonly(gtype):
+            gtype.name = "foo"
+
+        errors = (AttributeError,)
+        if platform.python_implementation() == "PyPy":
+            # https://bitbucket.org/pypy/pypy/issues/2788
+            errors = (AttributeError, TypeError)
+
+        self.assertRaises(errors, check_readonly, GObject.TYPE_NONE)
+        self.assertRaises(errors, check_readonly, GObject.TYPE_STRING)
+
+    def test_gtype_return(self):
+        self.assertEqual(GObject.TYPE_NONE, GIMarshallingTests.gtype_return())
+        self.assertEqual(GObject.TYPE_STRING, GIMarshallingTests.gtype_string_return())
+
+    def test_gtype_in(self):
+        GIMarshallingTests.gtype_in(GObject.TYPE_NONE)
+        GIMarshallingTests.gtype_string_in(GObject.TYPE_STRING)
+        self.assertRaises(TypeError, GIMarshallingTests.gtype_in, "foo")
+        self.assertRaises(TypeError, GIMarshallingTests.gtype_string_in, "foo")
+
+    def test_gtype_out(self):
+        self.assertEqual(GObject.TYPE_NONE, GIMarshallingTests.gtype_out())
+        self.assertEqual(GObject.TYPE_STRING, GIMarshallingTests.gtype_string_out())
+
+    def test_gtype_inout(self):
+        self.assertEqual(GObject.TYPE_INT, GIMarshallingTests.gtype_inout(GObject.TYPE_NONE))
+
+
+class TestUtf8(unittest.TestCase):
+
+    def test_utf8_as_uint8array_in(self):
+        data = CONSTANT_UTF8
+        if not isinstance(data, bytes):
+            data = data.encode("utf-8")
+        GIMarshallingTests.utf8_as_uint8array_in(data)
+
+    def test_utf8_none_return(self):
+        self.assertEqual(CONSTANT_UTF8, GIMarshallingTests.utf8_none_return())
+
+    def test_utf8_full_return(self):
+        self.assertEqual(CONSTANT_UTF8, GIMarshallingTests.utf8_full_return())
+
+    def test_extra_utf8_full_return_invalid(self):
+        with pytest.raises(UnicodeDecodeError):
+            value = GIMarshallingTests.extra_utf8_full_return_invalid()
+            if PY2:
+                value.decode("utf-8")
+
+    def test_extra_utf8_full_out_invalid(self):
+        with pytest.raises(UnicodeDecodeError):
+            value = GIMarshallingTests.extra_utf8_full_out_invalid()
+            if PY2:
+                value.decode("utf-8")
+
+    def test_utf8_none_in(self):
+        GIMarshallingTests.utf8_none_in(CONSTANT_UTF8)
+        if PY2:
+            GIMarshallingTests.utf8_none_in(CONSTANT_UTF8.decode("utf-8"))
+
+        self.assertRaises(TypeError, GIMarshallingTests.utf8_none_in, 42)
+        self.assertRaises(TypeError, GIMarshallingTests.utf8_none_in, None)
+
+    def test_utf8_none_out(self):
+        self.assertEqual(CONSTANT_UTF8, GIMarshallingTests.utf8_none_out())
+
+    def test_utf8_full_out(self):
+        self.assertEqual(CONSTANT_UTF8, GIMarshallingTests.utf8_full_out())
+
+    def test_utf8_dangling_out(self):
+        GIMarshallingTests.utf8_dangling_out()
+
+    def test_utf8_none_inout(self):
+        self.assertEqual("", GIMarshallingTests.utf8_none_inout(CONSTANT_UTF8))
+
+    def test_utf8_full_inout(self):
+        self.assertEqual("", GIMarshallingTests.utf8_full_inout(CONSTANT_UTF8))
+
+
+class TestFilename(unittest.TestCase):
+    def setUp(self):
+        self.workdir = tempfile.mkdtemp()
+
+    def tearDown(self):
+        shutil.rmtree(self.workdir)
+
+    def tests_filename_list_return(self):
+        assert GIMarshallingTests.filename_list_return() == []
+
+    @unittest.skipIf(os.name == "nt", "fixme")
+    def test_filename_in(self):
+        fname = os.path.join(self.workdir, u'testäø.txt')
+
+        try:
+            os.path.exists(fname)
+        except ValueError:
+            # non-unicode fs encoding
+            return
+
+        self.assertRaises(GLib.GError, GLib.file_get_contents, fname)
+
+        with open(fname.encode('UTF-8'), 'wb') as f:
+            f.write(b'hello world!\n\x01\x02')
+
+        (result, contents) = GLib.file_get_contents(fname)
+        self.assertEqual(result, True)
+        self.assertEqual(contents, b'hello world!\n\x01\x02')
+
+    def test_filename_in_nullable(self):
+        self.assertTrue(GIMarshallingTests.filename_copy(None) is None)
+        self.assertRaises(TypeError, GIMarshallingTests.filename_exists, None)
+
+    @unittest.skipIf(os.name == "nt", "fixme")
+    def test_filename_out(self):
+        self.assertRaises(GLib.GError, GLib.Dir.make_tmp, 'test')
+        name = 'testäø.XXXXXX'
+
+        try:
+            os.path.exists(name)
+        except ValueError:
+            # non-unicode fs encoding
+            return
+
+        dirname = GLib.Dir.make_tmp(name)
+        self.assertTrue(os.path.sep + 'testäø.' in dirname, dirname)
+        self.assertTrue(os.path.isdir(dirname))
+        os.rmdir(dirname)
+
+    def test_wrong_types(self):
+        self.assertRaises(TypeError, GIMarshallingTests.filename_copy, 23)
+        self.assertRaises(TypeError, GIMarshallingTests.filename_copy, [])
+
+    def test_null(self):
+        self.assertTrue(GIMarshallingTests.filename_copy(None) is None)
+        self.assertRaises(TypeError, GIMarshallingTests.filename_exists, None)
+
+    def test_round_trip(self):
+        self.assertEqual(GIMarshallingTests.filename_copy(u"foo"), "foo")
+        self.assertEqual(GIMarshallingTests.filename_copy(b"foo"), "foo")
+
+    def test_contains_null(self):
+        self.assertRaises(
+            (ValueError, TypeError),
+            GIMarshallingTests.filename_copy, b"foo\x00")
+        self.assertRaises(
+            (ValueError, TypeError),
+            GIMarshallingTests.filename_copy, u"foo\x00")
+
+    def test_as_is_py2(self):
+        if not PY2:
+            return
+
+        values = [
+            b"foo",
+            b"\xff\xff",
+            b"\xc3\xb6\xc3\xa4\xc3\xbc",
+            b"\xed\xa0\xbd",
+            b"\xf0\x90\x80\x81",
+        ]
+
+        for v in values:
+            self.assertEqual(GIMarshallingTests.filename_copy(v), v)
+            self.assertEqual(GIMarshallingTests.filename_to_glib_repr(v), v)
+
+    def test_win32_surrogates(self):
+        if os.name != "nt":
+            return
+
+        copy = GIMarshallingTests.filename_copy
+        glib_repr = GIMarshallingTests.filename_to_glib_repr
+
+        if PY3:
+            self.assertEqual(copy(u"\ud83d"), u"\ud83d")
+            self.assertEqual(copy(u"\x61\uDC00"), u"\x61\uDC00")
+            self.assertEqual(copy(u"\uD800\uDC01"), u"\U00010001")
+            self.assertEqual(copy(u"\uD83D\x20\uDCA9"), u"\uD83D\x20\uDCA9")
+        else:
+            self.assertEqual(copy(u"\ud83d"), u"\ud83d".encode("utf-8"))
+            self.assertEqual(copy(u"\uD800\uDC01").decode("utf-8"),
+                             u"\U00010001")
+
+        self.assertEqual(glib_repr(u"\ud83d"), b"\xed\xa0\xbd")
+        self.assertEqual(glib_repr(u"\uD800\uDC01"), b"\xf0\x90\x80\x81")
+
+        self.assertEqual(
+            glib_repr(u"\uD800\uDBFF"), b"\xED\xA0\x80\xED\xAF\xBF")
+        self.assertEqual(
+            glib_repr(u"\uD800\uE000"), b"\xED\xA0\x80\xEE\x80\x80")
+        self.assertEqual(
+            glib_repr(u"\uD7FF\uDC00"), b"\xED\x9F\xBF\xED\xB0\x80")
+        self.assertEqual(glib_repr(u"\x61\uDC00"), b"\x61\xED\xB0\x80")
+        self.assertEqual(glib_repr(u"\uDC00"), b"\xED\xB0\x80")
+
+    def test_win32_bytes_py3(self):
+        if not (os.name == "nt" and PY3):
+            return
+
+        values = [
+            b"foo",
+            b"\xff\xff",
+            b"\xc3\xb6\xc3\xa4\xc3\xbc",
+            b"\xed\xa0\xbd",
+            b"\xf0\x90\x80\x81",
+        ]
+
+        for v in values:
+            try:
+                uni = v.decode(sys.getfilesystemencoding(), "surrogatepass")
+            except UnicodeDecodeError:
+                continue
+            self.assertEqual(GIMarshallingTests.filename_copy(v), uni)
+
+    def test_unix_various(self):
+        if os.name == "nt":
+            return
+
+        copy = GIMarshallingTests.filename_copy
+        glib_repr = GIMarshallingTests.filename_to_glib_repr
+
+        if PY3:
+            try:
+                os.fsdecode(b"\xff\xfe")
+            except UnicodeDecodeError:
+                self.assertRaises(UnicodeDecodeError, copy, b"\xff\xfe")
+            else:
+                str_path = copy(b"\xff\xfe")
+                self.assertTrue(isinstance(str_path, str))
+                self.assertEqual(str_path, os.fsdecode(b"\xff\xfe"))
+                self.assertEqual(copy(str_path), str_path)
+                self.assertEqual(glib_repr(b"\xff\xfe"), b"\xff\xfe")
+                self.assertEqual(glib_repr(str_path), b"\xff\xfe")
+
+            # if getfilesystemencoding is ASCII, then we should fail like
+            # os.fsencode
+            try:
+                byte_path = os.fsencode(u"ä")
+            except UnicodeEncodeError:
+                self.assertRaises(UnicodeEncodeError, copy, u"ä")
+            else:
+                self.assertEqual(copy(u"ä"), u"ä")
+                self.assertEqual(glib_repr(u"ä"), byte_path)
+        else:
+            self.assertTrue(isinstance(copy(b"\xff\xfe"), bytes))
+            self.assertEqual(copy(u"foo"), b"foo")
+            self.assertTrue(isinstance(copy(u"foo"), bytes))
+            try:
+                byte_path = u"ä".encode(sys.getfilesystemencoding())
+            except UnicodeEncodeError:
+                self.assertRaises(UnicodeEncodeError, copy, u"ä")
+            else:
+                self.assertEqual(copy(u"ä"), byte_path)
+                self.assertEqual(glib_repr(u"ä"), byte_path)
+
+    @unittest.skip("glib can't handle non-unicode paths")
+    def test_win32_surrogates_exists(self):
+        if os.name != "nt":
+            return
+
+        path = os.path.join(self.workdir, u"\ud83d")
+        with open(path, "wb"):
+            self.assertTrue(os.path.exists(path))
+            self.assertTrue(GIMarshallingTests.filename_exists(path))
+        os.unlink(path)
+
+    def test_path_exists_various_types(self):
+        wd = self.workdir
+        wdb = os.fsencode(wd) if PY3 else wd
+
+        paths = [(wdb, b"foo-1"), (wd, u"foo-2"), (wd, u"öäü-3")]
+        if PY3:
+            try:
+                paths.append((wd, os.fsdecode(b"\xff\xfe-4")))
+            except UnicodeDecodeError:
+                # depends on the code page
+                pass
+
+        if os.name != "nt":
+            paths.append((wdb, b"\xff\xfe-5"))
+
+        def valid_path(p):
+            try:
+                os.path.exists(p)
+            except ValueError:
+                return False
+            return True
+
+        for (d, path) in paths:
+            if not valid_path(path):
+                continue
+            path = os.path.join(d, path)
+            with open(path, "wb"):
+                self.assertTrue(GIMarshallingTests.filename_exists(path))
+
+
+class TestArray(unittest.TestCase):
+
+    @unittest.skipUnless(
+        hasattr(GIMarshallingTests, "array_bool_in"), "too old gi")
+    def test_array_bool_in(self):
+        GIMarshallingTests.array_bool_in([True, False, True, True])
+
+    @unittest.skipUnless(
+        hasattr(GIMarshallingTests, "array_bool_out"), "too old gi")
+    def test_array_bool_out(self):
+        assert GIMarshallingTests.array_bool_out() == [True, False, True, True]
+
+    @unittest.skipUnless(
+        hasattr(GIMarshallingTests, "array_int64_in"), "too old gi")
+    def test_array_int64_in(self):
+        GIMarshallingTests.array_int64_in([-1, 0, 1, 2])
+
+    @unittest.skipUnless(
+        hasattr(GIMarshallingTests, "array_uint64_in"), "too old gi")
+    def test_array_uint64_in(self):
+        GIMarshallingTests.array_uint64_in([GLib.MAXUINT64, 0, 1, 2])
+
+    @unittest.skipUnless(
+        hasattr(GIMarshallingTests, "array_unichar_in"), "too old gi")
+    def test_array_unichar_in(self):
+        GIMarshallingTests.array_unichar_in(list(CONSTANT_UCS4))
+        GIMarshallingTests.array_unichar_in(CONSTANT_UCS4)
+
+    @unittest.skipUnless(
+        hasattr(GIMarshallingTests, "array_unichar_out"), "too old gi")
+    def test_array_unichar_out(self):
+        if PY2:
+            result = [c.encode("utf-8") for c in list(CONSTANT_UCS4)]
+        else:
+            result = list(CONSTANT_UCS4)
+        assert GIMarshallingTests.array_unichar_out() == result
+
+    @unittest.skip("broken")
+    def test_array_zero_terminated_return_unichar(self):
+        assert GIMarshallingTests.array_zero_terminated_return_unichar() == \
+            list(CONSTANT_UCS4)
+
+    def test_array_fixed_int_return(self):
+        self.assertEqual([-1, 0, 1, 2], GIMarshallingTests.array_fixed_int_return())
+
+    def test_array_fixed_short_return(self):
+        self.assertEqual([-1, 0, 1, 2], GIMarshallingTests.array_fixed_short_return())
+
+    def test_array_fixed_int_in(self):
+        GIMarshallingTests.array_fixed_int_in(Sequence([-1, 0, 1, 2]))
+
+        self.assertRaises(TypeError, GIMarshallingTests.array_fixed_int_in, Sequence([-1, '0', 1, 2]))
+
+        self.assertRaises(TypeError, GIMarshallingTests.array_fixed_int_in, 42)
+        self.assertRaises(TypeError, GIMarshallingTests.array_fixed_int_in, None)
+
+    def test_array_fixed_short_in(self):
+        GIMarshallingTests.array_fixed_short_in(Sequence([-1, 0, 1, 2]))
+
+    def test_array_fixed_out(self):
+        self.assertEqual([-1, 0, 1, 2], GIMarshallingTests.array_fixed_out())
+
+    def test_array_fixed_inout(self):
+        self.assertEqual([2, 1, 0, -1], GIMarshallingTests.array_fixed_inout([-1, 0, 1, 2]))
+
+    def test_array_return(self):
+        self.assertEqual([-1, 0, 1, 2], GIMarshallingTests.array_return())
+
+    def test_array_return_etc(self):
+        self.assertEqual(([5, 0, 1, 9], 14), GIMarshallingTests.array_return_etc(5, 9))
+
+    def test_array_in(self):
+        GIMarshallingTests.array_in(Sequence([-1, 0, 1, 2]))
+        GIMarshallingTests.array_in_guint64_len(Sequence([-1, 0, 1, 2]))
+        GIMarshallingTests.array_in_guint8_len(Sequence([-1, 0, 1, 2]))
+
+    def test_array_in_len_before(self):
+        GIMarshallingTests.array_in_len_before(Sequence([-1, 0, 1, 2]))
+
+    def test_array_in_len_zero_terminated(self):
+        GIMarshallingTests.array_in_len_zero_terminated(Sequence([-1, 0, 1, 2]))
+
+    def test_array_uint8_in(self):
+        GIMarshallingTests.array_uint8_in(Sequence([97, 98, 99, 100]))
+        GIMarshallingTests.array_uint8_in(b"abcd")
+
+    def test_array_string_in(self):
+        GIMarshallingTests.array_string_in(['foo', 'bar'])
+
+    def test_array_out(self):
+        self.assertEqual([-1, 0, 1, 2], GIMarshallingTests.array_out())
+
+    def test_array_out_etc(self):
+        self.assertEqual(([-5, 0, 1, 9], 4), GIMarshallingTests.array_out_etc(-5, 9))
+
+    def test_array_inout(self):
+        self.assertEqual([-2, -1, 0, 1, 2], GIMarshallingTests.array_inout(Sequence([-1, 0, 1, 2])))
+
+    def test_array_inout_etc(self):
+        self.assertEqual(([-5, -1, 0, 1, 9], 4),
+                         GIMarshallingTests.array_inout_etc(-5, Sequence([-1, 0, 1, 2]), 9))
+
+    def test_method_array_in(self):
+        object_ = GIMarshallingTests.Object()
+        object_.method_array_in(Sequence([-1, 0, 1, 2]))
+
+    def test_method_array_out(self):
+        object_ = GIMarshallingTests.Object()
+        self.assertEqual([-1, 0, 1, 2], object_.method_array_out())
+
+    def test_method_array_inout(self):
+        object_ = GIMarshallingTests.Object()
+        self.assertEqual([-2, -1, 0, 1, 2], object_.method_array_inout(Sequence([-1, 0, 1, 2])))
+
+    def test_method_array_return(self):
+        object_ = GIMarshallingTests.Object()
+        self.assertEqual([-1, 0, 1, 2], object_.method_array_return())
+
+    def test_array_enum_in(self):
+        GIMarshallingTests.array_enum_in([GIMarshallingTests.Enum.VALUE1,
+                                          GIMarshallingTests.Enum.VALUE2,
+                                          GIMarshallingTests.Enum.VALUE3])
+
+    def test_array_boxed_struct_in(self):
+        struct1 = GIMarshallingTests.BoxedStruct()
+        struct1.long_ = 1
+        struct2 = GIMarshallingTests.BoxedStruct()
+        struct2.long_ = 2
+        struct3 = GIMarshallingTests.BoxedStruct()
+        struct3.long_ = 3
+
+        GIMarshallingTests.array_struct_in([struct1, struct2, struct3])
+
+    def test_array_boxed_struct_in_item_marshal_failure(self):
+        struct1 = GIMarshallingTests.BoxedStruct()
+        struct1.long_ = 1
+        struct2 = GIMarshallingTests.BoxedStruct()
+        struct2.long_ = 2
+
+        self.assertRaises(TypeError, GIMarshallingTests.array_struct_in,
+                          [struct1, struct2, 'not_a_struct'])
+
+    def test_array_boxed_struct_value_in(self):
+        struct1 = GIMarshallingTests.BoxedStruct()
+        struct1.long_ = 1
+        struct2 = GIMarshallingTests.BoxedStruct()
+        struct2.long_ = 2
+        struct3 = GIMarshallingTests.BoxedStruct()
+        struct3.long_ = 3
+
+        GIMarshallingTests.array_struct_value_in([struct1, struct2, struct3])
+
+    def test_array_boxed_struct_value_in_item_marshal_failure(self):
+        struct1 = GIMarshallingTests.BoxedStruct()
+        struct1.long_ = 1
+        struct2 = GIMarshallingTests.BoxedStruct()
+        struct2.long_ = 2
+
+        self.assertRaises(TypeError, GIMarshallingTests.array_struct_value_in,
+                          [struct1, struct2, 'not_a_struct'])
+
+    def test_array_boxed_struct_take_in(self):
+        struct1 = GIMarshallingTests.BoxedStruct()
+        struct1.long_ = 1
+        struct2 = GIMarshallingTests.BoxedStruct()
+        struct2.long_ = 2
+        struct3 = GIMarshallingTests.BoxedStruct()
+        struct3.long_ = 3
+
+        GIMarshallingTests.array_struct_take_in([struct1, struct2, struct3])
+
+        self.assertEqual(1, struct1.long_)
+
+    def test_array_boxed_struct_return(self):
+        (struct1, struct2, struct3) = GIMarshallingTests.array_zero_terminated_return_struct()
+        self.assertEqual(GIMarshallingTests.BoxedStruct, type(struct1))
+        self.assertEqual(GIMarshallingTests.BoxedStruct, type(struct2))
+        self.assertEqual(GIMarshallingTests.BoxedStruct, type(struct3))
+        self.assertEqual(42, struct1.long_)
+        self.assertEqual(43, struct2.long_)
+        self.assertEqual(44, struct3.long_)
+
+    def test_array_simple_struct_in(self):
+        struct1 = GIMarshallingTests.SimpleStruct()
+        struct1.long_ = 1
+        struct2 = GIMarshallingTests.SimpleStruct()
+        struct2.long_ = 2
+        struct3 = GIMarshallingTests.SimpleStruct()
+        struct3.long_ = 3
+
+        GIMarshallingTests.array_simple_struct_in([struct1, struct2, struct3])
+
+    def test_array_simple_struct_in_item_marshal_failure(self):
+        struct1 = GIMarshallingTests.SimpleStruct()
+        struct1.long_ = 1
+        struct2 = GIMarshallingTests.SimpleStruct()
+        struct2.long_ = 2
+
+        self.assertRaises(TypeError, GIMarshallingTests.array_simple_struct_in,
+                          [struct1, struct2, 'not_a_struct'])
+
+    def test_array_multi_array_key_value_in(self):
+        GIMarshallingTests.multi_array_key_value_in(["one", "two", "three"],
+                                                    [1, 2, 3])
+
+    def test_array_in_nonzero_nonlen(self):
+        GIMarshallingTests.array_in_nonzero_nonlen(1, b'abcd')
+
+    def test_array_fixed_out_struct(self):
+        struct1, struct2 = GIMarshallingTests.array_fixed_out_struct()
+
+        self.assertEqual(7, struct1.long_)
+        self.assertEqual(6, struct1.int8)
+        self.assertEqual(6, struct2.long_)
+        self.assertEqual(7, struct2.int8)
+
+    def test_array_zero_terminated_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.array_zero_terminated_return())
+
+    def test_array_zero_terminated_return_null(self):
+        self.assertEqual([], GIMarshallingTests.array_zero_terminated_return_null())
+
+    def test_array_zero_terminated_in(self):
+        GIMarshallingTests.array_zero_terminated_in(Sequence(['0', '1', '2']))
+
+    def test_array_zero_terminated_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.array_zero_terminated_out())
+
+    def test_array_zero_terminated_inout(self):
+        self.assertEqual(['-1', '0', '1', '2'], GIMarshallingTests.array_zero_terminated_inout(['0', '1', '2']))
+
+    def test_init_function(self):
+        self.assertEqual((True, []), GIMarshallingTests.init_function([]))
+        self.assertEqual((True, []), GIMarshallingTests.init_function(['hello']))
+        self.assertEqual((True, ['hello']),
+                         GIMarshallingTests.init_function(['hello', 'world']))
+
+    def test_enum_array_return_type(self):
+        self.assertEqual(GIMarshallingTests.enum_array_return_type(),
+                         [GIMarshallingTests.ExtraEnum.VALUE1,
+                          GIMarshallingTests.ExtraEnum.VALUE2,
+                          GIMarshallingTests.ExtraEnum.VALUE3])
+
+
+class TestGStrv(unittest.TestCase):
+
+    def test_gstrv_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gstrv_return())
+
+    def test_gstrv_in(self):
+        GIMarshallingTests.gstrv_in(Sequence(['0', '1', '2']))
+
+    def test_gstrv_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gstrv_out())
+
+    def test_gstrv_inout(self):
+        self.assertEqual(['-1', '0', '1', '2'], GIMarshallingTests.gstrv_inout(['0', '1', '2']))
+
+
+class TestArrayGVariant(unittest.TestCase):
+
+    def test_array_gvariant_none_in(self):
+        v = [GLib.Variant("i", 27), GLib.Variant("s", "Hello")]
+        returned = [GLib.Variant.unpack(r) for r in GIMarshallingTests.array_gvariant_none_in(v)]
+        self.assertEqual([27, "Hello"], returned)
+
+    def test_array_gvariant_container_in(self):
+        v = [GLib.Variant("i", 27), GLib.Variant("s", "Hello")]
+        returned = [GLib.Variant.unpack(r) for r in GIMarshallingTests.array_gvariant_container_in(v)]
+        self.assertEqual([27, "Hello"], returned)
+
+    def test_array_gvariant_full_in(self):
+        v = [GLib.Variant("i", 27), GLib.Variant("s", "Hello")]
+        returned = [GLib.Variant.unpack(r) for r in GIMarshallingTests.array_gvariant_full_in(v)]
+        self.assertEqual([27, "Hello"], returned)
+
+    def test_bytearray_gvariant(self):
+        v = GLib.Variant.new_bytestring(b"foo")
+        self.assertEqual(v.get_bytestring(), b"foo")
+
+
+class TestGArray(unittest.TestCase):
+
+    @unittest.skipUnless(
+        hasattr(GIMarshallingTests, "garray_bool_none_in"), "too old gi")
+    def test_garray_bool_none_in(self):
+        GIMarshallingTests.garray_bool_none_in([True, False, True, True])
+
+    @unittest.skipUnless(
+        hasattr(GIMarshallingTests, "garray_unichar_none_in"), "too old gi")
+    def test_garray_unichar_none_in(self):
+        GIMarshallingTests.garray_unichar_none_in(CONSTANT_UCS4)
+        GIMarshallingTests.garray_unichar_none_in(list(CONSTANT_UCS4))
+
+    def test_garray_int_none_return(self):
+        self.assertEqual([-1, 0, 1, 2], GIMarshallingTests.garray_int_none_return())
+
+    def test_garray_uint64_none_return(self):
+        self.assertEqual([0, GLib.MAXUINT64], GIMarshallingTests.garray_uint64_none_return())
+
+    def test_garray_utf8_none_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.garray_utf8_none_return())
+
+    def test_garray_utf8_container_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.garray_utf8_container_return())
+
+    def test_garray_utf8_full_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.garray_utf8_full_return())
+
+    def test_garray_int_none_in(self):
+        GIMarshallingTests.garray_int_none_in(Sequence([-1, 0, 1, 2]))
+
+        self.assertRaises(TypeError, GIMarshallingTests.garray_int_none_in, Sequence([-1, '0', 1, 2]))
+
+        self.assertRaises(TypeError, GIMarshallingTests.garray_int_none_in, 42)
+        self.assertRaises(TypeError, GIMarshallingTests.garray_int_none_in, None)
+
+    def test_garray_uint64_none_in(self):
+        GIMarshallingTests.garray_uint64_none_in(Sequence([0, GLib.MAXUINT64]))
+
+    def test_garray_utf8_none_in(self):
+        GIMarshallingTests.garray_utf8_none_in(Sequence(['0', '1', '2']))
+
+    def test_garray_utf8_none_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.garray_utf8_none_out())
+
+    def test_garray_utf8_container_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.garray_utf8_container_out())
+
+    def test_garray_utf8_full_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.garray_utf8_full_out())
+
+    def test_garray_utf8_full_out_caller_allocated(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.garray_utf8_full_out_caller_allocated())
+
+    def test_garray_utf8_none_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.garray_utf8_none_inout(Sequence(('0', '1', '2'))))
+
+    def test_garray_utf8_container_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.garray_utf8_container_inout(['0', '1', '2']))
+
+    def test_garray_utf8_full_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.garray_utf8_full_inout(['0', '1', '2']))
+
+
+class TestGPtrArray(unittest.TestCase):
+
+    def test_gptrarray_utf8_none_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gptrarray_utf8_none_return())
+
+    def test_gptrarray_utf8_container_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gptrarray_utf8_container_return())
+
+    def test_gptrarray_utf8_full_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gptrarray_utf8_full_return())
+
+    def test_gptrarray_utf8_none_in(self):
+        GIMarshallingTests.gptrarray_utf8_none_in(Sequence(['0', '1', '2']))
+
+    def test_gptrarray_utf8_none_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gptrarray_utf8_none_out())
+
+    def test_gptrarray_utf8_container_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gptrarray_utf8_container_out())
+
+    def test_gptrarray_utf8_full_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gptrarray_utf8_full_out())
+
+    def test_gptrarray_utf8_none_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.gptrarray_utf8_none_inout(Sequence(('0', '1', '2'))))
+
+    def test_gptrarray_utf8_container_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.gptrarray_utf8_container_inout(['0', '1', '2']))
+
+    def test_gptrarray_utf8_full_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.gptrarray_utf8_full_inout(['0', '1', '2']))
+
+
+class TestGBytes(unittest.TestCase):
+    def test_gbytes_create(self):
+        b = GLib.Bytes.new(b'\x00\x01\xFF')
+        self.assertEqual(3, b.get_size())
+        self.assertEqual(b'\x00\x01\xFF', b.get_data())
+
+    def test_gbytes_create_take(self):
+        b = GLib.Bytes.new_take(b'\x00\x01\xFF')
+        self.assertEqual(3, b.get_size())
+        self.assertEqual(b'\x00\x01\xFF', b.get_data())
+
+    def test_gbytes_full_return(self):
+        b = GIMarshallingTests.gbytes_full_return()
+        self.assertEqual(4, b.get_size())
+        self.assertEqual(b'\x00\x31\xFF\x33', b.get_data())
+
+    def test_gbytes_none_in(self):
+        b = GIMarshallingTests.gbytes_full_return()
+        GIMarshallingTests.gbytes_none_in(b)
+
+    def test_compare(self):
+        a1 = GLib.Bytes.new(b'\x00\x01\xFF')
+        a2 = GLib.Bytes.new(b'\x00\x01\xFF')
+        b = GLib.Bytes.new(b'\x00\x01\xFE')
+
+        self.assertTrue(a1.equal(a2))
+        self.assertTrue(a2.equal(a1))
+        self.assertFalse(a1.equal(b))
+        self.assertFalse(b.equal(a2))
+
+        self.assertEqual(0, a1.compare(a2))
+        self.assertLess(0, a1.compare(b))
+        self.assertGreater(0, b.compare(a1))
+
+
+class TestGByteArray(unittest.TestCase):
+    def test_new(self):
+        ba = GLib.ByteArray.new()
+        self.assertEqual(b'', ba)
+
+        ba = GLib.ByteArray.new_take(b'\x01\x02\xFF')
+        self.assertEqual(b'\x01\x02\xFF', ba)
+
+    def test_bytearray_full_return(self):
+        self.assertEqual(b'\x001\xFF3', GIMarshallingTests.bytearray_full_return())
+
+    def test_bytearray_none_in(self):
+        b = b'\x00\x31\xFF\x33'
+        ba = GLib.ByteArray.new_take(b)
+
+        # b should always have the same value even
+        # though the generated GByteArray is being modified
+        GIMarshallingTests.bytearray_none_in(b)
+        GIMarshallingTests.bytearray_none_in(b)
+
+        # The GByteArray is just a bytes
+        # thus it will not reflect any changes
+        GIMarshallingTests.bytearray_none_in(ba)
+        GIMarshallingTests.bytearray_none_in(ba)
+
+
+class TestGList(unittest.TestCase):
+
+    def test_glist_int_none_return(self):
+        self.assertEqual([-1, 0, 1, 2], GIMarshallingTests.glist_int_none_return())
+
+    def test_glist_uint32_none_return(self):
+        self.assertEqual([0, GLib.MAXUINT32], GIMarshallingTests.glist_uint32_none_return())
+
+    def test_glist_utf8_none_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.glist_utf8_none_return())
+
+    def test_glist_utf8_container_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.glist_utf8_container_return())
+
+    def test_glist_utf8_full_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.glist_utf8_full_return())
+
+    def test_glist_int_none_in(self):
+        GIMarshallingTests.glist_int_none_in(Sequence((-1, 0, 1, 2)))
+
+        self.assertRaises(TypeError, GIMarshallingTests.glist_int_none_in, Sequence((-1, '0', 1, 2)))
+
+        self.assertRaises(TypeError, GIMarshallingTests.glist_int_none_in, 42)
+        self.assertRaises(TypeError, GIMarshallingTests.glist_int_none_in, None)
+
+    def test_glist_int_none_in_error_getitem(self):
+
+        class FailingSequence(Sequence):
+            def __getitem__(self, key):
+                raise Exception
+
+        self.assertRaises(Exception, GIMarshallingTests.glist_int_none_in, FailingSequence((-1, 0, 1, 2)))
+
+    def test_glist_uint32_none_in(self):
+        GIMarshallingTests.glist_uint32_none_in(Sequence((0, GLib.MAXUINT32)))
+
+    def test_glist_utf8_none_in(self):
+        GIMarshallingTests.glist_utf8_none_in(Sequence(('0', '1', '2')))
+
+    def test_glist_utf8_none_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.glist_utf8_none_out())
+
+    def test_glist_utf8_container_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.glist_utf8_container_out())
+
+    def test_glist_utf8_full_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.glist_utf8_full_out())
+
+    def test_glist_utf8_none_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.glist_utf8_none_inout(Sequence(('0', '1', '2'))))
+
+    def test_glist_utf8_container_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.glist_utf8_container_inout(('0', '1', '2')))
+
+    def test_glist_utf8_full_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.glist_utf8_full_inout(('0', '1', '2')))
+
+
+class TestGSList(unittest.TestCase):
+
+    def test_gslist_int_none_return(self):
+        self.assertEqual([-1, 0, 1, 2], GIMarshallingTests.gslist_int_none_return())
+
+    def test_gslist_utf8_none_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gslist_utf8_none_return())
+
+    def test_gslist_utf8_container_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gslist_utf8_container_return())
+
+    def test_gslist_utf8_full_return(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gslist_utf8_full_return())
+
+    def test_gslist_int_none_in(self):
+        GIMarshallingTests.gslist_int_none_in(Sequence((-1, 0, 1, 2)))
+
+        self.assertRaises(TypeError, GIMarshallingTests.gslist_int_none_in, Sequence((-1, '0', 1, 2)))
+
+        self.assertRaises(TypeError, GIMarshallingTests.gslist_int_none_in, 42)
+        self.assertRaises(TypeError, GIMarshallingTests.gslist_int_none_in, None)
+
+    def test_gslist_int_none_in_error_getitem(self):
+
+        class FailingSequence(Sequence):
+            def __getitem__(self, key):
+                raise Exception
+
+        self.assertRaises(Exception, GIMarshallingTests.gslist_int_none_in, FailingSequence((-1, 0, 1, 2)))
+
+    def test_gslist_utf8_none_in(self):
+        GIMarshallingTests.gslist_utf8_none_in(Sequence(('0', '1', '2')))
+
+    def test_gslist_utf8_none_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gslist_utf8_none_out())
+
+    def test_gslist_utf8_container_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gslist_utf8_container_out())
+
+    def test_gslist_utf8_full_out(self):
+        self.assertEqual(['0', '1', '2'], GIMarshallingTests.gslist_utf8_full_out())
+
+    def test_gslist_utf8_none_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.gslist_utf8_none_inout(Sequence(('0', '1', '2'))))
+
+    def test_gslist_utf8_container_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.gslist_utf8_container_inout(('0', '1', '2')))
+
+    def test_gslist_utf8_full_inout(self):
+        self.assertEqual(['-2', '-1', '0', '1'], GIMarshallingTests.gslist_utf8_full_inout(('0', '1', '2')))
+
+
+class TestGHashTable(unittest.TestCase):
+
+    @unittest.skip("broken")
+    def test_ghashtable_double_in(self):
+        GIMarshallingTests.ghashtable_double_in(
+            {"-1": -0.1, "0": 0.0, "1": 0.1, "2": 0.2})
+
+    @unittest.skip("broken")
+    def test_ghashtable_float_in(self):
+        GIMarshallingTests.ghashtable_float_in(
+            {"-1": -0.1, "0": 0.0, "1": 0.1, "2": 0.2})
+
+    @unittest.skip("broken")
+    def test_ghashtable_int64_in(self):
+        GIMarshallingTests.ghashtable_int64_in(
+            {"-1": GLib.MAXUINT32 + 1, "0": 0, "1": 1, "2": 2})
+
+    @unittest.skip("broken")
+    def test_ghashtable_uint64_in(self):
+        GIMarshallingTests.ghashtable_uint64_in(
+            {"-1": GLib.MAXUINT32 + 1, "0": 0, "1": 1, "2": 2})
+
+    def test_ghashtable_int_none_return(self):
+        self.assertEqual({-1: 1, 0: 0, 1: -1, 2: -2}, GIMarshallingTests.ghashtable_int_none_return())
+
+    def test_ghashtable_int_none_return2(self):
+        self.assertEqual({'-1': '1', '0': '0', '1': '-1', '2': '-2'}, GIMarshallingTests.ghashtable_utf8_none_return())
+
+    def test_ghashtable_int_container_return(self):
+        self.assertEqual({'-1': '1', '0': '0', '1': '-1', '2': '-2'}, GIMarshallingTests.ghashtable_utf8_container_return())
+
+    def test_ghashtable_int_full_return(self):
+        self.assertEqual({'-1': '1', '0': '0', '1': '-1', '2': '-2'}, GIMarshallingTests.ghashtable_utf8_full_return())
+
+    def test_ghashtable_int_none_in(self):
+        GIMarshallingTests.ghashtable_int_none_in({-1: 1, 0: 0, 1: -1, 2: -2})
+
+        self.assertRaises(TypeError, GIMarshallingTests.ghashtable_int_none_in, {-1: 1, '0': 0, 1: -1, 2: -2})
+        self.assertRaises(TypeError, GIMarshallingTests.ghashtable_int_none_in, {-1: 1, 0: '0', 1: -1, 2: -2})
+
+        self.assertRaises(TypeError, GIMarshallingTests.ghashtable_int_none_in, '{-1: 1, 0: 0, 1: -1, 2: -2}')
+        self.assertRaises(TypeError, GIMarshallingTests.ghashtable_int_none_in, None)
+
+    def test_ghashtable_utf8_none_in(self):
+        GIMarshallingTests.ghashtable_utf8_none_in({'-1': '1', '0': '0', '1': '-1', '2': '-2'})
+
+    def test_ghashtable_utf8_none_out(self):
+        self.assertEqual({'-1': '1', '0': '0', '1': '-1', '2': '-2'}, GIMarshallingTests.ghashtable_utf8_none_out())
+
+    def test_ghashtable_utf8_container_out(self):
+        self.assertEqual({'-1': '1', '0': '0', '1': '-1', '2': '-2'}, GIMarshallingTests.ghashtable_utf8_container_out())
+
+    def test_ghashtable_utf8_full_out(self):
+        self.assertEqual({'-1': '1', '0': '0', '1': '-1', '2': '-2'}, GIMarshallingTests.ghashtable_utf8_full_out())
+
+    def test_ghashtable_utf8_none_inout(self):
+        i = {'-1': '1', '0': '0', '1': '-1', '2': '-2'}
+        self.assertEqual({'-1': '1', '0': '0', '1': '1'},
+                         GIMarshallingTests.ghashtable_utf8_none_inout(i))
+
+    def test_ghashtable_utf8_container_inout(self):
+        i = {'-1': '1', '0': '0', '1': '-1', '2': '-2'}
+        self.assertEqual({'-1': '1', '0': '0', '1': '1'},
+                         GIMarshallingTests.ghashtable_utf8_container_inout(i))
+
+    def test_ghashtable_utf8_full_inout(self):
+        i = {'-1': '1', '0': '0', '1': '-1', '2': '-2'}
+        self.assertEqual({'-1': '1', '0': '0', '1': '1'},
+                         GIMarshallingTests.ghashtable_utf8_full_inout(i))
+
+    def test_ghashtable_enum_none_in(self):
+        GIMarshallingTests.ghashtable_enum_none_in({1: GIMarshallingTests.ExtraEnum.VALUE1,
+                                                    2: GIMarshallingTests.ExtraEnum.VALUE2,
+                                                    3: GIMarshallingTests.ExtraEnum.VALUE3})
+
+    def test_ghashtable_enum_none_return(self):
+        self.assertEqual({1: GIMarshallingTests.ExtraEnum.VALUE1,
+                          2: GIMarshallingTests.ExtraEnum.VALUE2,
+                          3: GIMarshallingTests.ExtraEnum.VALUE3},
+                         GIMarshallingTests.ghashtable_enum_none_return())
+
+
+class TestGValue(unittest.TestCase):
+
+    def test_gvalue_return(self):
+        self.assertEqual(42, GIMarshallingTests.gvalue_return())
+
+    def test_gvalue_in(self):
+        GIMarshallingTests.gvalue_in(42)
+        value = GObject.Value(GObject.TYPE_INT, 42)
+        GIMarshallingTests.gvalue_in(value)
+
+    def test_gvalue_in_with_modification(self):
+        value = GObject.Value(GObject.TYPE_INT, 42)
+        GIMarshallingTests.gvalue_in_with_modification(value)
+        self.assertEqual(value.get_int(), 24)
+
+    def test_gvalue_int64_in(self):
+        value = GObject.Value(GObject.TYPE_INT64, GLib.MAXINT64)
+        GIMarshallingTests.gvalue_int64_in(value)
+
+    def test_gvalue_in_with_type(self):
+        value = GObject.Value(GObject.TYPE_STRING, 'foo')
+        GIMarshallingTests.gvalue_in_with_type(value, GObject.TYPE_STRING)
+
+        value = GObject.Value(GIMarshallingTests.Flags.__gtype__,
+                              GIMarshallingTests.Flags.VALUE1)
+        GIMarshallingTests.gvalue_in_with_type(value, GObject.TYPE_FLAGS)
+
+    def test_gvalue_in_enum(self):
+        value = GObject.Value(GIMarshallingTests.Enum.__gtype__,
+                              GIMarshallingTests.Enum.VALUE3)
+        GIMarshallingTests.gvalue_in_enum(value)
+
+    def test_gvalue_out(self):
+        self.assertEqual(42, GIMarshallingTests.gvalue_out())
+
+    def test_gvalue_int64_out(self):
+        self.assertEqual(GLib.MAXINT64, GIMarshallingTests.gvalue_int64_out())
+
+    def test_gvalue_out_caller_allocates(self):
+        self.assertEqual(42, GIMarshallingTests.gvalue_out_caller_allocates())
+
+    def test_gvalue_inout(self):
+        self.assertEqual('42', GIMarshallingTests.gvalue_inout(42))
+        value = GObject.Value(int, 42)
+        self.assertEqual('42', GIMarshallingTests.gvalue_inout(value))
+
+    def test_gvalue_flat_array_in(self):
+        # the function already asserts the correct values
+        GIMarshallingTests.gvalue_flat_array([42, "42", True])
+
+    def test_gvalue_flat_array_in_item_marshal_failure(self):
+        # Tests the failure to marshal 2^256 to a GValue mid-way through the array marshaling.
+        self.assertRaises(OverflowError, GIMarshallingTests.gvalue_flat_array,
+                          [42, 2 ** 256, True])
+
+        self.assertRaises(OverflowError, GIMarshallingTests.gvalue_flat_array,
+                          [GLib.MAXINT + 1, "42", True])
+        self.assertRaises(OverflowError, GIMarshallingTests.gvalue_flat_array,
+                          [GLib.MININT - 1, "42", True])
+
+        with pytest.raises(
+                OverflowError,
+                match='Item 0: %d not in range %d to %d' % (
+                    GLib.MAXINT + 1, GLib.MININT, GLib.MAXINT)):
+            GIMarshallingTests.gvalue_flat_array([GLib.MAXINT + 1, "42", True])
+
+        if PY2:
+            min_, max_ = GLib.MINLONG, GLib.MAXLONG
+        else:
+            min_, max_ = GLib.MININT, GLib.MAXINT
+
+        with pytest.raises(
+                OverflowError,
+                match='Item 0: %d not in range %d to %d' % (
+                    GLib.MAXUINT64 * 2, min_, max_)):
+            GIMarshallingTests.gvalue_flat_array([GLib.MAXUINT64 * 2, "42", True])
+
+    def test_gvalue_flat_array_out(self):
+        values = GIMarshallingTests.return_gvalue_flat_array()
+        self.assertEqual(values, [42, '42', True])
+
+    def test_gvalue_gobject_ref_counts_simple(self):
+        obj = GObject.Object()
+        grefcount = obj.__grefcount__
+        value = GObject.Value(GObject.TYPE_OBJECT, obj)
+        del value
+        gc.collect()
+        gc.collect()
+        assert obj.__grefcount__ == grefcount
+
+    @unittest.skipIf(platform.python_implementation() == "PyPy" and PY3, "fixme")
+    def test_gvalue_gobject_ref_counts(self):
+        # Tests a GObject held by a GValue
+        obj = GObject.Object()
+        ref = weakref.ref(obj)
+        grefcount = obj.__grefcount__
+
+        value = GObject.Value()
+        value.init(GObject.TYPE_OBJECT)
+
+        # TYPE_OBJECT will inc ref count as it should
+        value.set_object(obj)
+        self.assertEqual(obj.__grefcount__, grefcount + 1)
+
+        # multiple set_object should not inc ref count
+        value.set_object(obj)
+        self.assertEqual(obj.__grefcount__, grefcount + 1)
+
+        # get_object will re-use the same wrapper as obj
+        res = value.get_object()
+        self.assertEqual(obj, res)
+        self.assertEqual(obj.__grefcount__, grefcount + 1)
+
+        # multiple get_object should not inc ref count
+        res = value.get_object()
+        self.assertEqual(obj.__grefcount__, grefcount + 1)
+
+        # deletion of the result and value holder should bring the
+        # refcount back to where we started
+        del res
+        del value
+        gc.collect()
+        gc.collect()
+        self.assertEqual(obj.__grefcount__, grefcount)
+
+        del obj
+        gc.collect()
+        self.assertEqual(ref(), None)
+
+    @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount")
+    def test_gvalue_boxed_ref_counts(self):
+        # Tests a boxed type wrapping a python object pointer (TYPE_PYOBJECT)
+        # held by a GValue
+        class Obj(object):
+            pass
+
+        obj = Obj()
+        ref = weakref.ref(obj)
+        refcount = sys.getrefcount(obj)
+
+        value = GObject.Value()
+        value.init(GObject.TYPE_PYOBJECT)
+
+        # boxed TYPE_PYOBJECT will inc ref count as it should
+        value.set_boxed(obj)
+        self.assertEqual(sys.getrefcount(obj), refcount + 1)
+
+        # multiple set_boxed should not inc ref count
+        value.set_boxed(obj)
+        self.assertEqual(sys.getrefcount(obj), refcount + 1)
+
+        res = value.get_boxed()
+        self.assertEqual(obj, res)
+        self.assertEqual(sys.getrefcount(obj), refcount + 2)
+
+        # multiple get_boxed should not inc ref count
+        res = value.get_boxed()
+        self.assertEqual(sys.getrefcount(obj), refcount + 2)
+
+        # deletion of the result and value holder should bring the
+        # refcount back to where we started
+        del res
+        del value
+        gc.collect()
+        self.assertEqual(sys.getrefcount(obj), refcount)
+
+        del obj
+        gc.collect()
+        self.assertEqual(ref(), None)
+
+    @unittest.skip("broken")
+    def test_gvalue_flat_array_round_trip(self):
+        self.assertEqual([42, '42', True],
+                         GIMarshallingTests.gvalue_flat_array_round_trip(42, '42', True))
+
+
+class TestGClosure(unittest.TestCase):
+
+    def test_in(self):
+        GIMarshallingTests.gclosure_in(lambda: 42)
+
+    def test_pass(self):
+        # test passing a closure between two C calls
+        closure = GIMarshallingTests.gclosure_return()
+        GIMarshallingTests.gclosure_in(closure)
+
+    def test_type_error(self):
+        self.assertRaises(TypeError, GIMarshallingTests.gclosure_in, 42)
+        self.assertRaises(TypeError, GIMarshallingTests.gclosure_in, None)
+
+
+class TestCallbacks(unittest.TestCase):
+    def test_return_value_only(self):
+        def cb():
+            return 5
+        self.assertEqual(GIMarshallingTests.callback_return_value_only(cb), 5)
+
+    def test_one_out_arg(self):
+        def cb():
+            return 5.5
+        self.assertAlmostEqual(GIMarshallingTests.callback_one_out_parameter(cb), 5.5)
+
+    def test_multiple_out_args(self):
+        def cb():
+            return (5.5, 42.0)
+        res = GIMarshallingTests.callback_multiple_out_parameters(cb)
+        self.assertAlmostEqual(res[0], 5.5)
+        self.assertAlmostEqual(res[1], 42.0)
+
+    def test_return_and_one_out_arg(self):
+        def cb():
+            return (5, 42.0)
+        res = GIMarshallingTests.callback_return_value_and_one_out_parameter(cb)
+        self.assertEqual(res[0], 5)
+        self.assertAlmostEqual(res[1], 42.0)
+
+    def test_return_and_multiple_out_arg(self):
+        def cb():
+            return (5, 42, -1000)
+        self.assertEqual(GIMarshallingTests.callback_return_value_and_multiple_out_parameters(cb),
+                         (5, 42, -1000))
+
+
+class TestPointer(unittest.TestCase):
+    def test_pointer_in_return(self):
+        self.assertEqual(GIMarshallingTests.pointer_in_return(42), 42)
+
+
+class TestEnum(unittest.TestCase):
+
+    def test_enum(self):
+        self.assertTrue(issubclass(GIMarshallingTests.Enum, int))
+        self.assertTrue(isinstance(GIMarshallingTests.Enum.VALUE1, GIMarshallingTests.Enum))
+        self.assertTrue(isinstance(GIMarshallingTests.Enum.VALUE2, GIMarshallingTests.Enum))
+        self.assertTrue(isinstance(GIMarshallingTests.Enum.VALUE3, GIMarshallingTests.Enum))
+        self.assertEqual(42, GIMarshallingTests.Enum.VALUE3)
+
+    def test_value_nick_and_name(self):
+        self.assertEqual(GIMarshallingTests.Enum.VALUE1.value_nick, 'value1')
+        self.assertEqual(GIMarshallingTests.Enum.VALUE2.value_nick, 'value2')
+        self.assertEqual(GIMarshallingTests.Enum.VALUE3.value_nick, 'value3')
+
+        self.assertEqual(GIMarshallingTests.Enum.VALUE1.value_name, 'GI_MARSHALLING_TESTS_ENUM_VALUE1')
+        self.assertEqual(GIMarshallingTests.Enum.VALUE2.value_name, 'GI_MARSHALLING_TESTS_ENUM_VALUE2')
+        self.assertEqual(GIMarshallingTests.Enum.VALUE3.value_name, 'GI_MARSHALLING_TESTS_ENUM_VALUE3')
+
+    def test_enum_in(self):
+        GIMarshallingTests.enum_in(GIMarshallingTests.Enum.VALUE3)
+        GIMarshallingTests.enum_in(42)
+
+        self.assertRaises(TypeError, GIMarshallingTests.enum_in, 43)
+        self.assertRaises(TypeError, GIMarshallingTests.enum_in, 'GIMarshallingTests.Enum.VALUE3')
+
+    def test_enum_return(self):
+        enum = GIMarshallingTests.enum_returnv()
+        self.assertTrue(isinstance(enum, GIMarshallingTests.Enum))
+        self.assertEqual(enum, GIMarshallingTests.Enum.VALUE3)
+
+    def test_enum_out(self):
+        enum = GIMarshallingTests.enum_out()
+        self.assertTrue(isinstance(enum, GIMarshallingTests.Enum))
+        self.assertEqual(enum, GIMarshallingTests.Enum.VALUE3)
+
+    def test_enum_inout(self):
+        enum = GIMarshallingTests.enum_inout(GIMarshallingTests.Enum.VALUE3)
+        self.assertTrue(isinstance(enum, GIMarshallingTests.Enum))
+        self.assertEqual(enum, GIMarshallingTests.Enum.VALUE1)
+
+    def test_enum_second(self):
+        # check for the bug where different non-gtype enums share the same class
+        self.assertNotEqual(GIMarshallingTests.Enum, GIMarshallingTests.SecondEnum)
+
+        # check that values are not being shared between different enums
+        self.assertTrue(hasattr(GIMarshallingTests.SecondEnum, "SECONDVALUE1"))
+        self.assertRaises(AttributeError, getattr, GIMarshallingTests.Enum, "SECONDVALUE1")
+        self.assertTrue(hasattr(GIMarshallingTests.Enum, "VALUE1"))
+        self.assertRaises(AttributeError, getattr, GIMarshallingTests.SecondEnum, "VALUE1")
+
+    def test_enum_gtype_name_is_namespaced(self):
+        self.assertEqual(GIMarshallingTests.Enum.__gtype__.name,
+                         'PyGIMarshallingTestsEnum')
+
+    def test_enum_add_type_error(self):
+        self.assertRaises(TypeError,
+                          gi._gi.enum_add,
+                          GIMarshallingTests.NoTypeFlags.__gtype__)
+
+    def test_type_module_name(self):
+        self.assertEqual(GIMarshallingTests.Enum.__name__, "Enum")
+        self.assertEqual(GIMarshallingTests.Enum.__module__,
+                         "gi.repository.GIMarshallingTests")
+
+    def test_hash(self):
+        assert (hash(GIMarshallingTests.Enum.VALUE1) ==
+                hash(GIMarshallingTests.Enum(GIMarshallingTests.Enum.VALUE1)))
+
+    def test_repr(self):
+        self.assertEqual(repr(GIMarshallingTests.Enum.VALUE3),
+                         "<enum GI_MARSHALLING_TESTS_ENUM_VALUE3 of type "
+                         "GIMarshallingTests.Enum>")
+
+
+class TestEnumVFuncResults(unittest.TestCase):
+    class EnumTester(GIMarshallingTests.Object):
+        def do_vfunc_return_enum(self):
+            return GIMarshallingTests.Enum.VALUE2
+
+        def do_vfunc_out_enum(self):
+            return GIMarshallingTests.Enum.VALUE3
+
+    def test_vfunc_return_enum(self):
+        tester = self.EnumTester()
+        self.assertEqual(tester.vfunc_return_enum(), GIMarshallingTests.Enum.VALUE2)
+
+    def test_vfunc_out_enum(self):
+        tester = self.EnumTester()
+        self.assertEqual(tester.vfunc_out_enum(), GIMarshallingTests.Enum.VALUE3)
+
+
+class TestGEnum(unittest.TestCase):
+
+    def test_genum(self):
+        self.assertTrue(issubclass(GIMarshallingTests.GEnum, GObject.GEnum))
+        self.assertTrue(isinstance(GIMarshallingTests.GEnum.VALUE1, GIMarshallingTests.GEnum))
+        self.assertTrue(isinstance(GIMarshallingTests.GEnum.VALUE2, GIMarshallingTests.GEnum))
+        self.assertTrue(isinstance(GIMarshallingTests.GEnum.VALUE3, GIMarshallingTests.GEnum))
+        self.assertEqual(42, GIMarshallingTests.GEnum.VALUE3)
+
+    def test_pickle(self):
+        v = GIMarshallingTests.GEnum.VALUE3
+        new_v = pickle.loads(pickle.dumps(v))
+        assert new_v == v
+        assert isinstance(new_v, GIMarshallingTests.GEnum)
+
+    def test_value_nick_and_name(self):
+        self.assertEqual(GIMarshallingTests.GEnum.VALUE1.value_nick, 'value1')
+        self.assertEqual(GIMarshallingTests.GEnum.VALUE2.value_nick, 'value2')
+        self.assertEqual(GIMarshallingTests.GEnum.VALUE3.value_nick, 'value3')
+
+        self.assertEqual(GIMarshallingTests.GEnum.VALUE1.value_name, 'GI_MARSHALLING_TESTS_GENUM_VALUE1')
+        self.assertEqual(GIMarshallingTests.GEnum.VALUE2.value_name, 'GI_MARSHALLING_TESTS_GENUM_VALUE2')
+        self.assertEqual(GIMarshallingTests.GEnum.VALUE3.value_name, 'GI_MARSHALLING_TESTS_GENUM_VALUE3')
+
+    def test_genum_in(self):
+        GIMarshallingTests.genum_in(GIMarshallingTests.GEnum.VALUE3)
+        GIMarshallingTests.genum_in(42)
+        GIMarshallingTests.GEnum.in_(42)
+
+        self.assertRaises(TypeError, GIMarshallingTests.genum_in, 43)
+        self.assertRaises(TypeError, GIMarshallingTests.genum_in, 'GIMarshallingTests.GEnum.VALUE3')
+
+    def test_genum_return(self):
+        genum = GIMarshallingTests.genum_returnv()
+        self.assertTrue(isinstance(genum, GIMarshallingTests.GEnum))
+        self.assertEqual(genum, GIMarshallingTests.GEnum.VALUE3)
+
+    def test_genum_out(self):
+        genum = GIMarshallingTests.genum_out()
+        genum = GIMarshallingTests.GEnum.out()
+        self.assertTrue(isinstance(genum, GIMarshallingTests.GEnum))
+        self.assertEqual(genum, GIMarshallingTests.GEnum.VALUE3)
+
+    def test_genum_inout(self):
+        genum = GIMarshallingTests.genum_inout(GIMarshallingTests.GEnum.VALUE3)
+        self.assertTrue(isinstance(genum, GIMarshallingTests.GEnum))
+        self.assertEqual(genum, GIMarshallingTests.GEnum.VALUE1)
+
+    def test_type_module_name(self):
+        self.assertEqual(GIMarshallingTests.GEnum.__name__, "GEnum")
+        self.assertEqual(GIMarshallingTests.GEnum.__module__,
+                         "gi.repository.GIMarshallingTests")
+
+    def test_hash(self):
+        assert (hash(GIMarshallingTests.GEnum.VALUE3) ==
+                hash(GIMarshallingTests.GEnum(GIMarshallingTests.GEnum.VALUE3)))
+
+    def test_repr(self):
+        self.assertEqual(repr(GIMarshallingTests.GEnum.VALUE3),
+                         "<enum GI_MARSHALLING_TESTS_GENUM_VALUE3 of type "
+                         "GIMarshallingTests.GEnum>")
+
+
+class TestGFlags(unittest.TestCase):
+
+    def test_flags(self):
+        self.assertTrue(issubclass(GIMarshallingTests.Flags, GObject.GFlags))
+        self.assertTrue(isinstance(GIMarshallingTests.Flags.VALUE1, GIMarshallingTests.Flags))
+        self.assertTrue(isinstance(GIMarshallingTests.Flags.VALUE2, GIMarshallingTests.Flags))
+        self.assertTrue(isinstance(GIMarshallingTests.Flags.VALUE3, GIMarshallingTests.Flags))
+        # __or__() operation should still return an instance, not an int.
+        self.assertTrue(isinstance(GIMarshallingTests.Flags.VALUE1 | GIMarshallingTests.Flags.VALUE2,
+                                   GIMarshallingTests.Flags))
+        self.assertEqual(1 << 1, GIMarshallingTests.Flags.VALUE2)
+
+    def test_value_nick_and_name(self):
+        self.assertEqual(GIMarshallingTests.Flags.VALUE1.first_value_nick, 'value1')
+        self.assertEqual(GIMarshallingTests.Flags.VALUE2.first_value_nick, 'value2')
+        self.assertEqual(GIMarshallingTests.Flags.VALUE3.first_value_nick, 'value3')
+
+        self.assertEqual(GIMarshallingTests.Flags.VALUE1.first_value_name, 'GI_MARSHALLING_TESTS_FLAGS_VALUE1')
+        self.assertEqual(GIMarshallingTests.Flags.VALUE2.first_value_name, 'GI_MARSHALLING_TESTS_FLAGS_VALUE2')
+        self.assertEqual(GIMarshallingTests.Flags.VALUE3.first_value_name, 'GI_MARSHALLING_TESTS_FLAGS_VALUE3')
+
+    def test_flags_in(self):
+        GIMarshallingTests.flags_in(GIMarshallingTests.Flags.VALUE2)
+        GIMarshallingTests.Flags.in_(GIMarshallingTests.Flags.VALUE2)
+        # result of __or__() operation should still be valid instance, not an int.
+        GIMarshallingTests.flags_in(GIMarshallingTests.Flags.VALUE2 | GIMarshallingTests.Flags.VALUE2)
+        GIMarshallingTests.flags_in_zero(Number(0))
+        GIMarshallingTests.Flags.in_zero(Number(0))
+
+        self.assertRaises(TypeError, GIMarshallingTests.flags_in, 1 << 1)
+        self.assertRaises(TypeError, GIMarshallingTests.flags_in, 'GIMarshallingTests.Flags.VALUE2')
+
+    def test_flags_return(self):
+        flags = GIMarshallingTests.flags_returnv()
+        self.assertTrue(isinstance(flags, GIMarshallingTests.Flags))
+        self.assertEqual(flags, GIMarshallingTests.Flags.VALUE2)
+
+    def test_flags_return_method(self):
+        flags = GIMarshallingTests.Flags.returnv()
+        self.assertTrue(isinstance(flags, GIMarshallingTests.Flags))
+        self.assertEqual(flags, GIMarshallingTests.Flags.VALUE2)
+
+    def test_flags_out(self):
+        flags = GIMarshallingTests.flags_out()
+        self.assertTrue(isinstance(flags, GIMarshallingTests.Flags))
+        self.assertEqual(flags, GIMarshallingTests.Flags.VALUE2)
+
+    def test_flags_inout(self):
+        flags = GIMarshallingTests.flags_inout(GIMarshallingTests.Flags.VALUE2)
+        self.assertTrue(isinstance(flags, GIMarshallingTests.Flags))
+        self.assertEqual(flags, GIMarshallingTests.Flags.VALUE1)
+
+    def test_type_module_name(self):
+        self.assertEqual(GIMarshallingTests.Flags.__name__, "Flags")
+        self.assertEqual(GIMarshallingTests.Flags.__module__,
+                         "gi.repository.GIMarshallingTests")
+
+    def test_repr(self):
+        self.assertEqual(repr(GIMarshallingTests.Flags.VALUE2),
+                         "<flags GI_MARSHALLING_TESTS_FLAGS_VALUE2 of type "
+                         "GIMarshallingTests.Flags>")
+
+    def test_hash(self):
+        assert (hash(GIMarshallingTests.Flags.VALUE2) ==
+                hash(GIMarshallingTests.Flags(GIMarshallingTests.Flags.VALUE2)))
+
+    def test_flags_large_in(self):
+        GIMarshallingTests.extra_flags_large_in(
+            GIMarshallingTests.ExtraFlags.VALUE2)
+
+
+class TestNoTypeFlags(unittest.TestCase):
+
+    def test_flags(self):
+        self.assertTrue(issubclass(GIMarshallingTests.NoTypeFlags, GObject.GFlags))
+        self.assertTrue(isinstance(GIMarshallingTests.NoTypeFlags.VALUE1, GIMarshallingTests.NoTypeFlags))
+        self.assertTrue(isinstance(GIMarshallingTests.NoTypeFlags.VALUE2, GIMarshallingTests.NoTypeFlags))
+        self.assertTrue(isinstance(GIMarshallingTests.NoTypeFlags.VALUE3, GIMarshallingTests.NoTypeFlags))
+        # __or__() operation should still return an instance, not an int.
+        self.assertTrue(isinstance(GIMarshallingTests.NoTypeFlags.VALUE1 | GIMarshallingTests.NoTypeFlags.VALUE2,
+                                   GIMarshallingTests.NoTypeFlags))
+        self.assertEqual(1 << 1, GIMarshallingTests.NoTypeFlags.VALUE2)
+
+    def test_value_nick_and_name(self):
+        self.assertEqual(GIMarshallingTests.NoTypeFlags.VALUE1.first_value_nick, 'value1')
+        self.assertEqual(GIMarshallingTests.NoTypeFlags.VALUE2.first_value_nick, 'value2')
+        self.assertEqual(GIMarshallingTests.NoTypeFlags.VALUE3.first_value_nick, 'value3')
+
+        self.assertEqual(GIMarshallingTests.NoTypeFlags.VALUE1.first_value_name, 'GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE1')
+        self.assertEqual(GIMarshallingTests.NoTypeFlags.VALUE2.first_value_name, 'GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE2')
+        self.assertEqual(GIMarshallingTests.NoTypeFlags.VALUE3.first_value_name, 'GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE3')
+
+    def test_flags_in(self):
+        GIMarshallingTests.no_type_flags_in(GIMarshallingTests.NoTypeFlags.VALUE2)
+        GIMarshallingTests.no_type_flags_in(GIMarshallingTests.NoTypeFlags.VALUE2 | GIMarshallingTests.NoTypeFlags.VALUE2)
+        GIMarshallingTests.no_type_flags_in_zero(Number(0))
+
+        self.assertRaises(TypeError, GIMarshallingTests.no_type_flags_in, 1 << 1)
+        self.assertRaises(TypeError, GIMarshallingTests.no_type_flags_in, 'GIMarshallingTests.NoTypeFlags.VALUE2')
+
+    def test_flags_return(self):
+        flags = GIMarshallingTests.no_type_flags_returnv()
+        self.assertTrue(isinstance(flags, GIMarshallingTests.NoTypeFlags))
+        self.assertEqual(flags, GIMarshallingTests.NoTypeFlags.VALUE2)
+
+    def test_flags_out(self):
+        flags = GIMarshallingTests.no_type_flags_out()
+        self.assertTrue(isinstance(flags, GIMarshallingTests.NoTypeFlags))
+        self.assertEqual(flags, GIMarshallingTests.NoTypeFlags.VALUE2)
+
+    def test_flags_inout(self):
+        flags = GIMarshallingTests.no_type_flags_inout(GIMarshallingTests.NoTypeFlags.VALUE2)
+        self.assertTrue(isinstance(flags, GIMarshallingTests.NoTypeFlags))
+        self.assertEqual(flags, GIMarshallingTests.NoTypeFlags.VALUE1)
+
+    def test_flags_gtype_name_is_namespaced(self):
+        self.assertEqual(GIMarshallingTests.NoTypeFlags.__gtype__.name,
+                         'PyGIMarshallingTestsNoTypeFlags')
+
+    def test_type_module_name(self):
+        self.assertEqual(GIMarshallingTests.NoTypeFlags.__name__,
+                         "NoTypeFlags")
+        self.assertEqual(GIMarshallingTests.NoTypeFlags.__module__,
+                         "gi.repository.GIMarshallingTests")
+
+    def test_repr(self):
+        self.assertEqual(repr(GIMarshallingTests.NoTypeFlags.VALUE2),
+                         "<flags GI_MARSHALLING_TESTS_NO_TYPE_FLAGS_VALUE2 of "
+                         "type GIMarshallingTests.NoTypeFlags>")
+
+
+class TestStructure(unittest.TestCase):
+
+    def test_simple_struct(self):
+        self.assertTrue(issubclass(GIMarshallingTests.SimpleStruct, GObject.GPointer))
+
+        struct = GIMarshallingTests.SimpleStruct()
+        self.assertTrue(isinstance(struct, GIMarshallingTests.SimpleStruct))
+
+        self.assertEqual(0, struct.long_)
+        self.assertEqual(0, struct.int8)
+
+        struct.long_ = 6
+        struct.int8 = 7
+
+        self.assertEqual(6, struct.long_)
+        self.assertEqual(7, struct.int8)
+
+        del struct
+
+    def test_nested_struct(self):
+        struct = GIMarshallingTests.NestedStruct()
+
+        self.assertTrue(isinstance(struct.simple_struct, GIMarshallingTests.SimpleStruct))
+
+        struct.simple_struct.long_ = 42
+        self.assertEqual(42, struct.simple_struct.long_)
+
+        del struct
+
+    def test_not_simple_struct(self):
+        struct = GIMarshallingTests.NotSimpleStruct()
+        self.assertEqual(None, struct.pointer)
+
+    def test_simple_struct_return(self):
+        struct = GIMarshallingTests.simple_struct_returnv()
+
+        self.assertTrue(isinstance(struct, GIMarshallingTests.SimpleStruct))
+        self.assertEqual(6, struct.long_)
+        self.assertEqual(7, struct.int8)
+
+        del struct
+
+    def test_simple_struct_in(self):
+        struct = GIMarshallingTests.SimpleStruct()
+        struct.long_ = 6
+        struct.int8 = 7
+
+        GIMarshallingTests.SimpleStruct.inv(struct)
+
+        del struct
+
+        struct = GIMarshallingTests.NestedStruct()
+
+        self.assertRaises(TypeError, GIMarshallingTests.SimpleStruct.inv, struct)
+
+        del struct
+
+        self.assertRaises(TypeError, GIMarshallingTests.SimpleStruct.inv, None)
+
+    def test_simple_struct_method(self):
+        struct = GIMarshallingTests.SimpleStruct()
+        struct.long_ = 6
+        struct.int8 = 7
+
+        struct.method()
+
+        del struct
+
+        self.assertRaises(TypeError, GIMarshallingTests.SimpleStruct.method)
+
+    def test_pointer_struct(self):
+        self.assertTrue(issubclass(GIMarshallingTests.PointerStruct, GObject.GPointer))
+
+        struct = GIMarshallingTests.PointerStruct()
+        self.assertTrue(isinstance(struct, GIMarshallingTests.PointerStruct))
+
+        del struct
+
+    def test_pointer_struct_return(self):
+        struct = GIMarshallingTests.pointer_struct_returnv()
+
+        self.assertTrue(isinstance(struct, GIMarshallingTests.PointerStruct))
+        self.assertEqual(42, struct.long_)
+
+        del struct
+
+    def test_pointer_struct_in(self):
+        struct = GIMarshallingTests.PointerStruct()
+        struct.long_ = 42
+
+        struct.inv()
+
+        del struct
+
+    def test_boxed_struct(self):
+        self.assertTrue(issubclass(GIMarshallingTests.BoxedStruct, GObject.GBoxed))
+
+        struct = GIMarshallingTests.BoxedStruct()
+        self.assertTrue(isinstance(struct, GIMarshallingTests.BoxedStruct))
+
+        self.assertEqual(0, struct.long_)
+        self.assertEqual(None, struct.string_)
+        self.assertEqual([], struct.g_strv)
+
+        del struct
+
+    def test_boxed_struct_new(self):
+        struct = GIMarshallingTests.BoxedStruct.new()
+        self.assertTrue(isinstance(struct, GIMarshallingTests.BoxedStruct))
+        self.assertEqual(struct.long_, 0)
+        self.assertEqual(struct.string_, None)
+
+        del struct
+
+    def test_boxed_struct_copy(self):
+        struct = GIMarshallingTests.BoxedStruct()
+        struct.long_ = 42
+        struct.string_ = 'hello'
+
+        new_struct = struct.copy()
+        self.assertTrue(isinstance(new_struct, GIMarshallingTests.BoxedStruct))
+        self.assertEqual(new_struct.long_, 42)
+        self.assertEqual(new_struct.string_, 'hello')
+
+        del new_struct
+        del struct
+
+    def test_boxed_struct_return(self):
+        struct = GIMarshallingTests.boxed_struct_returnv()
+
+        self.assertTrue(isinstance(struct, GIMarshallingTests.BoxedStruct))
+        self.assertEqual(42, struct.long_)
+        self.assertEqual('hello', struct.string_)
+        self.assertEqual(['0', '1', '2'], struct.g_strv)
+
+        del struct
+
+    def test_boxed_struct_in(self):
+        struct = GIMarshallingTests.BoxedStruct()
+        struct.long_ = 42
+
+        struct.inv()
+
+        del struct
+
+    def test_boxed_struct_out(self):
+        struct = GIMarshallingTests.boxed_struct_out()
+
+        self.assertTrue(isinstance(struct, GIMarshallingTests.BoxedStruct))
+        self.assertEqual(42, struct.long_)
+
+        del struct
+
+    def test_boxed_struct_inout(self):
+        in_struct = GIMarshallingTests.BoxedStruct()
+        in_struct.long_ = 42
+
+        out_struct = GIMarshallingTests.boxed_struct_inout(in_struct)
+
+        self.assertTrue(isinstance(out_struct, GIMarshallingTests.BoxedStruct))
+        self.assertEqual(0, out_struct.long_)
+
+        del in_struct
+        del out_struct
+
+    def test_struct_field_assignment(self):
+        struct = GIMarshallingTests.BoxedStruct()
+
+        struct.long_ = 42
+        struct.string_ = 'hello'
+        self.assertEqual(struct.long_, 42)
+        self.assertEqual(struct.string_, 'hello')
+
+    def test_union_init(self):
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            GIMarshallingTests.Union(42)
+
+        self.assertTrue(issubclass(warn[0].category, DeprecationWarning))
+
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            GIMarshallingTests.Union(f=42)
+
+        self.assertTrue(issubclass(warn[0].category, DeprecationWarning))
+
+    def test_union(self):
+        union = GIMarshallingTests.Union()
+
+        self.assertTrue(isinstance(union, GIMarshallingTests.Union))
+
+        new_union = union.copy()
+        self.assertTrue(isinstance(new_union, GIMarshallingTests.Union))
+
+        del union
+        del new_union
+
+    def test_union_return(self):
+        union = GIMarshallingTests.union_returnv()
+
+        self.assertTrue(isinstance(union, GIMarshallingTests.Union))
+        self.assertEqual(42, union.long_)
+
+        del union
+
+    def test_union_in(self):
+        union = GIMarshallingTests.Union()
+        union.long_ = 42
+
+        union.inv()
+
+        del union
+
+    def test_union_method(self):
+        union = GIMarshallingTests.Union()
+        union.long_ = 42
+
+        union.method()
+
+        del union
+
+        self.assertRaises(TypeError, GIMarshallingTests.Union.method)
+
+    def test_repr(self):
+        self.assertRegexpMatches(
+            repr(GIMarshallingTests.PointerStruct()),
+            r"<GIMarshallingTests.PointerStruct object at 0x[^\s]+ "
+            r"\(void at 0x[^\s]+\)>")
+
+        self.assertRegexpMatches(
+            repr(GIMarshallingTests.SimpleStruct()),
+            r"<GIMarshallingTests.SimpleStruct object at 0x[^\s]+ "
+            r"\(void at 0x[^\s]+\)>")
+
+        self.assertRegexpMatches(
+            repr(GIMarshallingTests.Union()),
+            r"<GIMarshallingTests.Union object at 0x[^\s]+ "
+            r"\(GIMarshallingTestsUnion at 0x[^\s]+\)>")
+
+        self.assertRegexpMatches(
+            repr(GIMarshallingTests.BoxedStruct()),
+            r"<GIMarshallingTests.BoxedStruct object at 0x[^\s]+ "
+            r"\(GIMarshallingTestsBoxedStruct at 0x[^\s]+\)>")
+
+
+class TestGObject(unittest.TestCase):
+
+    def test_object(self):
+        self.assertTrue(issubclass(GIMarshallingTests.Object, GObject.GObject))
+
+        object_ = GIMarshallingTests.Object()
+        self.assertTrue(isinstance(object_, GIMarshallingTests.Object))
+        self.assertEqual(object_.__grefcount__, 1)
+
+    def test_object_new(self):
+        object_ = GIMarshallingTests.Object.new(42)
+        self.assertTrue(isinstance(object_, GIMarshallingTests.Object))
+        self.assertEqual(object_.__grefcount__, 1)
+
+    def test_object_int(self):
+        object_ = GIMarshallingTests.Object(int=42)
+        self.assertEqual(object_.int_, 42)
+# FIXME: Don't work yet.
+#        object_.int_ = 0
+#        self.assertEqual(object_.int_, 0)
+
+    def test_object_static_method(self):
+        GIMarshallingTests.Object.static_method()
+
+    def test_object_method(self):
+        GIMarshallingTests.Object(int=42).method()
+        self.assertRaises(TypeError, GIMarshallingTests.Object.method, GObject.GObject())
+        self.assertRaises(TypeError, GIMarshallingTests.Object.method)
+
+    def test_sub_object(self):
+        self.assertTrue(issubclass(GIMarshallingTests.SubObject, GIMarshallingTests.Object))
+
+        object_ = GIMarshallingTests.SubObject()
+        self.assertTrue(isinstance(object_, GIMarshallingTests.SubObject))
+
+    def test_sub_object_new(self):
+        self.assertRaises(TypeError, GIMarshallingTests.SubObject.new, 42)
+
+    def test_sub_object_static_method(self):
+        object_ = GIMarshallingTests.SubObject()
+        object_.static_method()
+
+    def test_sub_object_method(self):
+        object_ = GIMarshallingTests.SubObject(int=42)
+        object_.method()
+
+    def test_sub_object_sub_method(self):
+        object_ = GIMarshallingTests.SubObject()
+        object_.sub_method()
+
+    def test_sub_object_overwritten_method(self):
+        object_ = GIMarshallingTests.SubObject()
+        object_.overwritten_method()
+
+        self.assertRaises(TypeError, GIMarshallingTests.SubObject.overwritten_method, GIMarshallingTests.Object())
+
+    def test_sub_object_int(self):
+        object_ = GIMarshallingTests.SubObject()
+        self.assertEqual(object_.int_, 0)
+# FIXME: Don't work yet.
+#        object_.int_ = 42
+#        self.assertEqual(object_.int_, 42)
+
+    def test_object_none_return(self):
+        object_ = GIMarshallingTests.Object.none_return()
+        self.assertTrue(isinstance(object_, GIMarshallingTests.Object))
+        self.assertEqual(object_.__grefcount__, 2)
+
+    def test_object_full_return(self):
+        object_ = GIMarshallingTests.Object.full_return()
+        self.assertTrue(isinstance(object_, GIMarshallingTests.Object))
+        self.assertEqual(object_.__grefcount__, 1)
+
+    def test_object_none_in(self):
+        object_ = GIMarshallingTests.Object(int=42)
+        GIMarshallingTests.Object.none_in(object_)
+        self.assertEqual(object_.__grefcount__, 1)
+
+        object_ = GIMarshallingTests.SubObject(int=42)
+        GIMarshallingTests.Object.none_in(object_)
+
+        object_ = GObject.GObject()
+        self.assertRaises(TypeError, GIMarshallingTests.Object.none_in, object_)
+
+        self.assertRaises(TypeError, GIMarshallingTests.Object.none_in, None)
+
+    def test_object_none_out(self):
+        object_ = GIMarshallingTests.Object.none_out()
+        self.assertTrue(isinstance(object_, GIMarshallingTests.Object))
+        self.assertEqual(object_.__grefcount__, 2)
+
+        new_object = GIMarshallingTests.Object.none_out()
+        self.assertTrue(new_object is object_)
+
+    def test_object_full_out(self):
+        object_ = GIMarshallingTests.Object.full_out()
+        self.assertTrue(isinstance(object_, GIMarshallingTests.Object))
+        self.assertEqual(object_.__grefcount__, 1)
+
+    def test_object_none_inout(self):
+        object_ = GIMarshallingTests.Object(int=42)
+        new_object = GIMarshallingTests.Object.none_inout(object_)
+
+        self.assertTrue(isinstance(new_object, GIMarshallingTests.Object))
+
+        self.assertFalse(object_ is new_object)
+
+        self.assertEqual(object_.__grefcount__, 1)
+        self.assertEqual(new_object.__grefcount__, 2)
+
+        new_new_object = GIMarshallingTests.Object.none_inout(object_)
+        self.assertTrue(new_new_object is new_object)
+
+        GIMarshallingTests.Object.none_inout(GIMarshallingTests.SubObject(int=42))
+
+    def test_object_full_inout(self):
+        # Using gimarshallingtests.c from GI versions > 1.38.0 will show this
+        # test as an "unexpected success" due to reference leak fixes in that file.
+        # TODO: remove the expectedFailure once PyGI relies on GI > 1.38.0.
+        object_ = GIMarshallingTests.Object(int=42)
+        new_object = GIMarshallingTests.Object.full_inout(object_)
+
+        self.assertTrue(isinstance(new_object, GIMarshallingTests.Object))
+
+        self.assertFalse(object_ is new_object)
+
+        self.assertEqual(object_.__grefcount__, 1)
+        self.assertEqual(new_object.__grefcount__, 1)
+
+    def test_repr(self):
+        self.assertRegexpMatches(
+            repr(GIMarshallingTests.Object(int=42)),
+            r"<GIMarshallingTests.Object object at 0x[^\s]+ "
+            r"\(GIMarshallingTestsObject at 0x[^\s]+\)>")
+
+    def test_nongir_repr(self):
+        self.assertRegexpMatches(
+            repr(Gio.File.new_for_path("")),
+            r"<__gi__.GLocalFile object at 0x[^\s]+ "
+            r"\(GLocalFile at 0x[^\s]+\)>")
+
+# FIXME: Doesn't actually return the same object.
+#    def test_object_inout_same(self):
+#        object_ = GIMarshallingTests.Object()
+#        new_object = GIMarshallingTests.object_full_inout(object_)
+#        self.assertTrue(object_ is new_object)
+#        self.assertEqual(object_.__grefcount__, 1)
+
+
+class TestPythonGObject(unittest.TestCase):
+
+    class Object(GIMarshallingTests.Object):
+        return_for_caller_allocated_out_parameter = 'test caller alloc return'
+
+        def __init__(self, int):
+            GIMarshallingTests.Object.__init__(self)
+            self.val = None
+
+        def method(self):
+            # Don't call super, which asserts that self.int == 42.
+            pass
+
+        def do_method_int8_in(self, int8):
+            self.val = int8
+
+        def do_method_int8_out(self):
+            return 42
+
+        def do_method_int8_arg_and_out_caller(self, arg):
+            return arg + 1
+
+        def do_method_int8_arg_and_out_callee(self, arg):
+            return arg + 1
+
+        def do_method_str_arg_out_ret(self, arg):
+            return (arg.upper(), len(arg))
+
+        def do_method_with_default_implementation(self, int8):
+            GIMarshallingTests.Object.do_method_with_default_implementation(self, int8)
+            self.props.int += int8
+
+        def do_vfunc_return_value_only(self):
+            return 4242
+
+        def do_vfunc_one_out_parameter(self):
+            return 42.42
+
+        def do_vfunc_multiple_out_parameters(self):
+            return (42.42, 3.14)
+
+        def do_vfunc_return_value_and_one_out_parameter(self):
+            return (5, 42)
+
+        def do_vfunc_return_value_and_multiple_out_parameters(self):
+            return (5, 42, 99)
+
+        def do_vfunc_caller_allocated_out_parameter(self):
+            return self.return_for_caller_allocated_out_parameter
+
+    class SubObject(GIMarshallingTests.SubObject):
+        def __init__(self, int):
+            GIMarshallingTests.SubObject.__init__(self)
+            self.val = None
+
+        def do_method_with_default_implementation(self, int8):
+            self.val = int8
+
+        def do_vfunc_return_value_only(self):
+            return 2121
+
+    class Interface3Impl(GObject.Object, GIMarshallingTests.Interface3):
+        def __init__(self):
+            GObject.Object.__init__(self)
+            self.variants = None
+            self.n_variants = None
+
+        def do_test_variant_array_in(self, variants, n_variants):
+            self.variants = variants
+            self.n_variants = n_variants
+
+    class ErrorObject(GIMarshallingTests.Object):
+        def do_vfunc_return_value_only(self):
+            raise ValueError('Return value should be 0')
+
+    def test_object(self):
+        self.assertTrue(issubclass(self.Object, GIMarshallingTests.Object))
+
+        object_ = self.Object(int=42)
+        self.assertTrue(isinstance(object_, self.Object))
+
+    @unittest.skipUnless(hasattr(GIMarshallingTests.Object, 'new_fail'),
+                         'Requires newer version of GI')
+    def test_object_fail(self):
+        with self.assertRaises(GLib.Error):
+            GIMarshallingTests.Object.new_fail(int_=42)
+
+    def test_object_method(self):
+        self.Object(int=0).method()
+
+    def test_object_vfuncs(self):
+        object_ = self.Object(int=42)
+        object_.method_int8_in(84)
+        self.assertEqual(object_.val, 84)
+        self.assertEqual(object_.method_int8_out(), 42)
+
+        # can be dropped when bumping g-i dependencies to >= 1.35.2
+        if hasattr(object_, 'method_int8_arg_and_out_caller'):
+            self.assertEqual(object_.method_int8_arg_and_out_caller(42), 43)
+            self.assertEqual(object_.method_int8_arg_and_out_callee(42), 43)
+            self.assertEqual(object_.method_str_arg_out_ret('hello'), ('HELLO', 5))
+
+        object_.method_with_default_implementation(42)
+        self.assertEqual(object_.props.int, 84)
+
+        self.assertEqual(object_.vfunc_return_value_only(), 4242)
+        self.assertAlmostEqual(object_.vfunc_one_out_parameter(), 42.42, places=5)
+
+        (a, b) = object_.vfunc_multiple_out_parameters()
+        self.assertAlmostEqual(a, 42.42, places=5)
+        self.assertAlmostEqual(b, 3.14, places=5)
+
+        self.assertEqual(object_.vfunc_return_value_and_one_out_parameter(), (5, 42))
+        self.assertEqual(object_.vfunc_return_value_and_multiple_out_parameters(), (5, 42, 99))
+
+        self.assertEqual(object_.vfunc_caller_allocated_out_parameter(),
+                         object_.return_for_caller_allocated_out_parameter)
+
+        class ObjectWithoutVFunc(GIMarshallingTests.Object):
+            def __init__(self, int):
+                GIMarshallingTests.Object.__init__(self)
+
+        object_ = ObjectWithoutVFunc(int=42)
+        object_.method_with_default_implementation(84)
+        self.assertEqual(object_.props.int, 84)
+
+    @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount")
+    def test_vfunc_return_ref_count(self):
+        obj = self.Object(int=42)
+        ref_count = sys.getrefcount(obj.return_for_caller_allocated_out_parameter)
+        ret = obj.vfunc_caller_allocated_out_parameter()
+        gc.collect()
+
+        # Make sure the return and what the vfunc returned
+        # are equal but not the same object.
+        self.assertEqual(ret, obj.return_for_caller_allocated_out_parameter)
+        self.assertFalse(ret is obj.return_for_caller_allocated_out_parameter)
+        self.assertEqual(sys.getrefcount(obj.return_for_caller_allocated_out_parameter),
+                         ref_count)
+
+    def test_vfunc_return_no_ref_count(self):
+        obj = self.Object(int=42)
+        ret = obj.vfunc_caller_allocated_out_parameter()
+        self.assertEqual(ret, obj.return_for_caller_allocated_out_parameter)
+        self.assertFalse(ret is obj.return_for_caller_allocated_out_parameter)
+
+    def test_subobject_parent_vfunc(self):
+        object_ = self.SubObject(int=81)
+        object_.method_with_default_implementation(87)
+        self.assertEqual(object_.val, 87)
+
+    def test_subobject_child_vfunc(self):
+        object_ = self.SubObject(int=1)
+        self.assertEqual(object_.vfunc_return_value_only(), 2121)
+
+    def test_subobject_non_vfunc_do_method(self):
+        class PythonObjectWithNonVFuncDoMethod(object):
+            def do_not_a_vfunc(self):
+                return 5
+
+        class ObjectOverrideNonVFuncDoMethod(GIMarshallingTests.Object, PythonObjectWithNonVFuncDoMethod):
+            def do_not_a_vfunc(self):
+                value = super(ObjectOverrideNonVFuncDoMethod, self).do_not_a_vfunc()
+                return 13 + value
+
+        object_ = ObjectOverrideNonVFuncDoMethod()
+        self.assertEqual(18, object_.do_not_a_vfunc())
+
+    def test_native_function_not_set_in_subclass_dict(self):
+        # Previously, GI was setting virtual functions on the class as well
+        # as any *native* class that subclasses it. Here we check that it is only
+        # set on the class that the method is originally from.
+        self.assertTrue('do_method_with_default_implementation' in GIMarshallingTests.Object.__dict__)
+        self.assertTrue('do_method_with_default_implementation' not in GIMarshallingTests.SubObject.__dict__)
+
+    def test_subobject_with_interface_and_non_vfunc_do_method(self):
+        # There was a bug for searching for vfuncs in interfaces. It was
+        # triggered by having a do_* method that wasn't overriding
+        # a native vfunc, as well as inheriting from an interface.
+        class GObjectSubclassWithInterface(GObject.GObject, GIMarshallingTests.Interface):
+            def do_method_not_a_vfunc(self):
+                pass
+
+    def test_subsubobject(self):
+        class SubSubSubObject(GIMarshallingTests.SubSubObject):
+            def do_method_deep_hierarchy(self, num):
+                self.props.int = num * 2
+
+        sub_sub_sub_object = SubSubSubObject()
+        GIMarshallingTests.SubSubObject.do_method_deep_hierarchy(sub_sub_sub_object, 5)
+        self.assertEqual(sub_sub_sub_object.props.int, 5)
+
+    def test_interface3impl(self):
+        iface3 = self.Interface3Impl()
+        variants = [GLib.Variant('i', 27), GLib.Variant('s', 'Hello')]
+        iface3.test_variant_array_in(variants)
+        self.assertEqual(iface3.n_variants, 2)
+        self.assertEqual(iface3.variants[0].unpack(), 27)
+        self.assertEqual(iface3.variants[1].unpack(), 'Hello')
+
+    def test_python_subsubobject_vfunc(self):
+        class PySubObject(GIMarshallingTests.Object):
+            def __init__(self):
+                GIMarshallingTests.Object.__init__(self)
+                self.sub_method_int8_called = 0
+
+            def do_method_int8_in(self, int8):
+                self.sub_method_int8_called += 1
+
+        class PySubSubObject(PySubObject):
+            def __init__(self):
+                PySubObject.__init__(self)
+                self.subsub_method_int8_called = 0
+
+            def do_method_int8_in(self, int8):
+                self.subsub_method_int8_called += 1
+
+        so = PySubObject()
+        so.method_int8_in(1)
+        self.assertEqual(so.sub_method_int8_called, 1)
+
+        # it should call the method on the SubSub object only
+        sso = PySubSubObject()
+        sso.method_int8_in(1)
+        self.assertEqual(sso.subsub_method_int8_called, 1)
+        self.assertEqual(sso.sub_method_int8_called, 0)
+
+    def test_callback_in_vfunc(self):
+        class SubObject(GIMarshallingTests.Object):
+            def __init__(self):
+                GObject.GObject.__init__(self)
+                self.worked = False
+
+            def do_vfunc_with_callback(self, callback):
+                self.worked = callback(42) == 42
+
+        _object = SubObject()
+        _object.call_vfunc_with_callback()
+        self.assertTrue(_object.worked)
+        _object.worked = False
+        _object.call_vfunc_with_callback()
+        self.assertTrue(_object.worked)
+
+    def test_exception_in_vfunc_return_value(self):
+        obj = self.ErrorObject()
+        with capture_exceptions() as exc:
+            self.assertEqual(obj.vfunc_return_value_only(), 0)
+        self.assertEqual(len(exc), 1)
+        self.assertEqual(exc[0].type, ValueError)
+
+    @unittest.skipUnless(hasattr(GIMarshallingTests, 'callback_owned_boxed'),
+                         'requires newer version of GI')
+    def test_callback_owned_box(self):
+        def callback(box, data):
+            self.box = box
+
+        def nop_callback(box, data):
+            pass
+
+        GIMarshallingTests.callback_owned_boxed(callback, None)
+        GIMarshallingTests.callback_owned_boxed(nop_callback, None)
+        self.assertEqual(self.box.long_, 1)
+
+
+class TestMultiOutputArgs(unittest.TestCase):
+
+    def test_int_out_out(self):
+        self.assertEqual((6, 7), GIMarshallingTests.int_out_out())
+
+    def test_int_return_out(self):
+        self.assertEqual((6, 7), GIMarshallingTests.int_return_out())
+
+
+# Interface
+
+class TestInterfaces(unittest.TestCase):
+
+    class TestInterfaceImpl(GObject.GObject, GIMarshallingTests.Interface):
+        def __init__(self):
+            GObject.GObject.__init__(self)
+            self.val = None
+
+        def do_test_int8_in(self, int8):
+            self.val = int8
+
+    def setUp(self):
+        self.instance = self.TestInterfaceImpl()
+
+    def test_iface_impl(self):
+        instance = GIMarshallingTests.InterfaceImpl()
+        assert instance.get_as_interface() is instance
+        instance.test_int8_in(42)
+
+    def test_wrapper(self):
+        self.assertTrue(issubclass(GIMarshallingTests.Interface, GObject.GInterface))
+        self.assertEqual(GIMarshallingTests.Interface.__gtype__.name, 'GIMarshallingTestsInterface')
+        self.assertRaises(NotImplementedError, GIMarshallingTests.Interface)
+
+    def test_implementation(self):
+        self.assertTrue(issubclass(self.TestInterfaceImpl, GIMarshallingTests.Interface))
+        self.assertTrue(isinstance(self.instance, GIMarshallingTests.Interface))
+
+    def test_int8_int(self):
+        GIMarshallingTests.test_interface_test_int8_in(self.instance, 42)
+        self.assertEqual(self.instance.val, 42)
+
+    def test_subclass(self):
+        class TestInterfaceImplA(self.TestInterfaceImpl):
+            pass
+
+        class TestInterfaceImplB(TestInterfaceImplA):
+            pass
+
+        instance = TestInterfaceImplA()
+        GIMarshallingTests.test_interface_test_int8_in(instance, 42)
+        self.assertEqual(instance.val, 42)
+
+    def test_subclass_override(self):
+        class TestInterfaceImplD(TestInterfaces.TestInterfaceImpl):
+            val2 = None
+
+            def do_test_int8_in(self, int8):
+                self.val2 = int8
+
+        instance = TestInterfaceImplD()
+        self.assertEqual(instance.val, None)
+        self.assertEqual(instance.val2, None)
+
+        GIMarshallingTests.test_interface_test_int8_in(instance, 42)
+        self.assertEqual(instance.val, None)
+        self.assertEqual(instance.val2, 42)
+
+    def test_type_mismatch(self):
+        obj = GIMarshallingTests.Object()
+
+        # wrong type for first argument: interface
+        enum = Gio.File.new_for_path('.').enumerate_children(
+            '', Gio.FileQueryInfoFlags.NONE, None)
+        try:
+            enum.next_file(obj)
+            self.fail('call with wrong type argument unexpectedly succeeded')
+        except TypeError as e:
+            # should have argument name
+            self.assertTrue('cancellable' in str(e), e)
+            # should have expected type
+            self.assertTrue('xpected Gio.Cancellable' in str(e), e)
+            # should have actual type
+            self.assertTrue('GIMarshallingTests.Object' in str(e), e)
+
+        # wrong type for self argument: interface
+        try:
+            Gio.FileEnumerator.next_file(obj, None)
+            self.fail('call with wrong type argument unexpectedly succeeded')
+        except TypeError as e:
+            # should have argument name
+            self.assertTrue('self' in str(e), e)
+            # should have expected type
+            self.assertTrue('xpected Gio.FileEnumerator' in str(e), e)
+            # should have actual type
+            self.assertTrue('GIMarshallingTests.Object' in str(e), e)
+
+        # wrong type for first argument: GObject
+        var = GLib.Variant('s', 'mystring')
+        action = Gio.SimpleAction.new('foo', var.get_type())
+        try:
+            action.activate(obj)
+            self.fail('call with wrong type argument unexpectedly succeeded')
+        except TypeError as e:
+            # should have argument name
+            self.assertTrue('parameter' in str(e), e)
+            # should have expected type
+            self.assertTrue('xpected GLib.Variant' in str(e), e)
+            # should have actual type
+            self.assertTrue('GIMarshallingTests.Object' in str(e), e)
+
+        # wrong type for self argument: GObject
+        try:
+            Gio.SimpleAction.activate(obj, obj)
+            self.fail('call with wrong type argument unexpectedly succeeded')
+        except TypeError as e:
+            # should have argument name
+            self.assertTrue('self' in str(e), e)
+            # should have expected type
+            self.assertTrue('xpected Gio.Action' in str(e), e)
+            # should have actual type
+            self.assertTrue('GIMarshallingTests.Object' in str(e), e)
+
+
+class TestMRO(unittest.TestCase):
+    def test_mro(self):
+        # check that our own MRO calculation matches what we would expect
+        # from Python's own C3 calculations
+        class A(object):
+            pass
+
+        class B(A):
+            pass
+
+        class C(A):
+            pass
+
+        class D(B, C):
+            pass
+
+        class E(D, GIMarshallingTests.Object):
+            pass
+
+        expected = (E, D, B, C, A, GIMarshallingTests.Object,
+                    GObject.Object, GObject.Object.__base__, gi._gi.GObject,
+                    object)
+        self.assertEqual(expected, E.__mro__)
+
+    def test_interface_collision(self):
+        # there was a problem with Python bailing out because of
+        # http://en.wikipedia.org/wiki/Diamond_problem with interfaces,
+        # which shouldn't really be a problem.
+
+        class TestInterfaceImpl(GObject.GObject, GIMarshallingTests.Interface):
+            pass
+
+        class TestInterfaceImpl2(GIMarshallingTests.Interface,
+                                 TestInterfaceImpl):
+            pass
+
+        class TestInterfaceImpl3(TestInterfaceImpl,
+                                 GIMarshallingTests.Interface2):
+            pass
+
+    def test_old_style_mixin(self):
+        # Note: Old style classes don't exist in Python 3
+        class Mixin:
+            pass
+
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+
+            # Dynamically create a new gi based class with an old
+            # style mixin.
+            type('GIWithOldStyleMixin', (GIMarshallingTests.Object, Mixin), {})
+
+            if PY2:
+                self.assertTrue(issubclass(warn[0].category, RuntimeWarning))
+            else:
+                self.assertEqual(len(warn), 0)
+
+
+class TestInterfaceClash(unittest.TestCase):
+
+    def test_clash(self):
+        def create_clash():
+            class TestClash(GObject.GObject, GIMarshallingTests.Interface, GIMarshallingTests.Interface2):
+                def do_test_int8_in(self, int8):
+                    pass
+            TestClash()
+
+        self.assertRaises(TypeError, create_clash)
+
+
+class TestOverrides(unittest.TestCase):
+
+    def test_constant(self):
+        self.assertEqual(GIMarshallingTests.OVERRIDES_CONSTANT, 7)
+
+    def test_struct(self):
+        # Test that the constructor has been overridden.
+        struct = GIMarshallingTests.OverridesStruct(42)
+
+        self.assertTrue(isinstance(struct, GIMarshallingTests.OverridesStruct))
+
+        # Test that the method has been overridden.
+        self.assertEqual(6, struct.method())
+
+        del struct
+
+        # Test that the overrides wrapper has been registered.
+        struct = GIMarshallingTests.overrides_struct_returnv()
+
+        self.assertTrue(isinstance(struct, GIMarshallingTests.OverridesStruct))
+
+        del struct
+
+    def test_object(self):
+        # Test that the constructor has been overridden.
+        object_ = GIMarshallingTests.OverridesObject(42)
+
+        self.assertTrue(isinstance(object_, GIMarshallingTests.OverridesObject))
+
+        # Test that the alternate constructor has been overridden.
+        object_ = GIMarshallingTests.OverridesObject.new(42)
+
+        self.assertTrue(isinstance(object_, GIMarshallingTests.OverridesObject))
+
+        # Test that the method has been overridden.
+        self.assertEqual(6, object_.method())
+
+        # Test that the overrides wrapper has been registered.
+        object_ = GIMarshallingTests.OverridesObject.returnv()
+
+        self.assertTrue(isinstance(object_, GIMarshallingTests.OverridesObject))
+
+    def test_module_name(self):
+        # overridden types
+        self.assertEqual(GIMarshallingTests.OverridesStruct.__module__, 'gi.overrides.GIMarshallingTests')
+        self.assertEqual(GIMarshallingTests.OverridesObject.__module__, 'gi.overrides.GIMarshallingTests')
+        self.assertEqual(GObject.Object.__module__, 'gi.overrides.GObject')
+
+        # not overridden
+        self.assertEqual(GIMarshallingTests.SubObject.__module__, 'gi.repository.GIMarshallingTests')
+        self.assertEqual(GObject.InitiallyUnowned.__module__, 'gi.repository.GObject')
+
+
+class TestDir(unittest.TestCase):
+    def test_members_list(self):
+        list = dir(GIMarshallingTests)
+        self.assertTrue('OverridesStruct' in list)
+        self.assertTrue('BoxedStruct' in list)
+        self.assertTrue('OVERRIDES_CONSTANT' in list)
+        self.assertTrue('GEnum' in list)
+        self.assertTrue('int32_return_max' in list)
+
+    def test_modules_list(self):
+        import gi.repository
+        list = dir(gi.repository)
+        self.assertTrue('GIMarshallingTests' in list)
+
+        # FIXME: test to see if a module which was not imported is in the list
+        #        we should be listing every typelib we find, not just the ones
+        #        which are imported
+        #
+        #        to test this I recommend we compile a fake module which
+        #        our tests would never import and check to see if it is
+        #        in the list:
+        #
+        # self.assertTrue('DoNotImportDummyTests' in list)
+
+
+class TestParamSpec(unittest.TestCase):
+    # https://bugzilla.gnome.org/show_bug.cgi?id=682355
+    @unittest.expectedFailure
+    def test_param_spec_in_bool(self):
+        ps = GObject.param_spec_boolean('mybool', 'test-bool', 'boolblurb',
+                                        True, GObject.ParamFlags.READABLE)
+        GIMarshallingTests.param_spec_in_bool(ps)
+
+    def test_param_spec_return(self):
+        obj = GIMarshallingTests.param_spec_return()
+        self.assertEqual(obj.name, 'test-param')
+        self.assertEqual(obj.nick, 'test')
+        self.assertEqual(obj.value_type, GObject.TYPE_STRING)
+
+    def test_param_spec_out(self):
+        obj = GIMarshallingTests.param_spec_out()
+        self.assertEqual(obj.name, 'test-param')
+        self.assertEqual(obj.nick, 'test')
+        self.assertEqual(obj.value_type, GObject.TYPE_STRING)
+
+
+class TestKeywordArgs(unittest.TestCase):
+
+    def test_calling(self):
+        kw_func = GIMarshallingTests.int_three_in_three_out
+
+        self.assertEqual(kw_func(1, 2, 3), (1, 2, 3))
+        self.assertEqual(kw_func(**{'a': 4, 'b': 5, 'c': 6}), (4, 5, 6))
+        self.assertEqual(kw_func(1, **{'b': 7, 'c': 8}), (1, 7, 8))
+        self.assertEqual(kw_func(1, 7, **{'c': 8}), (1, 7, 8))
+        self.assertEqual(kw_func(1, c=8, **{'b': 7}), (1, 7, 8))
+        self.assertEqual(kw_func(2, c=4, b=3), (2, 3, 4))
+        self.assertEqual(kw_func(a=2, c=4, b=3), (2, 3, 4))
+
+    def assertRaisesMessage(self, exception, message, func, *args, **kwargs):
+        try:
+            func(*args, **kwargs)
+        except exception:
+            (e_type, e) = sys.exc_info()[:2]
+            if message is not None:
+                self.assertEqual(str(e), message)
+        except:
+            raise
+        else:
+            msg = "%s() did not raise %s" % (func.__name__, exception.__name__)
+            raise AssertionError(msg)
+
+    def test_type_errors(self):
+        # test too few args
+        self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 arguments (0 given)",
+                                 GIMarshallingTests.int_three_in_three_out)
+        self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 arguments (1 given)",
+                                 GIMarshallingTests.int_three_in_three_out, 1)
+        self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 arguments (0 given)",
+                                 GIMarshallingTests.int_three_in_three_out, *())
+        self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 arguments (0 given)",
+                                 GIMarshallingTests.int_three_in_three_out, *(), **{})
+        self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 non-keyword arguments (0 given)",
+                                 GIMarshallingTests.int_three_in_three_out, *(), **{'c': 4})
+
+        # test too many args
+        self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 arguments (4 given)",
+                                 GIMarshallingTests.int_three_in_three_out, *(1, 2, 3, 4))
+        self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() takes exactly 3 non-keyword arguments (4 given)",
+                                 GIMarshallingTests.int_three_in_three_out, *(1, 2, 3, 4), c=6)
+
+        # test too many keyword args
+        self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() got multiple values for keyword argument 'a'",
+                                 GIMarshallingTests.int_three_in_three_out, 1, 2, 3, **{'a': 4, 'b': 5})
+        self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() got an unexpected keyword argument 'd'",
+                                 GIMarshallingTests.int_three_in_three_out, d=4)
+        self.assertRaisesMessage(TypeError, "GIMarshallingTests.int_three_in_three_out() got an unexpected keyword argument 'e'",
+                                 GIMarshallingTests.int_three_in_three_out, **{'e': 2})
+
+    def test_kwargs_are_not_modified(self):
+        d = {'b': 2}
+        d2 = d.copy()
+        GIMarshallingTests.int_three_in_three_out(1, c=4, **d)
+        self.assertEqual(d, d2)
+
+    @unittest.skipUnless(hasattr(GIMarshallingTests, 'int_one_in_utf8_two_in_one_allows_none'),
+                         'Requires newer GIMarshallingTests')
+    def test_allow_none_as_default(self):
+        GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3', '4')
+        GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, '3')
+        GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2)
+        GIMarshallingTests.int_two_in_utf8_two_in_with_allow_none(1, 2, d='4')
+
+        GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2])
+        GIMarshallingTests.array_in_utf8_two_in_out_of_order('1', [-1, 0, 1, 2], '2')
+        self.assertRaises(TypeError,
+                          GIMarshallingTests.array_in_utf8_two_in_out_of_order,
+                          [-1, 0, 1, 2], a='1')
+        self.assertRaises(TypeError,
+                          GIMarshallingTests.array_in_utf8_two_in_out_of_order,
+                          [-1, 0, 1, 2])
+
+        GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1', '2')
+        GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], '1')
+        GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2])
+        GIMarshallingTests.array_in_utf8_two_in([-1, 0, 1, 2], b='2')
+
+        GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none(1, '2', '3')
+        self.assertRaises(TypeError,
+                          GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none,
+                          1, '3')
+        self.assertRaises(TypeError,
+                          GIMarshallingTests.int_one_in_utf8_two_in_one_allows_none,
+                          1, c='3')
+
+
+class TestKeywords(unittest.TestCase):
+    def test_method(self):
+        # g_variant_print()
+        v = GLib.Variant('i', 1)
+        self.assertEqual(v.print_(False), '1')
+
+    def test_function(self):
+        # g_thread_yield()
+        self.assertEqual(GLib.Thread.yield_(), None)
+
+    def test_struct_method(self):
+        # g_timer_continue()
+        # we cannot currently instantiate GLib.Timer objects, so just ensure
+        # the method exists
+        self.assertTrue(callable(GLib.Timer.continue_))
+
+    def test_uppercase(self):
+        self.assertEqual(GLib.IOCondition.IN.value_nicks, ['in'])
+
+
+class TestModule(unittest.TestCase):
+    def test_path(self):
+        path = GIMarshallingTests.__path__
+        assert isinstance(path, list)
+        assert len(path) == 1
+        assert path[0].endswith('GIMarshallingTests-1.0.typelib')
+
+    def test_str(self):
+        self.assertTrue("'GIMarshallingTests' from '" in str(GIMarshallingTests),
+                        str(GIMarshallingTests))
+
+    def test_dir(self):
+        _dir = dir(GIMarshallingTests)
+        self.assertGreater(len(_dir), 10)
+
+        self.assertTrue('SimpleStruct' in _dir)
+        self.assertTrue('Interface2' in _dir)
+        self.assertTrue('CONSTANT_GERROR_CODE' in _dir)
+        self.assertTrue('array_zero_terminated_inout' in _dir)
+
+        # assert that dir() does not contain garbage
+        for item_name in _dir:
+            item = getattr(GIMarshallingTests, item_name)
+            self.assertTrue(hasattr(item, '__class__'))
+
+    def test_help(self):
+        with capture_output() as (stdout, stderr):
+            help(GIMarshallingTests)
+        output = stdout.getvalue()
+
+        self.assertTrue('SimpleStruct' in output, output)
+        self.assertTrue('Interface2' in output, output)
+        self.assertTrue('method_array_inout' in output, output)
+
+
+class TestProjectVersion(unittest.TestCase):
+    def test_version_str(self):
+        self.assertGreater(gi.__version__, "3.")
+
+    def test_version_info(self):
+        self.assertEqual(len(gi.version_info), 3)
+        self.assertGreaterEqual(gi.version_info, (3, 3, 5))
+
+    def test_check_version(self):
+        self.assertRaises(ValueError, gi.check_version, (99, 0, 0))
+        self.assertRaises(ValueError, gi.check_version, "99.0.0")
+        gi.check_version((3, 3, 5))
+        gi.check_version("3.3.5")
+
+
+class TestGIWarning(unittest.TestCase):
+
+    def test_warning(self):
+        ignored_by_default = (DeprecationWarning, PendingDeprecationWarning,
+                              ImportWarning)
+
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            warnings.warn("test", PyGIWarning)
+            self.assertTrue(issubclass(warn[0].category, Warning))
+            # We don't want PyGIWarning get ignored by default
+            self.assertFalse(issubclass(warn[0].category, ignored_by_default))
+
+
+class TestDeprecation(unittest.TestCase):
+    def test_method(self):
+        d = GLib.Date.new()
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            d.set_time(1)
+            self.assertTrue(issubclass(warn[0].category, DeprecationWarning))
+            self.assertEqual(str(warn[0].message), "GLib.Date.set_time is deprecated")
+
+    def test_function(self):
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            GLib.strcasecmp("foo", "bar")
+            self.assertTrue(issubclass(warn[0].category, DeprecationWarning))
+            self.assertEqual(str(warn[0].message), "GLib.strcasecmp is deprecated")
+
+    def test_deprecated_attribute_compat(self):
+        # test if the deprecation descriptor behaves like an instance attribute
+
+        # save the descriptor
+        desc = type(GLib).__dict__["IO_STATUS_ERROR"]
+
+        # the descriptor raises AttributeError for itself
+        self.assertFalse(hasattr(type(GLib), "IO_STATUS_ERROR"))
+
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', PyGIDeprecationWarning)
+            self.assertTrue(hasattr(GLib, "IO_STATUS_ERROR"))
+
+        try:
+            # check if replacing works
+            GLib.IO_STATUS_ERROR = "foo"
+            self.assertEqual(GLib.IO_STATUS_ERROR, "foo")
+        finally:
+            # restore descriptor
+            try:
+                del GLib.IO_STATUS_ERROR
+            except AttributeError:
+                pass
+            setattr(type(GLib), "IO_STATUS_ERROR", desc)
+
+        try:
+            # check if deleting works
+            del GLib.IO_STATUS_ERROR
+            self.assertFalse(hasattr(GLib, "IO_STATUS_ERROR"))
+        finally:
+            # restore descriptor
+            try:
+                del GLib.IO_STATUS_ERROR
+            except AttributeError:
+                pass
+            setattr(type(GLib), "IO_STATUS_ERROR", desc)
+
+    def test_deprecated_attribute_warning(self):
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            self.assertEqual(GLib.IO_STATUS_ERROR, GLib.IOStatus.ERROR)
+            GLib.IO_STATUS_ERROR
+            GLib.IO_STATUS_ERROR
+            self.assertEqual(len(warn), 3)
+            self.assertTrue(
+                issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(
+                str(warn[0].message),
+                ".*GLib.IO_STATUS_ERROR.*GLib.IOStatus.ERROR.*")
+
+    def test_deprecated_attribute_warning_coverage(self):
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            GObject.markup_escape_text
+            GObject.PRIORITY_DEFAULT
+            GObject.GError
+            GObject.PARAM_CONSTRUCT
+            GObject.SIGNAL_ACTION
+            GObject.property
+            GObject.IO_STATUS_ERROR
+            GObject.G_MAXUINT64
+            GLib.IO_STATUS_ERROR
+            GLib.SPAWN_SEARCH_PATH
+            GLib.OPTION_FLAG_HIDDEN
+            GLib.IO_FLAG_IS_WRITEABLE
+            GLib.IO_FLAG_NONBLOCK
+            GLib.USER_DIRECTORY_DESKTOP
+            GLib.OPTION_ERROR_BAD_VALUE
+            GLib.glib_version
+            GLib.pyglib_version
+            self.assertEqual(len(warn), 17)
+
+    def test_deprecated_init_no_keywords(self):
+        def init(self, **kwargs):
+            self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3})
+
+        fn = gi.overrides.deprecated_init(init, arg_names=('a', 'b', 'c'))
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            fn(self, 1, 2, 3)
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*keyword.*a, b, c.*')
+
+    def test_deprecated_init_no_keywords_out_of_order(self):
+        def init(self, **kwargs):
+            self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3})
+
+        fn = gi.overrides.deprecated_init(init, arg_names=('b', 'a', 'c'))
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            fn(self, 2, 1, 3)
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*keyword.*b, a, c.*')
+
+    def test_deprecated_init_ignored_keyword(self):
+        def init(self, **kwargs):
+            self.assertDictEqual(kwargs, {'a': 1, 'c': 3})
+
+        fn = gi.overrides.deprecated_init(init,
+                                          arg_names=('a', 'b', 'c'),
+                                          ignore=('b',))
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            fn(self, 1, 2, 3)
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*keyword.*a, b, c.*')
+
+    def test_deprecated_init_with_aliases(self):
+        def init(self, **kwargs):
+            self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3})
+
+        fn = gi.overrides.deprecated_init(init,
+                                          arg_names=('a', 'b', 'c'),
+                                          deprecated_aliases={'b': 'bb', 'c': 'cc'})
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+
+            fn(self, a=1, bb=2, cc=3)
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*keyword.*"bb, cc".*deprecated.*"b, c" respectively')
+
+    def test_deprecated_init_with_defaults(self):
+        def init(self, **kwargs):
+            self.assertDictEqual(kwargs, {'a': 1, 'b': 2, 'c': 3})
+
+        fn = gi.overrides.deprecated_init(init,
+                                          arg_names=('a', 'b', 'c'),
+                                          deprecated_defaults={'b': 2, 'c': 3})
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            fn(self, a=1)
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*relying on deprecated non-standard defaults.*'
+                                     'explicitly use: b=2, c=3')
diff --git a/tests/test_gio.py b/tests/test_gio.py
new file mode 100644 (file)
index 0000000..b5fcdf3
--- /dev/null
@@ -0,0 +1,344 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import os
+import unittest
+import warnings
+
+import pytest
+
+import gi.overrides
+from gi import PyGIWarning
+from gi.repository import GLib, Gio
+
+from .helper import ignore_gi_deprecation_warnings
+
+
+class TestGio(unittest.TestCase):
+    def test_file_enumerator(self):
+        self.assertEqual(Gio.FileEnumerator, gi.overrides.Gio.FileEnumerator)
+        f = Gio.file_new_for_path("./")
+
+        iter_info = []
+        for info in f.enumerate_children("standard::*", 0, None):
+            iter_info.append(info.get_name())
+
+        next_info = []
+        enumerator = f.enumerate_children("standard::*", 0, None)
+        while True:
+            info = enumerator.next_file(None)
+            if info is None:
+                break
+            next_info.append(info.get_name())
+
+        self.assertEqual(iter_info, next_info)
+
+    def test_menu_item(self):
+        menu = Gio.Menu()
+        item = Gio.MenuItem()
+        item.set_attribute([("label", "s", "Test"),
+                            ("action", "s", "app.test")])
+        menu.append_item(item)
+        value = menu.get_item_attribute_value(0, "label", GLib.VariantType.new("s"))
+        self.assertEqual("Test", value.unpack())
+        value = menu.get_item_attribute_value(0, "action", GLib.VariantType.new("s"))
+        self.assertEqual("app.test", value.unpack())
+
+    def test_volume_monitor_warning(self):
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            Gio.VolumeMonitor()
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*Gio\\.VolumeMonitor\\.get\\(\\).*')
+
+
+class TestGSettings(unittest.TestCase):
+    def setUp(self):
+        self.settings = Gio.Settings.new('org.gnome.test')
+        # we change the values in the tests, so set them to predictable start
+        # value
+        self.settings.reset('test-string')
+        self.settings.reset('test-array')
+        self.settings.reset('test-boolean')
+        self.settings.reset('test-enum')
+
+    def test_iter(self):
+        assert list(self.settings) == [
+            'test-tuple', 'test-array', 'test-boolean', 'test-string',
+            'test-enum', 'test-range']
+
+    def test_get_set(self):
+        for key in self.settings:
+            old_value = self.settings[key]
+            self.settings[key] = old_value
+            assert self.settings[key] == old_value
+
+    def test_native(self):
+        self.assertTrue('test-array' in self.settings.list_keys())
+
+        # get various types
+        v = self.settings.get_value('test-boolean')
+        self.assertEqual(v.get_boolean(), True)
+        self.assertEqual(self.settings.get_boolean('test-boolean'), True)
+
+        v = self.settings.get_value('test-string')
+        self.assertEqual(v.get_string(), 'Hello')
+        self.assertEqual(self.settings.get_string('test-string'), 'Hello')
+
+        v = self.settings.get_value('test-array')
+        self.assertEqual(v.unpack(), [1, 2])
+
+        v = self.settings.get_value('test-tuple')
+        self.assertEqual(v.unpack(), (1, 2))
+
+        v = self.settings.get_value('test-range')
+        assert v.unpack() == 123
+
+        # set a value
+        self.settings.set_string('test-string', 'World')
+        self.assertEqual(self.settings.get_string('test-string'), 'World')
+
+        self.settings.set_value('test-string', GLib.Variant('s', 'Goodbye'))
+        self.assertEqual(self.settings.get_string('test-string'), 'Goodbye')
+
+    def test_constructor(self):
+        # default constructor uses path from schema
+        self.assertEqual(self.settings.get_property('path'), '/tests/')
+
+        # optional constructor arguments
+        with_path = Gio.Settings.new_with_path('org.gnome.nopathtest', '/mypath/')
+        self.assertEqual(with_path.get_property('path'), '/mypath/')
+        self.assertEqual(with_path['np-int'], 42)
+
+    def test_dictionary_api(self):
+        self.assertEqual(len(self.settings), 6)
+        self.assertTrue('test-array' in self.settings)
+        self.assertTrue('test-array' in self.settings.keys())
+        self.assertFalse('nonexisting' in self.settings)
+        self.assertFalse(4 in self.settings)
+        self.assertEqual(bool(self.settings), True)
+
+    def test_get(self):
+        self.assertEqual(self.settings['test-boolean'], True)
+        self.assertEqual(self.settings['test-string'], 'Hello')
+        self.assertEqual(self.settings['test-enum'], 'banana')
+        self.assertEqual(self.settings['test-array'], [1, 2])
+        self.assertEqual(self.settings['test-tuple'], (1, 2))
+
+        self.assertRaises(KeyError, self.settings.__getitem__, 'unknown')
+        self.assertRaises(KeyError, self.settings.__getitem__, 2)
+
+    def test_set(self):
+        self.settings['test-boolean'] = False
+        self.assertEqual(self.settings['test-boolean'], False)
+        self.settings['test-string'] = 'Goodbye'
+        self.assertEqual(self.settings['test-string'], 'Goodbye')
+        self.settings['test-array'] = [3, 4, 5]
+        self.assertEqual(self.settings['test-array'], [3, 4, 5])
+        self.settings['test-enum'] = 'pear'
+        self.assertEqual(self.settings['test-enum'], 'pear')
+
+        self.assertRaises(TypeError, self.settings.__setitem__, 'test-string', 1)
+        self.assertRaises(ValueError, self.settings.__setitem__, 'test-enum', 'plum')
+        self.assertRaises(KeyError, self.settings.__setitem__, 'unknown', 'moo')
+
+    def test_set_range(self):
+        self.settings['test-range'] = 7
+        assert self.settings['test-range'] == 7
+        self.settings['test-range'] = 65535
+        assert self.settings['test-range'] == 65535
+
+        with pytest.raises(ValueError, match=".*7 - 65535.*"):
+            self.settings['test-range'] = 7 - 1
+
+        with pytest.raises(ValueError, match=".*7 - 65535.*"):
+            self.settings['test-range'] = 65535 + 1
+
+    def test_empty(self):
+        empty = Gio.Settings.new_with_path('org.gnome.empty', '/tests/')
+        self.assertEqual(len(empty), 0)
+        self.assertEqual(bool(empty), True)
+        self.assertEqual(empty.keys(), [])
+
+    @ignore_gi_deprecation_warnings
+    def test_change_event(self):
+        changed_log = []
+        change_event_log = []
+
+        def on_changed(settings, key):
+            changed_log.append((settings, key))
+
+        def on_change_event(settings, keys, n_keys):
+            change_event_log.append((settings, keys, n_keys))
+
+        self.settings.connect('changed', on_changed)
+        self.settings.connect('change-event', on_change_event)
+        self.settings['test-string'] = 'Moo'
+        self.assertEqual(changed_log, [(self.settings, 'test-string')])
+        self.assertEqual(change_event_log, [(self.settings,
+                                             [GLib.quark_from_static_string('test-string')],
+                                             1)])
+
+
+@unittest.skipIf(os.name == "nt", "FIXME")
+class TestGFile(unittest.TestCase):
+    def setUp(self):
+        self.file, self.io_stream = Gio.File.new_tmp('TestGFile.XXXXXX')
+
+    def tearDown(self):
+        try:
+            self.file.delete(None)
+            # test_delete and test_delete_async already remove it
+        except GLib.GError:
+            pass
+
+    def test_replace_contents(self):
+        content = b'hello\0world\x7F!'
+        succ, etag = self.file.replace_contents(content, None, False,
+                                                Gio.FileCreateFlags.NONE, None)
+        new_succ, new_content, new_etag = self.file.load_contents(None)
+
+        self.assertTrue(succ)
+        self.assertTrue(new_succ)
+        self.assertEqual(etag, new_etag)
+        self.assertEqual(content, new_content)
+
+    # https://bugzilla.gnome.org/show_bug.cgi?id=690525
+    def disabled_test_replace_contents_async(self):
+        content = b''.join(bytes(chr(i), 'utf-8') for i in range(128))
+
+        def callback(f, result, d):
+            # Quit so in case of failed assertations loop doesn't keep running.
+            main_loop.quit()
+            succ, etag = self.file.replace_contents_finish(result)
+            new_succ, new_content, new_etag = self.file.load_contents(None)
+            d['succ'], d['etag'] = self.file.replace_contents_finish(result)
+            load = self.file.load_contents(None)
+            d['new_succ'], d['new_content'], d['new_etag'] = load
+
+        data = {}
+        self.file.replace_contents_async(content, None, False,
+                                         Gio.FileCreateFlags.NONE, None,
+                                         callback, data)
+        main_loop = GLib.MainLoop()
+        main_loop.run()
+        self.assertTrue(data['succ'])
+        self.assertTrue(data['new_succ'])
+        self.assertEqual(data['etag'], data['new_etag'])
+        self.assertEqual(content, data['new_content'])
+
+    def test_tmp_exists(self):
+        # A simple test to check if Gio.File.new_tmp is working correctly.
+        self.assertTrue(self.file.query_exists(None))
+
+    def test_delete(self):
+        self.file.delete(None)
+        self.assertFalse(self.file.query_exists(None))
+
+    def test_delete_async(self):
+        def callback(f, result, data):
+            main_loop.quit()
+
+        self.file.delete_async(0, None, callback, None)
+        main_loop = GLib.MainLoop()
+        main_loop.run()
+        self.assertFalse(self.file.query_exists(None))
+
+
+@unittest.skipIf(os.name == "nt", "crashes on Windows")
+class TestGApplication(unittest.TestCase):
+    def test_command_line(self):
+        class App(Gio.Application):
+            args = None
+
+            def __init__(self):
+                super(App, self).__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
+
+            def do_command_line(self, cmdline):
+                self.args = cmdline.get_arguments()
+                return 42
+
+        app = App()
+        res = app.run(['spam', 'eggs'])
+
+        self.assertEqual(res, 42)
+        self.assertSequenceEqual(app.args, ['spam', 'eggs'])
+
+    def test_local_command_line(self):
+        class App(Gio.Application):
+            local_args = None
+
+            def __init__(self):
+                super(App, self).__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
+
+            def do_local_command_line(self, args):
+                self.local_args = args[:]  # copy
+                args.remove('eggs')
+
+                # True skips do_command_line being called.
+                return True, args, 42
+
+        app = App()
+        res = app.run(['spam', 'eggs'])
+
+        self.assertEqual(res, 42)
+        self.assertSequenceEqual(app.local_args, ['spam', 'eggs'])
+
+    def test_local_and_remote_command_line(self):
+        class App(Gio.Application):
+            args = None
+            local_args = None
+
+            def __init__(self):
+                super(App, self).__init__(flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
+
+            def do_command_line(self, cmdline):
+                self.args = cmdline.get_arguments()
+                return 42
+
+            def do_local_command_line(self, args):
+                self.local_args = args[:]  # copy
+                args.remove('eggs')
+
+                # False causes do_command_line to be called with args.
+                return False, args, 0
+
+        app = App()
+        res = app.run(['spam', 'eggs'])
+
+        self.assertEqual(res, 42)
+        self.assertSequenceEqual(app.args, ['spam'])
+        self.assertSequenceEqual(app.local_args, ['spam', 'eggs'])
+
+    @unittest.skipUnless(hasattr(Gio.Application, 'add_main_option'),
+                         'Requires newer version of GLib')
+    def test_add_main_option(self):
+        stored_options = []
+
+        def on_handle_local_options(app, options):
+            stored_options.append(options)
+            return 0  # Return 0 if options have been handled
+
+        def on_activate(app):
+            pass
+
+        app = Gio.Application()
+        app.add_main_option(long_name='string',
+                            short_name=b's',
+                            flags=0,
+                            arg=GLib.OptionArg.STRING,
+                            description='some string')
+
+        app.connect('activate', on_activate)
+        app.connect('handle-local-options', on_handle_local_options)
+        app.run(['app', '-s', 'test string'])
+
+        self.assertEqual(len(stored_options), 1)
+        options = stored_options[0]
+        self.assertTrue(options.contains('string'))
+        self.assertEqual(options.lookup_value('string').unpack(),
+                         'test string')
diff --git a/tests/test_glib.py b/tests/test_glib.py
new file mode 100644 (file)
index 0000000..565a872
--- /dev/null
@@ -0,0 +1,301 @@
+# -*- Mode: Python -*-
+# encoding: UTF-8
+
+from __future__ import absolute_import
+
+import os
+import sys
+import unittest
+import os.path
+import warnings
+import subprocess
+
+import pytest
+from gi.repository import GLib
+from gi import PyGIDeprecationWarning
+from gi._compat import PY3
+
+
+class TestGLib(unittest.TestCase):
+
+    @pytest.mark.xfail(strict=True)
+    def test_pytest_capture_error_in_closure(self):
+        # this test is supposed to fail
+        ml = GLib.MainLoop()
+
+        def callback():
+            ml.quit()
+            raise Exception("expected")
+
+        GLib.idle_add(callback)
+        ml.run()
+
+    @unittest.skipIf(os.name == "nt", "no bash on Windows")
+    def test_find_program_in_path(self):
+        bash_path = GLib.find_program_in_path('bash')
+        self.assertTrue(bash_path.endswith(os.path.sep + 'bash'))
+        self.assertTrue(os.path.exists(bash_path))
+
+        self.assertEqual(GLib.find_program_in_path('non existing'), None)
+
+    def test_markup_escape_text(self):
+        self.assertEqual(GLib.markup_escape_text(u'a&bä'), 'a&amp;bä')
+        self.assertEqual(GLib.markup_escape_text(b'a&b\x05'), 'a&amp;b&#x5;')
+
+        # with explicit length argument
+        self.assertEqual(GLib.markup_escape_text(b'a\x05\x01\x02', 2), 'a&#x5;')
+
+    def test_progname(self):
+        GLib.set_prgname('moo')
+        self.assertEqual(GLib.get_prgname(), 'moo')
+
+    def test_appname(self):
+        GLib.set_application_name('moo')
+        self.assertEqual(GLib.get_application_name(), 'moo')
+
+    def test_xdg_dirs(self):
+        d = GLib.get_user_data_dir()
+        self.assertTrue(os.path.sep in d, d)
+        d = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP)
+        self.assertTrue(os.path.sep in d, d)
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', PyGIDeprecationWarning)
+
+            # also works with backwards compatible enum names
+            self.assertEqual(GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_MUSIC),
+                             GLib.get_user_special_dir(GLib.USER_DIRECTORY_MUSIC))
+
+        for d in GLib.get_system_config_dirs():
+            self.assertTrue(os.path.sep in d, d)
+        for d in GLib.get_system_data_dirs():
+            self.assertTrue(isinstance(d, str), d)
+
+    def test_main_depth(self):
+        self.assertEqual(GLib.main_depth(), 0)
+
+    def test_filenames(self):
+        self.assertEqual(GLib.filename_display_name('foo'), 'foo')
+        self.assertEqual(GLib.filename_display_basename('bar/foo'), 'foo')
+
+        def glibfsencode(f):
+            # the annotations of filename_from_utf8() was changed in
+            # https://bugzilla.gnome.org/show_bug.cgi?id=756128
+            if isinstance(f, bytes):
+                return f
+            if os.name == "nt":
+                if PY3:
+                    return f.encode("utf-8", "surrogatepass")
+                else:
+                    return f.encode("utf-8")
+            else:
+                assert PY3
+                return os.fsencode(f)
+
+        # this is locale dependent, so we cannot completely verify the result
+        res = GLib.filename_from_utf8(u'aäb')
+        res = glibfsencode(res)
+        self.assertTrue(isinstance(res, bytes))
+        self.assertGreaterEqual(len(res), 3)
+
+        # with explicit length argument
+        res = GLib.filename_from_utf8(u'aäb', 1)
+        res = glibfsencode(res)
+        self.assertEqual(res, b'a')
+
+    def test_uri_extract(self):
+        res = GLib.uri_list_extract_uris('''# some comment
+http://example.com
+https://my.org/q?x=1&y=2
+            http://gnome.org/new''')
+        self.assertEqual(res, ['http://example.com',
+                               'https://my.org/q?x=1&y=2',
+                               'http://gnome.org/new'])
+
+    def test_current_time(self):
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            tm = GLib.get_current_time()
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+
+        self.assertTrue(isinstance(tm, float))
+        self.assertGreater(tm, 1350000000.0)
+
+    @unittest.skipIf(sys.platform == "darwin", "fails on OSX")
+    def test_main_loop(self):
+        # note we do not test run() here, as we use this in countless other
+        # tests
+        ml = GLib.MainLoop()
+        self.assertFalse(ml.is_running())
+
+        context = ml.get_context()
+        self.assertEqual(context, GLib.MainContext.default())
+        self.assertTrue(context.is_owner() in [True, False])
+        self.assertTrue(context.pending() in [True, False])
+        self.assertFalse(context.iteration(False))
+
+    def test_main_loop_with_context(self):
+        context = GLib.MainContext()
+        ml = GLib.MainLoop(context)
+        self.assertFalse(ml.is_running())
+        self.assertEqual(ml.get_context(), context)
+
+    def test_main_context(self):
+        # constructor
+        context = GLib.MainContext()
+        self.assertTrue(context.is_owner() in [True, False])
+        self.assertFalse(context.pending())
+        self.assertFalse(context.iteration(False))
+
+        # GLib API
+        context = GLib.MainContext.default()
+        self.assertTrue(context.is_owner() in [True, False])
+        self.assertTrue(context.pending() in [True, False])
+        self.assertTrue(context.iteration(False) in [True, False])
+
+        # backwards compatible API
+        context = GLib.main_context_default()
+        self.assertTrue(context.is_owner() in [True, False])
+        self.assertTrue(context.pending() in [True, False])
+        self.assertTrue(context.iteration(False) in [True, False])
+
+    @unittest.skipIf(os.name == "nt", "hangs")
+    def test_io_add_watch_no_data(self):
+        (r, w) = os.pipe()
+        call_data = []
+
+        def cb(fd, condition):
+            call_data.append((fd, condition, os.read(fd, 1)))
+            if len(call_data) == 2:
+                ml.quit()
+            return True
+
+        # io_add_watch() takes an IOChannel, calling with an fd is deprecated
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            GLib.io_add_watch(r, GLib.IOCondition.IN, cb,
+                              priority=GLib.PRIORITY_HIGH)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+
+        def write():
+            os.write(w, b'a')
+            GLib.idle_add(lambda: os.write(w, b'b') and False)
+
+        ml = GLib.MainLoop()
+        GLib.idle_add(write)
+        GLib.timeout_add(2000, ml.quit)
+        ml.run()
+
+        self.assertEqual(call_data, [(r, GLib.IOCondition.IN, b'a'),
+                                     (r, GLib.IOCondition.IN, b'b')])
+
+    @unittest.skipIf(os.name == "nt", "hangs")
+    def test_io_add_watch_with_data(self):
+        (r, w) = os.pipe()
+        call_data = []
+
+        def cb(fd, condition, data):
+            call_data.append((fd, condition, os.read(fd, 1), data))
+            if len(call_data) == 2:
+                ml.quit()
+            return True
+
+        # io_add_watch() takes an IOChannel, calling with an fd is deprecated
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            GLib.io_add_watch(r, GLib.IOCondition.IN, cb, 'moo',
+                              priority=GLib.PRIORITY_HIGH)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+
+        def write():
+            os.write(w, b'a')
+            GLib.idle_add(lambda: os.write(w, b'b') and False)
+
+        ml = GLib.MainLoop()
+        GLib.idle_add(write)
+        GLib.timeout_add(2000, ml.quit)
+        ml.run()
+
+        self.assertEqual(call_data, [(r, GLib.IOCondition.IN, b'a', 'moo'),
+                                     (r, GLib.IOCondition.IN, b'b', 'moo')])
+
+    @unittest.skipIf(os.name == "nt", "hangs")
+    def test_io_add_watch_with_multiple_data(self):
+        (r, w) = os.pipe()
+        call_data = []
+
+        def cb(fd, condition, *user_data):
+            call_data.append((fd, condition, os.read(fd, 1), user_data))
+            ml.quit()
+            return True
+
+        # io_add_watch() takes an IOChannel, calling with an fd is deprecated
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            GLib.io_add_watch(r, GLib.IOCondition.IN, cb, 'moo', 'foo')
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+
+        ml = GLib.MainLoop()
+        GLib.idle_add(lambda: os.write(w, b'a') and False)
+        GLib.timeout_add(2000, ml.quit)
+        ml.run()
+
+        self.assertEqual(call_data, [(r, GLib.IOCondition.IN, b'a', ('moo', 'foo'))])
+
+    @unittest.skipIf(sys.platform == "darwin", "fails")
+    @unittest.skipIf(os.name == "nt", "no shell on Windows")
+    def test_io_add_watch_pyfile(self):
+        call_data = []
+
+        cmd = subprocess.Popen('echo hello; echo world',
+                               shell=True, bufsize=0, stdout=subprocess.PIPE)
+
+        def cb(file, condition):
+            call_data.append((file, condition, file.readline()))
+            if len(call_data) == 2:
+                # avoid having to wait for the full timeout
+                ml.quit()
+            return True
+
+        # io_add_watch() takes an IOChannel, calling with a Python file is deprecated
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            GLib.io_add_watch(cmd.stdout, GLib.IOCondition.IN, cb)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+
+        ml = GLib.MainLoop()
+        GLib.timeout_add(2000, ml.quit)
+        ml.run()
+
+        cmd.wait()
+
+        self.assertEqual(call_data, [(cmd.stdout, GLib.IOCondition.IN, b'hello\n'),
+                                     (cmd.stdout, GLib.IOCondition.IN, b'world\n')])
+
+    def test_glib_version(self):
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', PyGIDeprecationWarning)
+
+            (major, minor, micro) = GLib.glib_version
+            self.assertGreaterEqual(major, 2)
+            self.assertGreaterEqual(minor, 0)
+            self.assertGreaterEqual(micro, 0)
+
+    def test_pyglib_version(self):
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', PyGIDeprecationWarning)
+
+            (major, minor, micro) = GLib.pyglib_version
+            self.assertGreaterEqual(major, 3)
+            self.assertGreaterEqual(minor, 0)
+            self.assertGreaterEqual(micro, 0)
+
+    def test_timezone_constructor(self):
+        timezone = GLib.TimeZone("+05:21")
+        self.assertEqual(timezone.get_offset(0), ((5 * 60) + 21) * 60)
+
+    def test_source_attach_implicit_context(self):
+        context = GLib.MainContext.default()
+        source = GLib.Idle()
+        source_id = source.attach()
+        self.assertEqual(context, source.get_context())
+        self.assertTrue(GLib.Source.remove(source_id))
diff --git a/tests/test_gobject.py b/tests/test_gobject.py
new file mode 100644 (file)
index 0000000..bef3e9b
--- /dev/null
@@ -0,0 +1,790 @@
+# -*- Mode: Python -*-
+
+from __future__ import absolute_import
+
+import sys
+import gc
+import unittest
+import warnings
+
+import pytest
+
+from gi.repository import GObject, GLib, Gio
+from gi import PyGIDeprecationWarning
+from gi.module import get_introspection_module
+from gi import _gi
+
+import testhelper
+
+
+class TestGObjectAPI(unittest.TestCase):
+
+    def test_call_method_uninitialized_instance(self):
+        obj = GObject.Object.__new__(GObject.Object)
+        with self.assertRaisesRegex(RuntimeError, '.*is not initialized'):
+            obj.notify("foo")
+
+    def test_gobject_inheritance(self):
+        # GObject.Object is a class hierarchy as follows:
+        # overrides.Object -> introspection.Object -> static.GObject
+        GIObjectModule = get_introspection_module('GObject')
+        self.assertTrue(issubclass(GObject.Object, GIObjectModule.Object))
+        self.assertTrue(issubclass(GIObjectModule.Object, _gi.GObject))
+
+        self.assertEqual(_gi.GObject.__gtype__, GObject.TYPE_OBJECT)
+        self.assertEqual(GIObjectModule.Object.__gtype__, GObject.TYPE_OBJECT)
+        self.assertEqual(GObject.Object.__gtype__, GObject.TYPE_OBJECT)
+
+        # The pytype wrapper should hold the outer most Object class from overrides.
+        self.assertEqual(GObject.TYPE_OBJECT.pytype, GObject.Object)
+
+    def test_gobject_unsupported_overrides(self):
+        obj = GObject.Object()
+
+        with self.assertRaisesRegex(RuntimeError, 'Data access methods are unsupported.*'):
+            obj.get_data()
+
+        with self.assertRaisesRegex(RuntimeError, 'This method is currently unsupported.*'):
+            obj.force_floating()
+
+    def test_compat_api(self):
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter('always')
+            # GObject formerly exposed a lot of GLib's functions
+            self.assertEqual(GObject.markup_escape_text('foo'), 'foo')
+
+            ml = GObject.MainLoop()
+            self.assertFalse(ml.is_running())
+
+            context = GObject.main_context_default()
+            self.assertTrue(context.pending() in [False, True])
+
+            context = GObject.MainContext()
+            self.assertFalse(context.pending())
+
+            self.assertTrue(issubclass(w[0].category, PyGIDeprecationWarning))
+            self.assertTrue('GLib.markup_escape_text' in str(w[0]), str(w[0]))
+
+            self.assertLess(GObject.PRIORITY_HIGH, GObject.PRIORITY_DEFAULT)
+
+    def test_min_max_int(self):
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', PyGIDeprecationWarning)
+
+            self.assertEqual(GObject.G_MAXINT16, 2 ** 15 - 1)
+            self.assertEqual(GObject.G_MININT16, -2 ** 15)
+            self.assertEqual(GObject.G_MAXUINT16, 2 ** 16 - 1)
+
+            self.assertEqual(GObject.G_MAXINT32, 2 ** 31 - 1)
+            self.assertEqual(GObject.G_MININT32, -2 ** 31)
+            self.assertEqual(GObject.G_MAXUINT32, 2 ** 32 - 1)
+
+            self.assertEqual(GObject.G_MAXINT64, 2 ** 63 - 1)
+            self.assertEqual(GObject.G_MININT64, -2 ** 63)
+            self.assertEqual(GObject.G_MAXUINT64, 2 ** 64 - 1)
+
+
+class TestReferenceCounting(unittest.TestCase):
+    def test_regular_object(self):
+        obj = GObject.GObject()
+        self.assertEqual(obj.__grefcount__, 1)
+
+        obj = GObject.new(GObject.GObject)
+        self.assertEqual(obj.__grefcount__, 1)
+
+    def test_floating(self):
+        obj = testhelper.Floating()
+        self.assertEqual(obj.__grefcount__, 1)
+
+        obj = GObject.new(testhelper.Floating)
+        self.assertEqual(obj.__grefcount__, 1)
+
+    def test_owned_by_library(self):
+        # Upon creation, the refcount of the object should be 2:
+        # - someone already has a reference on the new object.
+        # - the python wrapper should hold its own reference.
+        obj = testhelper.OwnedByLibrary()
+        self.assertEqual(obj.__grefcount__, 2)
+
+        # We ask the library to release its reference, so the only
+        # remaining ref should be our wrapper's. Once the wrapper
+        # will run out of scope, the object will get finalized.
+        obj.release()
+        self.assertEqual(obj.__grefcount__, 1)
+
+    def test_owned_by_library_out_of_scope(self):
+        obj = testhelper.OwnedByLibrary()
+        self.assertEqual(obj.__grefcount__, 2)
+
+        # We are manually taking the object out of scope. This means
+        # that our wrapper has been freed, and its reference dropped. We
+        # cannot check it but the refcount should now be 1 (the ref held
+        # by the library is still there, we didn't call release()
+        obj = None
+
+        # When we get the object back from the lib, the wrapper is
+        # re-created, so our refcount will be 2 once again.
+        obj = testhelper.owned_by_library_get_instance_list()[0]
+        self.assertEqual(obj.__grefcount__, 2)
+
+        obj.release()
+        self.assertEqual(obj.__grefcount__, 1)
+
+    def test_owned_by_library_using_gobject_new(self):
+        # Upon creation, the refcount of the object should be 2:
+        # - someone already has a reference on the new object.
+        # - the python wrapper should hold its own reference.
+        obj = GObject.new(testhelper.OwnedByLibrary)
+        self.assertEqual(obj.__grefcount__, 2)
+
+        # We ask the library to release its reference, so the only
+        # remaining ref should be our wrapper's. Once the wrapper
+        # will run out of scope, the object will get finalized.
+        obj.release()
+        self.assertEqual(obj.__grefcount__, 1)
+
+    def test_owned_by_library_out_of_scope_using_gobject_new(self):
+        obj = GObject.new(testhelper.OwnedByLibrary)
+        self.assertEqual(obj.__grefcount__, 2)
+
+        # We are manually taking the object out of scope. This means
+        # that our wrapper has been freed, and its reference dropped. We
+        # cannot check it but the refcount should now be 1 (the ref held
+        # by the library is still there, we didn't call release()
+        obj = None
+
+        # When we get the object back from the lib, the wrapper is
+        # re-created, so our refcount will be 2 once again.
+        obj = testhelper.owned_by_library_get_instance_list()[0]
+        self.assertEqual(obj.__grefcount__, 2)
+
+        obj.release()
+        self.assertEqual(obj.__grefcount__, 1)
+
+    def test_floating_and_sunk(self):
+        # Upon creation, the refcount of the object should be 2:
+        # - someone already has a reference on the new object.
+        # - the python wrapper should hold its own reference.
+        obj = testhelper.FloatingAndSunk()
+        self.assertEqual(obj.__grefcount__, 2)
+
+        # We ask the library to release its reference, so the only
+        # remaining ref should be our wrapper's. Once the wrapper
+        # will run out of scope, the object will get finalized.
+        obj.release()
+        self.assertEqual(obj.__grefcount__, 1)
+
+    def test_floating_and_sunk_out_of_scope(self):
+        obj = testhelper.FloatingAndSunk()
+        self.assertEqual(obj.__grefcount__, 2)
+
+        # We are manually taking the object out of scope. This means
+        # that our wrapper has been freed, and its reference dropped. We
+        # cannot check it but the refcount should now be 1 (the ref held
+        # by the library is still there, we didn't call release()
+        obj = None
+
+        # When we get the object back from the lib, the wrapper is
+        # re-created, so our refcount will be 2 once again.
+        obj = testhelper.floating_and_sunk_get_instance_list()[0]
+        self.assertEqual(obj.__grefcount__, 2)
+
+        obj.release()
+        self.assertEqual(obj.__grefcount__, 1)
+
+    def test_floating_and_sunk_using_gobject_new(self):
+        # Upon creation, the refcount of the object should be 2:
+        # - someone already has a reference on the new object.
+        # - the python wrapper should hold its own reference.
+        obj = GObject.new(testhelper.FloatingAndSunk)
+        self.assertEqual(obj.__grefcount__, 2)
+
+        # We ask the library to release its reference, so the only
+        # remaining ref should be our wrapper's. Once the wrapper
+        # will run out of scope, the object will get finalized.
+        obj.release()
+        self.assertEqual(obj.__grefcount__, 1)
+
+    def test_floating_and_sunk_out_of_scope_using_gobject_new(self):
+        obj = GObject.new(testhelper.FloatingAndSunk)
+        self.assertEqual(obj.__grefcount__, 2)
+
+        # We are manually taking the object out of scope. This means
+        # that our wrapper has been freed, and its reference dropped. We
+        # cannot check it but the refcount should now be 1 (the ref held
+        # by the library is still there, we didn't call release()
+        obj = None
+
+        # When we get the object back from the lib, the wrapper is
+        # re-created, so our refcount will be 2 once again.
+        obj = testhelper.floating_and_sunk_get_instance_list()[0]
+        self.assertEqual(obj.__grefcount__, 2)
+
+        obj.release()
+        self.assertEqual(obj.__grefcount__, 1)
+
+    def test_uninitialized_object(self):
+        class Obj(GObject.GObject):
+            def __init__(self):
+                x = self.__grefcount__
+                super(Obj, self).__init__()
+                assert x >= 0  # quiesce pyflakes
+
+        # Accessing __grefcount__ before the object is initialized is wrong.
+        # Ensure we get a proper exception instead of a crash.
+        self.assertRaises(TypeError, Obj)
+
+
+class A(GObject.GObject):
+    def __init__(self):
+        super(A, self).__init__()
+
+
+class TestPythonReferenceCounting(unittest.TestCase):
+    # Newly created instances should alwayshave two references: one for
+    # the GC, and one for the bound variable in the local scope.
+
+    def test_new_instance_has_two_refs(self):
+        obj = GObject.GObject()
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(obj), 2)
+
+    def test_new_instance_has_two_refs_using_gobject_new(self):
+        obj = GObject.new(GObject.GObject)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(obj), 2)
+
+    def test_new_subclass_instance_has_two_refs(self):
+        obj = A()
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(obj), 2)
+
+    def test_new_subclass_instance_has_two_refs_using_gobject_new(self):
+        obj = GObject.new(A)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(obj), 2)
+
+
+class TestContextManagers(unittest.TestCase):
+    class ContextTestObject(GObject.GObject):
+        prop = GObject.Property(default=0, type=int)
+
+    def on_prop_set(self, obj, prop):
+        # Handler which tracks property changed notifications.
+        self.tracking.append(obj.get_property(prop.name))
+
+    def setUp(self):
+        self.tracking = []
+        self.obj = self.ContextTestObject()
+        self.handler = self.obj.connect('notify::prop', self.on_prop_set)
+
+    def test_freeze_notify_context(self):
+        # Verify prop tracking list
+        self.assertEqual(self.tracking, [])
+        self.obj.props.prop = 1
+        self.assertEqual(self.tracking, [1])
+        self.obj.props.prop = 2
+        self.assertEqual(self.tracking, [1, 2])
+        self.assertEqual(self.obj.__grefcount__, 1)
+
+        if hasattr(sys, "getrefcount"):
+            pyref_count = sys.getrefcount(self.obj)
+
+        # Using the context manager the tracking list should not be affected.
+        # The GObject reference count should stay the same and the python
+        # object ref-count should go up.
+        with self.obj.freeze_notify():
+            self.assertEqual(self.obj.__grefcount__, 1)
+            if hasattr(sys, "getrefcount"):
+                self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1)
+            self.obj.props.prop = 3
+            self.assertEqual(self.obj.props.prop, 3)
+            self.assertEqual(self.tracking, [1, 2])
+
+        # After the context manager, the prop should have been modified,
+        # the tracking list will be modified, and the python object ref
+        # count goes back down.
+        gc.collect()
+        self.assertEqual(self.obj.props.prop, 3)
+        self.assertEqual(self.tracking, [1, 2, 3])
+        self.assertEqual(self.obj.__grefcount__, 1)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(self.obj), pyref_count)
+
+    def test_handler_block_context(self):
+        # Verify prop tracking list
+        self.assertEqual(self.tracking, [])
+        self.obj.props.prop = 1
+        self.assertEqual(self.tracking, [1])
+        self.obj.props.prop = 2
+        self.assertEqual(self.tracking, [1, 2])
+        self.assertEqual(self.obj.__grefcount__, 1)
+
+        if hasattr(sys, "getrefcount"):
+            pyref_count = sys.getrefcount(self.obj)
+
+        # Using the context manager the tracking list should not be affected.
+        # The GObject reference count should stay the same and the python
+        # object ref-count should go up.
+        with self.obj.handler_block(self.handler):
+            self.assertEqual(self.obj.__grefcount__, 1)
+            if hasattr(sys, "getrefcount"):
+                self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1)
+            self.obj.props.prop = 3
+            self.assertEqual(self.obj.props.prop, 3)
+            self.assertEqual(self.tracking, [1, 2])
+
+        # After the context manager, the prop should have been modified
+        # the tracking list should have stayed the same and the GObject ref
+        # count goes back down.
+        gc.collect()
+        self.assertEqual(self.obj.props.prop, 3)
+        self.assertEqual(self.tracking, [1, 2])
+        self.assertEqual(self.obj.__grefcount__, 1)
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(self.obj), pyref_count)
+
+    def test_freeze_notify_context_nested(self):
+        self.assertEqual(self.tracking, [])
+        with self.obj.freeze_notify():
+            self.obj.props.prop = 1
+            self.assertEqual(self.tracking, [])
+
+            with self.obj.freeze_notify():
+                self.obj.props.prop = 2
+                self.assertEqual(self.tracking, [])
+
+                with self.obj.freeze_notify():
+                    self.obj.props.prop = 3
+                    self.assertEqual(self.tracking, [])
+                self.assertEqual(self.tracking, [])
+            self.assertEqual(self.tracking, [])
+
+        # Finally after last context, the notifications should have collapsed
+        # and the last one sent.
+        self.assertEqual(self.tracking, [3])
+
+    def test_handler_block_context_nested(self):
+        self.assertEqual(self.tracking, [])
+        with self.obj.handler_block(self.handler):
+            self.obj.props.prop = 1
+            self.assertEqual(self.tracking, [])
+
+            with self.obj.handler_block(self.handler):
+                self.obj.props.prop = 2
+                self.assertEqual(self.tracking, [])
+
+                with self.obj.handler_block(self.handler):
+                    self.obj.props.prop = 3
+                    self.assertEqual(self.tracking, [])
+                self.assertEqual(self.tracking, [])
+            self.assertEqual(self.tracking, [])
+
+        # Finally after last context, the notifications should have collapsed
+        # and the last one sent.
+        self.assertEqual(self.obj.props.prop, 3)
+        self.assertEqual(self.tracking, [])
+
+    def test_freeze_notify_normal_usage_ref_counts(self):
+        # Ensure ref counts without using methods as context managers
+        # maintain the same count.
+        self.assertEqual(self.obj.__grefcount__, 1)
+        self.obj.freeze_notify()
+        self.assertEqual(self.obj.__grefcount__, 1)
+        self.obj.thaw_notify()
+        self.assertEqual(self.obj.__grefcount__, 1)
+
+    def test_handler_block_normal_usage_ref_counts(self):
+        self.assertEqual(self.obj.__grefcount__, 1)
+        self.obj.handler_block(self.handler)
+        self.assertEqual(self.obj.__grefcount__, 1)
+        self.obj.handler_unblock(self.handler)
+        self.assertEqual(self.obj.__grefcount__, 1)
+
+    def test_freeze_notify_context_error(self):
+        # Test an exception occurring within a freeze context exits the context
+        try:
+            with self.obj.freeze_notify():
+                self.obj.props.prop = 1
+                self.assertEqual(self.tracking, [])
+                raise ValueError('Simulation')
+        except ValueError:
+            pass
+
+        # Verify the property set within the context called notify.
+        self.assertEqual(self.obj.props.prop, 1)
+        self.assertEqual(self.tracking, [1])
+
+        # Verify we are still not in a frozen context.
+        self.obj.props.prop = 2
+        self.assertEqual(self.tracking, [1, 2])
+
+    def test_handler_block_context_error(self):
+        # Test an exception occurring within a handler block exits the context
+        try:
+            with self.obj.handler_block(self.handler):
+                self.obj.props.prop = 1
+                self.assertEqual(self.tracking, [])
+                raise ValueError('Simulation')
+        except ValueError:
+            pass
+
+        # Verify the property set within the context didn't call notify.
+        self.assertEqual(self.obj.props.prop, 1)
+        self.assertEqual(self.tracking, [])
+
+        # Verify we are still not in a handler block context.
+        self.obj.props.prop = 2
+        self.assertEqual(self.tracking, [2])
+
+
+@unittest.skipUnless(hasattr(GObject.Binding, 'unbind'),
+                     'Requires newer GLib which has g_binding_unbind')
+class TestPropertyBindings(unittest.TestCase):
+    class TestObject(GObject.GObject):
+        int_prop = GObject.Property(default=0, type=int)
+
+    def setUp(self):
+        self.source = self.TestObject()
+        self.target = self.TestObject()
+
+    def test_default_binding(self):
+        binding = self.source.bind_property('int_prop', self.target, 'int_prop',
+                                            GObject.BindingFlags.DEFAULT)
+        binding = binding  # PyFlakes
+
+        # Test setting value on source gets pushed to target
+        self.source.int_prop = 1
+        self.assertEqual(self.source.int_prop, 1)
+        self.assertEqual(self.target.int_prop, 1)
+
+        # Test setting value on target does not change source
+        self.target.props.int_prop = 2
+        self.assertEqual(self.source.int_prop, 1)
+        self.assertEqual(self.target.int_prop, 2)
+
+    def test_bidirectional_binding(self):
+        binding = self.source.bind_property('int_prop', self.target, 'int_prop',
+                                            GObject.BindingFlags.BIDIRECTIONAL)
+        binding = binding  # PyFlakes
+
+        # Test setting value on source gets pushed to target
+        self.source.int_prop = 1
+        self.assertEqual(self.source.int_prop, 1)
+        self.assertEqual(self.target.int_prop, 1)
+
+        # Test setting value on target also changes source
+        self.target.props.int_prop = 2
+        self.assertEqual(self.source.int_prop, 2)
+        self.assertEqual(self.target.int_prop, 2)
+
+    def test_transform_to_only(self):
+        def transform_to(binding, value, user_data=None):
+            self.assertEqual(user_data, 'test-data')
+            return value * 2
+
+        binding = self.source.bind_property('int_prop', self.target, 'int_prop',
+                                            GObject.BindingFlags.DEFAULT,
+                                            transform_to, None, 'test-data')
+        binding = binding  # PyFlakes
+
+        self.source.int_prop = 1
+        self.assertEqual(self.source.int_prop, 1)
+        self.assertEqual(self.target.int_prop, 2)
+
+        self.target.props.int_prop = 1
+        self.assertEqual(self.source.int_prop, 1)
+        self.assertEqual(self.target.int_prop, 1)
+
+    def test_transform_from_only(self):
+        def transform_from(binding, value, user_data=None):
+            self.assertEqual(user_data, None)
+            return value * 2
+
+        binding = self.source.bind_property('int_prop', self.target, 'int_prop',
+                                            GObject.BindingFlags.BIDIRECTIONAL,
+                                            None, transform_from)
+        binding = binding  # PyFlakes
+
+        self.source.int_prop = 1
+        self.assertEqual(self.source.int_prop, 1)
+        self.assertEqual(self.target.int_prop, 1)
+
+        self.target.props.int_prop = 1
+        self.assertEqual(self.source.int_prop, 2)
+        self.assertEqual(self.target.int_prop, 1)
+
+    def test_transform_bidirectional(self):
+        test_data = object()
+
+        def transform_to(binding, value, user_data=None):
+            self.assertEqual(user_data, test_data)
+            return value * 2
+
+        def transform_from(binding, value, user_data=None):
+            self.assertEqual(user_data, test_data)
+            return value // 2
+
+        if hasattr(sys, "getrefcount"):
+            test_data_ref_count = sys.getrefcount(test_data)
+            transform_to_ref_count = sys.getrefcount(transform_to)
+            transform_from_ref_count = sys.getrefcount(transform_from)
+
+        # bidirectional bindings
+        binding = self.source.bind_property('int_prop', self.target, 'int_prop',
+                                            GObject.BindingFlags.BIDIRECTIONAL,
+                                            transform_to, transform_from, test_data)
+        binding = binding  # PyFlakes
+        if hasattr(sys, "getrefcount"):
+            binding_ref_count = sys.getrefcount(binding)
+            binding_gref_count = binding.__grefcount__
+
+        self.source.int_prop = 1
+        self.assertEqual(self.source.int_prop, 1)
+        self.assertEqual(self.target.int_prop, 2)
+
+        self.target.props.int_prop = 4
+        self.assertEqual(self.source.int_prop, 2)
+        self.assertEqual(self.target.int_prop, 4)
+
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(binding), binding_ref_count)
+            self.assertEqual(binding.__grefcount__, binding_gref_count)
+
+        # test_data ref count increases by 2, once for each callback.
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(test_data), test_data_ref_count + 2)
+            self.assertEqual(sys.getrefcount(transform_to), transform_to_ref_count + 1)
+            self.assertEqual(sys.getrefcount(transform_from), transform_from_ref_count + 1)
+
+        # Unbind should clear out the binding and its transforms
+        binding.unbind()
+
+        # Setting source or target should not change the other.
+        self.target.int_prop = 3
+        self.source.int_prop = 5
+        self.assertEqual(self.target.int_prop, 3)
+        self.assertEqual(self.source.int_prop, 5)
+
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(test_data), test_data_ref_count)
+            self.assertEqual(sys.getrefcount(transform_to), transform_to_ref_count)
+            self.assertEqual(sys.getrefcount(transform_from), transform_from_ref_count)
+
+    def test_explicit_unbind_clears_connection(self):
+        self.assertEqual(self.source.int_prop, 0)
+        self.assertEqual(self.target.int_prop, 0)
+
+        # Test deleting binding reference removes binding.
+        binding = self.source.bind_property('int_prop', self.target, 'int_prop')
+        self.source.int_prop = 1
+        self.assertEqual(self.source.int_prop, 1)
+        self.assertEqual(self.target.int_prop, 1)
+
+        # unbind should clear out the bindings self reference
+        binding.unbind()
+        self.assertEqual(binding.__grefcount__, 1)
+
+        self.source.int_prop = 10
+        self.assertEqual(self.source.int_prop, 10)
+        self.assertEqual(self.target.int_prop, 1)
+
+        glib_version = (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION)
+
+        # calling unbind() on an already unbound binding
+        if glib_version >= (2, 57, 3):
+            # Fixed in newer glib:
+            # https://gitlab.gnome.org/GNOME/glib/merge_requests/244
+            for i in range(10):
+                binding.unbind()
+        else:
+            self.assertRaises(ValueError, binding.unbind)
+
+    def test_reference_counts(self):
+        self.assertEqual(self.source.__grefcount__, 1)
+        self.assertEqual(self.target.__grefcount__, 1)
+
+        # Binding ref count will be 2 do to the initial ref implicitly held by
+        # the act of binding and the ref incurred by using __call__ to generate
+        # a wrapper from the weak binding ref within python.
+        binding = self.source.bind_property('int_prop', self.target, 'int_prop')
+        self.assertEqual(binding.__grefcount__, 2)
+
+        # Creating a binding does not inc refs on source and target (they are weak
+        # on the binding object itself)
+        self.assertEqual(self.source.__grefcount__, 1)
+        self.assertEqual(self.target.__grefcount__, 1)
+
+        # Use GObject.get_property because the "props" accessor leaks.
+        # Note property names are canonicalized.
+        self.assertEqual(binding.get_property('source'), self.source)
+        self.assertEqual(binding.get_property('source_property'), 'int-prop')
+        self.assertEqual(binding.get_property('target'), self.target)
+        self.assertEqual(binding.get_property('target_property'), 'int-prop')
+        self.assertEqual(binding.get_property('flags'), GObject.BindingFlags.DEFAULT)
+
+        # Delete reference to source or target and the binding will remove its own
+        # "self reference".
+        ref = self.source.weak_ref()
+        del self.source
+        gc.collect()
+        self.assertEqual(ref(), None)
+        self.assertEqual(binding.__grefcount__, 1)
+
+        # Finally clear out the last ref held by the python wrapper
+        ref = binding.weak_ref()
+        del binding
+        gc.collect()
+        self.assertEqual(ref(), None)
+
+
+class TestGValue(unittest.TestCase):
+    def test_type_constant(self):
+        self.assertEqual(GObject.TYPE_VALUE, GObject.Value.__gtype__)
+        self.assertEqual(GObject.type_name(GObject.TYPE_VALUE), 'GValue')
+
+    def test_no_type(self):
+        value = GObject.Value()
+        self.assertEqual(value.g_type, GObject.TYPE_INVALID)
+        self.assertRaises(TypeError, value.set_value, 23)
+        self.assertEqual(value.get_value(), None)
+
+    def test_int(self):
+        value = GObject.Value(GObject.TYPE_UINT)
+        self.assertEqual(value.g_type, GObject.TYPE_UINT)
+        value.set_value(23)
+        self.assertEqual(value.get_value(), 23)
+        value.set_value(42.0)
+        self.assertEqual(value.get_value(), 42)
+
+    def test_multi_del(self):
+        value = GObject.Value(str, 'foo_bar')
+        value.__del__()
+        value.__del__()
+        del value
+
+    def test_string(self):
+        value = GObject.Value(str, 'foo_bar')
+        self.assertEqual(value.g_type, GObject.TYPE_STRING)
+        self.assertEqual(value.get_value(), 'foo_bar')
+
+    def test_float(self):
+        # python float is G_TYPE_DOUBLE
+        value = GObject.Value(float, 23.4)
+        self.assertEqual(value.g_type, GObject.TYPE_DOUBLE)
+        value.set_value(1e50)
+        self.assertAlmostEqual(value.get_value(), 1e50)
+
+        value = GObject.Value(GObject.TYPE_FLOAT, 23.4)
+        self.assertEqual(value.g_type, GObject.TYPE_FLOAT)
+        self.assertRaises(TypeError, value.set_value, 'string')
+        self.assertRaises(OverflowError, value.set_value, 1e50)
+
+    def test_float_inf_nan(self):
+        nan = float('nan')
+        for type_ in [GObject.TYPE_FLOAT, GObject.TYPE_DOUBLE]:
+            for x in [float('inf'), float('-inf'), nan]:
+                value = GObject.Value(type_, x)
+                # assertEqual() is False for (nan, nan)
+                if x is nan:
+                    self.assertEqual(str(value.get_value()), 'nan')
+                else:
+                    self.assertEqual(value.get_value(), x)
+
+    def test_enum(self):
+        value = GObject.Value(GLib.FileError, GLib.FileError.FAILED)
+        self.assertEqual(value.get_value(), GLib.FileError.FAILED)
+
+    def test_flags(self):
+        value = GObject.Value(GLib.IOFlags, GLib.IOFlags.IS_READABLE)
+        self.assertEqual(value.get_value(), GLib.IOFlags.IS_READABLE)
+
+    def test_object(self):
+        class TestObject(GObject.Object):
+            pass
+        obj = TestObject()
+        value = GObject.Value(GObject.TYPE_OBJECT, obj)
+        self.assertEqual(value.get_value(), obj)
+
+    def test_value_array(self):
+        value = GObject.Value(GObject.ValueArray)
+        self.assertEqual(value.g_type, GObject.type_from_name('GValueArray'))
+        value.set_value([32, 'foo_bar', 0.3])
+        self.assertEqual(value.get_value(), [32, 'foo_bar', 0.3])
+
+    def test_value_array_from_gvalue_list(self):
+        value = GObject.Value(GObject.ValueArray, [
+            GObject.Value(GObject.TYPE_UINT, 0xffffffff),
+            GObject.Value(GObject.TYPE_STRING, 'foo_bar')])
+        self.assertEqual(value.g_type, GObject.type_from_name('GValueArray'))
+        self.assertEqual(value.get_value(), [0xffffffff, 'foo_bar'])
+        self.assertEqual(testhelper.value_array_get_nth_type(value, 0), GObject.TYPE_UINT)
+        self.assertEqual(testhelper.value_array_get_nth_type(value, 1), GObject.TYPE_STRING)
+
+    def test_value_array_append_gvalue(self):
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', DeprecationWarning)
+
+            arr = GObject.ValueArray.new(0)
+            arr.append(GObject.Value(GObject.TYPE_UINT, 0xffffffff))
+            arr.append(GObject.Value(GObject.TYPE_STRING, 'foo_bar'))
+            self.assertEqual(arr.get_nth(0), 0xffffffff)
+            self.assertEqual(arr.get_nth(1), 'foo_bar')
+            self.assertEqual(testhelper.value_array_get_nth_type(arr, 0), GObject.TYPE_UINT)
+            self.assertEqual(testhelper.value_array_get_nth_type(arr, 1), GObject.TYPE_STRING)
+
+    def test_gerror_boxing(self):
+        error = GLib.Error('test message', domain='mydomain', code=42)
+        value = GObject.Value(GLib.Error, error)
+        self.assertEqual(value.g_type, GObject.type_from_name('GError'))
+
+        unboxed = value.get_value()
+        self.assertEqual(unboxed.message, error.message)
+        self.assertEqual(unboxed.domain, error.domain)
+        self.assertEqual(unboxed.code, error.code)
+
+    def test_gerror_novalue(self):
+        GLib.Error('test message', domain='mydomain', code=42)
+        value = GObject.Value(GLib.Error)
+        self.assertEqual(value.g_type, GObject.type_from_name('GError'))
+        self.assertEqual(value.get_value(), None)
+
+
+def test_list_properties():
+
+    def find_param(l, name):
+        for param in l:
+            if param.name == name:
+                return param
+        return
+
+    list_props = GObject.list_properties
+
+    props = list_props(Gio.Action)
+    param = find_param(props, "enabled")
+    assert param
+    assert param.value_type == GObject.TYPE_BOOLEAN
+    assert list_props("GAction") == list_props(Gio.Action)
+    assert list_props(Gio.Action.__gtype__) == list_props(Gio.Action)
+
+    props = list_props(Gio.SimpleAction)
+    assert find_param(props, "enabled")
+
+    def names(l):
+        return [p.name for p in l]
+
+    assert (set(names(list_props(Gio.Action))) <=
+            set(names(list_props(Gio.SimpleAction))))
+
+    props = list_props(Gio.FileIcon)
+    param = find_param(props, "file")
+    assert param
+    assert param.value_type == Gio.File.__gtype__
+
+    assert list_props("GFileIcon") == list_props(Gio.FileIcon)
+    assert list_props(Gio.FileIcon.__gtype__) == list_props(Gio.FileIcon)
+    assert list_props(Gio.FileIcon()) == list_props(Gio.FileIcon)
+
+    for obj in [Gio.ActionEntry, Gio.DBusError, 0, object()]:
+        with pytest.raises(TypeError):
+            list_props(obj)
diff --git a/tests/test_gtk_template.py b/tests/test_gtk_template.py
new file mode 100644 (file)
index 0000000..f0cc963
--- /dev/null
@@ -0,0 +1,592 @@
+# coding: UTF-8
+
+from __future__ import absolute_import
+
+import tempfile
+import os
+import pytest
+
+Gtk = pytest.importorskip("gi.repository.Gtk")
+GLib = pytest.importorskip("gi.repository.GLib")
+GObject = pytest.importorskip("gi.repository.GObject")
+Gio = pytest.importorskip("gi.repository.Gio")
+
+
+from .helper import capture_exceptions
+
+
+def new_gtype_name(_count=[0]):
+    _count[0] += 1
+    return "GtkTemplateTest%d" % _count[0]
+
+
+def ensure_resource_registered():
+    resource_path = "/org/gnome/pygobject/test/a.ui"
+
+    def is_registered(path):
+        try:
+            Gio.resources_get_info(path, Gio.ResourceLookupFlags.NONE)
+        except GLib.Error:
+            return False
+        return True
+
+    if is_registered(resource_path):
+        return resource_path
+
+    gresource_data = (
+        b'GVariant\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00'
+        b'\xc8\x00\x00\x00\x00\x00\x00(\x06\x00\x00\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x05\x00\x00\x00'
+        b'\x06\x00\x00\x00KP\x90\x0b\x03\x00\x00\x00\xc8\x00\x00\x00'
+        b'\x04\x00L\x00\xcc\x00\x00\x00\xd0\x00\x00\x00\xb0\xb7$0'
+        b'\x00\x00\x00\x00\xd0\x00\x00\x00\x06\x00L\x00\xd8\x00\x00\x00'
+        b'\xdc\x00\x00\x00f\xc30\xd1\x01\x00\x00\x00\xdc\x00\x00\x00'
+        b'\n\x00L\x00\xe8\x00\x00\x00\xec\x00\x00\x00\xd4\xb5\x02\x00'
+        b'\xff\xff\xff\xff\xec\x00\x00\x00\x01\x00L\x00\xf0\x00\x00\x00'
+        b'\xf4\x00\x00\x005H}\xe3\x02\x00\x00\x00\xf4\x00\x00\x00'
+        b'\x05\x00L\x00\xfc\x00\x00\x00\x00\x01\x00\x00\xa2^\xd6t'
+        b'\x04\x00\x00\x00\x00\x01\x00\x00\x04\x00v\x00\x08\x01\x00\x00'
+        b'\xa5\x01\x00\x00org/\x01\x00\x00\x00gnome/\x00\x00\x02\x00\x00\x00'
+        b'pygobject/\x00\x00\x04\x00\x00\x00/\x00\x00\x00\x00\x00\x00\x00'
+        b'test/\x00\x00\x00\x05\x00\x00\x00a.ui\x00\x00\x00\x00'
+        b'\x8d\x00\x00\x00\x00\x00\x00\x00<interface>\n  <template class="G'
+        b'tkTemplateTestResource" parent="GtkBox">\n  <property name="spaci'
+        b'ng">42</property>\n  </template>\n</interface>\n\x00\x00(uuay)'
+    )
+
+    resource = Gio.Resource.new_from_data(GLib.Bytes.new(gresource_data))
+    Gio.resources_register(resource)
+    assert is_registered(resource_path)
+    return resource_path
+
+
+def test_allow_init_template_call():
+
+    type_name = new_gtype_name()
+
+    xml = """\
+<interface>
+  <template class="{0}" parent="GtkBox">
+  </template>
+</interface>
+""".format(type_name)
+
+    @Gtk.Template.from_string(xml)
+    class Foo(Gtk.Box):
+        __gtype_name__ = type_name
+
+        def __init__(self):
+            super(Foo, self).__init__()
+            self.init_template()
+
+    # Stop current pygobject from handling the initialisation
+    del Foo.__dontuse_ginstance_init__
+
+    Foo()
+
+
+def test_init_template_second_instance():
+    type_name = new_gtype_name()
+
+    xml = """\
+<interface>
+  <template class="{0}" parent="GtkBox">
+    <child>
+      <object class="GtkLabel" id="label">
+      </object>
+    </child>
+  </template>
+</interface>
+""".format(type_name)
+
+    @Gtk.Template.from_string(xml)
+    class Foo(Gtk.Box):
+        __gtype_name__ = type_name
+
+        label = Gtk.Template.Child("label")
+
+        def __init__(self):
+            super(Foo, self).__init__()
+            self.init_template()
+
+    # Stop current pygobject from handling the initialisation
+    del Foo.__dontuse_ginstance_init__
+
+    foo = Foo()
+    assert isinstance(foo.label, Gtk.Label)
+
+    foo2 = Foo()
+    assert isinstance(foo2.label, Gtk.Label)
+
+
+def test_main_example():
+
+    type_name = new_gtype_name()
+
+    example_xml = """\
+<interface>
+  <template class="{0}" parent="GtkBox">
+    <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
+    <property name="spacing">4</property>
+    <child>
+      <object class="GtkButton" id="hello_button">
+        <property name="label">Hello World</property>
+        <signal name="clicked" handler="hello_button_clicked"
+                object="{0}" swapped="no"/>
+        <signal name="clicked" handler="hello_button_clicked_after"
+                object="{0}" swapped="no" after="yes"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkButton" id="goodbye_button">
+        <property name="label">Goodbye World</property>
+        <signal name="clicked" handler="goodbye_button_clicked"/>
+        <signal name="clicked" handler="goodbye_button_clicked_after"
+                after="yes"/>
+      </object>
+    </child>
+  </template>
+</interface>
+""".format(type_name)
+
+    @Gtk.Template.from_string(example_xml)
+    class Foo(Gtk.Box):
+        __gtype_name__ = type_name
+
+        def __init__(self):
+            super(Foo, self).__init__()
+            self.callback_hello = []
+            self.callback_hello_after = []
+            self.callback_goodbye = []
+            self.callback_goodbye_after = []
+
+        @Gtk.Template.Callback("hello_button_clicked")
+        def _hello_button_clicked(self, *args):
+            self.callback_hello.append(args)
+
+        @Gtk.Template.Callback("hello_button_clicked_after")
+        def _hello_after(self, *args):
+            self.callback_hello_after.append(args)
+
+        _hello_button = Gtk.Template.Child("hello_button")
+
+        goodbye_button = Gtk.Template.Child()
+
+        @Gtk.Template.Callback("goodbye_button_clicked")
+        def _goodbye_button_clicked(self, *args):
+            self.callback_goodbye.append(args)
+
+        @Gtk.Template.Callback("goodbye_button_clicked_after")
+        def _goodbye_after(self, *args):
+            self.callback_goodbye_after.append(args)
+
+    w = Foo()
+    assert w.__gtype__.name == type_name
+    assert w.props.orientation == Gtk.Orientation.HORIZONTAL
+    assert w.props.spacing == 4
+    assert isinstance(w._hello_button, Gtk.Button)
+    assert w._hello_button.props.label == "Hello World"
+    assert isinstance(w.goodbye_button, Gtk.Button)
+    assert w.goodbye_button.props.label == "Goodbye World"
+
+    assert w.callback_hello == []
+    w._hello_button.clicked()
+    assert w.callback_hello == [(w,)]
+    assert w.callback_hello_after == [(w,)]
+
+    assert w.callback_goodbye == []
+    w.goodbye_button.clicked()
+    assert w.callback_goodbye == [(w.goodbye_button,)]
+    assert w.callback_goodbye_after == [(w.goodbye_button,)]
+
+
+def test_duplicate_handler():
+
+    type_name = new_gtype_name()
+
+    xml = """\
+<interface>
+  <template class="{0}" parent="GtkBox">
+    <child>
+      <object class="GtkButton" id="hello_button">
+        <signal name="clicked" handler="hello_button_clicked">
+      </object>
+    </child>
+  </template>
+</interface>
+""".format(type_name)
+
+    class Foo(Gtk.Box):
+        __gtype_name__ = type_name
+
+        @Gtk.Template.Callback("hello_button_clicked")
+        def _hello_button_clicked(self, *args):
+            pass
+
+        @Gtk.Template.Callback()
+        def hello_button_clicked(self, *args):
+            pass
+
+    with pytest.raises(RuntimeError, match=".*hello_button_clicked.*"):
+        Gtk.Template.from_string(xml)(Foo)
+
+
+def test_duplicate_child():
+    type_name = new_gtype_name()
+
+    xml = """\
+<interface>
+  <template class="{0}" parent="GtkBox">
+    <child>
+      <object class="GtkButton" id="hello_button" />
+    </child>
+  </template>
+</interface>
+""".format(type_name)
+
+    class Foo(Gtk.Box):
+        __gtype_name__ = type_name
+
+        foo = Gtk.Template.Child("hello_button")
+        hello_button = Gtk.Template.Child()
+
+    with pytest.raises(RuntimeError, match=".*hello_button.*"):
+        Gtk.Template.from_string(xml)(Foo)
+
+
+def test_nonexist_handler():
+    type_name = new_gtype_name()
+
+    xml = """\
+<interface>
+  <template class="{0}" parent="GtkBox">
+  </template>
+</interface>
+""".format(type_name)
+
+    @Gtk.Template.from_string(xml)
+    class Foo(Gtk.Box):
+        __gtype_name__ = type_name
+
+        @Gtk.Template.Callback("nonexit")
+        def foo(self, *args):
+            pass
+
+    with capture_exceptions() as exc_info:
+        Foo()
+    assert "nonexit" in str(exc_info[0].value)
+    assert exc_info[0].type is RuntimeError
+
+
+def test_missing_handler_callback():
+    type_name = new_gtype_name()
+
+    xml = """\
+<interface>
+  <template class="{0}" parent="GtkBox">
+    <child>
+      <object class="GtkButton" id="hello_button">
+        <signal name="clicked" handler="i_am_not_used_in_python" />
+      </object>
+    </child>
+  </template>
+</interface>
+""".format(type_name)
+
+    class Foo(Gtk.Box):
+        __gtype_name__ = type_name
+
+    Gtk.Template.from_string(xml)(Foo)()
+
+
+def test_handler_swapped_not_supported():
+
+    type_name = new_gtype_name()
+
+    xml = """\
+<interface>
+  <template class="{0}" parent="GtkBox">
+    <child>
+      <object class="GtkButton" id="hello_button">
+        <signal name="clicked" handler="hello_button_clicked"
+                object="{0}" swapped="yes" />
+      </object>
+    </child>
+  </template>
+</interface>
+""".format(type_name)
+
+    @Gtk.Template.from_string(xml)
+    class Foo(Gtk.Box):
+        __gtype_name__ = type_name
+
+        hello_button = Gtk.Template.Child()
+
+        @Gtk.Template.Callback("hello_button_clicked")
+        def foo(self, *args):
+            pass
+
+    with capture_exceptions() as exc_info:
+        Foo()
+    assert "G_CONNECT_SWAPPED" in str(exc_info[0].value)
+
+
+def test_handler_class_staticmethod():
+
+    type_name = new_gtype_name()
+
+    xml = """\
+<interface>
+  <template class="{0}" parent="GtkBox">
+    <child>
+      <object class="GtkButton" id="hello_button">
+        <signal name="clicked" handler="clicked_class" />
+        <signal name="clicked" handler="clicked_static" />
+      </object>
+    </child>
+  </template>
+</interface>
+""".format(type_name)
+
+    signal_args_class = []
+    signal_args_static = []
+
+    @Gtk.Template.from_string(xml)
+    class Foo(Gtk.Box):
+        __gtype_name__ = type_name
+
+        hello_button = Gtk.Template.Child()
+
+        @Gtk.Template.Callback("clicked_class")
+        @classmethod
+        def cb1(*args):
+            signal_args_class.append(args)
+
+        @Gtk.Template.Callback("clicked_static")
+        @staticmethod
+        def cb2(*args):
+            signal_args_static.append(args)
+
+    foo = Foo()
+    foo.hello_button.clicked()
+    assert signal_args_class == [(Foo, foo.hello_button)]
+    assert signal_args_static == [(foo.hello_button,)]
+
+
+def test_check_decorated_class():
+
+    NonWidget = type("Foo", (object,), {})
+    with pytest.raises(TypeError, match=".*on Widgets.*"):
+        Gtk.Template.from_string("")(NonWidget)
+
+    Widget = type("Foo", (Gtk.Widget,), {"__gtype_name__": new_gtype_name()})
+    with pytest.raises(TypeError, match=".*Cannot nest.*"):
+        Gtk.Template.from_string("")(Gtk.Template.from_string("")(Widget))
+
+    Widget = type("Foo", (Gtk.Widget,), {})
+    with pytest.raises(TypeError, match=".*__gtype_name__.*"):
+        Gtk.Template.from_string("")(Widget)
+
+    with pytest.raises(TypeError, match=".*on Widgets.*"):
+        Gtk.Template.from_string("")(object())
+
+    @Gtk.Template.from_string("")
+    class Base(Gtk.Widget):
+        __gtype_name__ = new_gtype_name()
+
+    with capture_exceptions() as exc_info:
+        type("Sub", (Base,), {})()
+    assert "not allowed at this time" in str(exc_info[0].value)
+    assert exc_info[0].type is TypeError
+
+
+def test_from_file():
+    fd, name = tempfile.mkstemp()
+    try:
+        os.close(fd)
+
+        type_name = new_gtype_name()
+
+        with open(name, "wb") as h:
+            h.write(u"""\
+    <interface>
+      <template class="{0}" parent="GtkBox">
+      <property name="spacing">42</property>
+      </template>
+    </interface>
+    """.format(type_name).encode())
+
+        @Gtk.Template.from_file(name)
+        class Foo(Gtk.Box):
+            __gtype_name__ = type_name
+
+        foo = Foo()
+        assert foo.props.spacing == 42
+    finally:
+        os.remove(name)
+
+
+def test_property_override():
+    type_name = new_gtype_name()
+
+    xml = """\
+    <interface>
+      <template class="{0}" parent="GtkBox">
+      <property name="spacing">42</property>
+      </template>
+    </interface>
+""".format(type_name)
+
+    @Gtk.Template.from_string(xml)
+    class Foo(Gtk.Box):
+        __gtype_name__ = type_name
+
+    foo = Foo()
+    assert foo.props.spacing == 42
+
+    foo = Foo(spacing=124)
+    assert foo.props.spacing == 124
+
+
+def test_from_file_non_exist():
+    dirname = tempfile.mkdtemp()
+    try:
+        path = os.path.join(dirname, "noexist")
+
+        Widget = type(
+            "Foo", (Gtk.Widget,), {"__gtype_name__": new_gtype_name()})
+        with pytest.raises(GLib.Error, match=".*No such file.*"):
+            Gtk.Template.from_file(path)(Widget)
+    finally:
+        os.rmdir(dirname)
+
+
+def test_from_string_bytes():
+    type_name = new_gtype_name()
+
+    xml = u"""\
+    <interface>
+      <template class="{0}" parent="GtkBox">
+      <property name="spacing">42</property>
+      </template>
+    </interface>
+    """.format(type_name).encode()
+
+    @Gtk.Template.from_string(xml)
+    class Foo(Gtk.Box):
+        __gtype_name__ = type_name
+
+    foo = Foo()
+    assert foo.props.spacing == 42
+
+
+def test_from_resource():
+    resource_path = ensure_resource_registered()
+
+    @Gtk.Template.from_resource(resource_path)
+    class Foo(Gtk.Box):
+        __gtype_name__ = "GtkTemplateTestResource"
+
+    foo = Foo()
+    assert foo.props.spacing == 42
+
+
+def test_from_resource_non_exit():
+    Widget = type("Foo", (Gtk.Widget,), {"__gtype_name__": new_gtype_name()})
+    with pytest.raises(GLib.Error, match=".*/or/gnome/pygobject/noexit.*"):
+        Gtk.Template.from_resource("/or/gnome/pygobject/noexit")(Widget)
+
+
+def test_constructors():
+    with pytest.raises(TypeError):
+        Gtk.Template()
+
+    with pytest.raises(TypeError):
+        Gtk.Template(foo=1)
+
+    Gtk.Template(filename="foo")
+    Gtk.Template(resource_path="foo")
+    Gtk.Template(string="foo")
+
+    with pytest.raises(TypeError):
+        Gtk.Template(filename="foo", resource_path="bar")
+
+    with pytest.raises(TypeError):
+        Gtk.Template(filename="foo", nope="bar")
+
+    Gtk.Template.from_string("bla")
+    Gtk.Template.from_resource("foo")
+    Gtk.Template.from_file("foo")
+
+
+def test_child_construct():
+    Gtk.Template.Child()
+    Gtk.Template.Child("name")
+    with pytest.raises(TypeError):
+        Gtk.Template.Child("name", True)
+    Gtk.Template.Child("name", internal=True)
+    with pytest.raises(TypeError):
+        Gtk.Template.Child("name", internal=True, something=False)
+
+
+def test_internal_child():
+
+    main_type_name = new_gtype_name()
+
+    xml = """\
+    <interface>
+      <template class="{0}" parent="GtkBox">
+        <child>
+          <object class="GtkBox" id="somechild">
+            <property name="margin">42</property>
+          </object>
+        </child>
+      </template>
+    </interface>
+    """.format(main_type_name)
+
+    @Gtk.Template.from_string(xml)
+    class MainThing(Gtk.Box):
+        __gtype_name__ = main_type_name
+
+        somechild = Gtk.Template.Child(internal=True)
+
+    thing = MainThing()
+    assert thing.somechild.props.margin == 42
+
+    other_type_name = new_gtype_name()
+
+    xml = """\
+    <interface>
+      <template class="{0}" parent="GtkBox">
+        <child>
+          <object class="{1}">
+            <child internal-child="somechild">
+              <object class="GtkBox">
+                <property name="margin">24</property>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="label">foo</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </template>
+    </interface>
+    """.format(other_type_name, main_type_name)
+
+    @Gtk.Template.from_string(xml)
+    class OtherThing(Gtk.Box):
+        __gtype_name__ = other_type_name
+
+    other = OtherThing()
+    child = other.get_children()[0]
+    assert isinstance(child, MainThing)
+    child = child.get_children()[0]
+    assert isinstance(child, Gtk.Box)
+    assert child.props.margin == 24
+    child = child.get_children()[0]
+    assert isinstance(child, Gtk.Label)
+    assert child.props.label == "foo"
diff --git a/tests/test_gtype.py b/tests/test_gtype.py
new file mode 100644 (file)
index 0000000..b9d3349
--- /dev/null
@@ -0,0 +1,114 @@
+from __future__ import absolute_import
+
+import unittest
+
+from gi.repository import GObject
+from gi.repository import GIMarshallingTests
+
+
+class CustomBase(GObject.GObject):
+    pass
+
+
+class CustomChild(CustomBase, GIMarshallingTests.Interface):
+    pass
+
+
+class TestTypeModuleLevelFunctions(unittest.TestCase):
+    def test_type_name(self):
+        self.assertEqual(GObject.type_name(GObject.TYPE_NONE), 'void')
+        self.assertEqual(GObject.type_name(GObject.TYPE_OBJECT), 'GObject')
+        self.assertEqual(GObject.type_name(GObject.TYPE_PYOBJECT), 'PyObject')
+
+    def test_type_from_name(self):
+        # A complete test is not needed here since the TYPE_* defines are created
+        # using this method.
+        self.assertRaises(RuntimeError, GObject.type_from_name, '!NOT_A_REAL_TYPE!')
+        self.assertEqual(GObject.type_from_name('GObject'), GObject.TYPE_OBJECT)
+        self.assertEqual(GObject.type_from_name('GObject'), GObject.GObject.__gtype__)
+
+    def test_type_is_a(self):
+        self.assertTrue(GObject.type_is_a(CustomBase, GObject.TYPE_OBJECT))
+        self.assertTrue(GObject.type_is_a(CustomChild, CustomBase))
+        self.assertTrue(GObject.type_is_a(CustomBase, GObject.GObject))
+        self.assertTrue(GObject.type_is_a(CustomBase.__gtype__, GObject.TYPE_OBJECT))
+        self.assertFalse(GObject.type_is_a(GObject.TYPE_OBJECT, CustomBase))
+        self.assertFalse(GObject.type_is_a(CustomBase, int))  # invalid type
+        self.assertRaises(TypeError, GObject.type_is_a, CustomBase, 1)
+        self.assertRaises(TypeError, GObject.type_is_a, 2, GObject.TYPE_OBJECT)
+        self.assertRaises(TypeError, GObject.type_is_a, 1, 2)
+
+    def test_type_children(self):
+        self.assertEqual(GObject.type_children(CustomBase), [CustomChild.__gtype__])
+        self.assertEqual(len(GObject.type_children(CustomChild)), 0)
+
+    def test_type_interfaces(self):
+        self.assertEqual(len(GObject.type_interfaces(CustomBase)), 0)
+        self.assertEqual(len(GObject.type_interfaces(CustomChild)), 1)
+        self.assertEqual(GObject.type_interfaces(CustomChild), [GIMarshallingTests.Interface.__gtype__])
+
+    def test_type_parent(self):
+        self.assertEqual(GObject.type_parent(CustomChild), CustomBase.__gtype__)
+        self.assertEqual(GObject.type_parent(CustomBase), GObject.TYPE_OBJECT)
+        self.assertRaises(RuntimeError, GObject.type_parent, GObject.GObject)
+
+
+def test_gtype_has_value_table():
+    assert CustomBase.__gtype__.has_value_table()
+    assert not GIMarshallingTests.Interface.__gtype__.has_value_table()
+    assert CustomChild.__gtype__.has_value_table()
+
+
+def test_gtype_is_abstract():
+    assert not CustomBase.__gtype__.is_abstract()
+    assert not GIMarshallingTests.Interface.__gtype__.is_abstract()
+    assert not CustomChild.__gtype__.is_abstract()
+
+
+def test_gtype_is_classed():
+    assert CustomBase.__gtype__.is_classed()
+    assert not GIMarshallingTests.Interface.__gtype__.is_classed()
+    assert CustomChild.__gtype__.is_classed()
+
+
+def test_gtype_is_deep_derivable():
+    assert CustomBase.__gtype__.is_deep_derivable()
+    assert not GIMarshallingTests.Interface.__gtype__.is_deep_derivable()
+    assert CustomChild.__gtype__.is_deep_derivable()
+
+
+def test_gtype_is_derivable():
+    assert CustomBase.__gtype__.is_derivable()
+    assert GIMarshallingTests.Interface.__gtype__.is_derivable()
+    assert CustomChild.__gtype__.is_derivable()
+
+
+def test_gtype_is_value_abstract():
+    assert not CustomBase.__gtype__.is_value_abstract()
+    assert not GIMarshallingTests.Interface.__gtype__.is_value_abstract()
+    assert not CustomChild.__gtype__.is_value_abstract()
+
+
+def test_gtype_is_value_type():
+    assert CustomBase.__gtype__.is_value_type()
+    assert not GIMarshallingTests.Interface.__gtype__.is_value_type()
+    assert CustomChild.__gtype__.is_value_type()
+
+
+def test_gtype_children():
+    assert CustomBase.__gtype__.children == [CustomChild.__gtype__]
+    assert GIMarshallingTests.Interface.__gtype__.children == []
+    assert CustomChild.__gtype__.children == []
+
+
+def test_gtype_depth():
+    assert CustomBase.__gtype__.depth == 2
+    assert GIMarshallingTests.Interface.__gtype__.depth == 2
+    assert CustomChild.__gtype__.depth == 3
+
+
+def test_gtype_interfaces():
+    assert CustomBase.__gtype__.interfaces == []
+    assert GIMarshallingTests.Interface.__gtype__.interfaces == []
+    assert CustomChild.__gtype__.interfaces == \
+        [GIMarshallingTests.Interface.__gtype__]
diff --git a/tests/test_import_machinery.py b/tests/test_import_machinery.py
new file mode 100644 (file)
index 0000000..fc1ba7b
--- /dev/null
@@ -0,0 +1,165 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import sys
+import unittest
+
+import gi.overrides
+import gi.module
+import gi.importer
+from gi._compat import PY2, PY3
+
+from gi.repository import Regress
+
+
+class TestOverrides(unittest.TestCase):
+
+    def test_non_gi(self):
+        class MyClass:
+            pass
+
+        try:
+            gi.overrides.override(MyClass)
+            self.fail('unexpected success of overriding non-GI class')
+        except TypeError as e:
+            self.assertTrue('Can not override a type MyClass' in str(e))
+
+    def test_separate_path(self):
+        # Regress override is in tests/gi/overrides, separate from gi/overrides
+        # https://bugzilla.gnome.org/show_bug.cgi?id=680913
+        self.assertEqual(Regress.REGRESS_OVERRIDE, 42)
+
+    def test_load_overrides(self):
+        mod = gi.module.get_introspection_module('GIMarshallingTests')
+        mod_override = gi.overrides.load_overrides(mod)
+        self.assertTrue(mod_override is not mod)
+        self.assertTrue(mod_override._introspection_module is mod)
+        self.assertEqual(mod_override.OVERRIDES_CONSTANT, 7)
+        self.assertEqual(mod.OVERRIDES_CONSTANT, 42)
+
+    def test_load_no_overrides(self):
+        mod_key = "gi.overrides.GIMarshallingTests"
+        had_mod = mod_key in sys.modules
+        old_mod = sys.modules.get(mod_key)
+        try:
+            # this makes override import fail
+            sys.modules[mod_key] = None
+            mod = gi.module.get_introspection_module('GIMarshallingTests')
+            mod_override = gi.overrides.load_overrides(mod)
+            self.assertTrue(mod_override is mod)
+        finally:
+            del sys.modules[mod_key]
+            if had_mod:
+                sys.modules[mod_key] = old_mod
+
+
+class TestModule(unittest.TestCase):
+    # Tests for gi.module
+
+    def test_get_introspection_module_caching(self):
+        # This test attempts to minimize side effects by
+        # using a DynamicModule directly instead of going though:
+        # from gi.repository import Foo
+
+        # Clear out introspection module cache before running this test.
+        old_modules = gi.module._introspection_modules
+        gi.module._introspection_modules = {}
+
+        mod_name = 'GIMarshallingTests'
+        mod1 = gi.module.get_introspection_module(mod_name)
+        mod2 = gi.module.get_introspection_module(mod_name)
+        self.assertTrue(mod1 is mod2)
+
+        # Restore the previous cache
+        gi.module._introspection_modules = old_modules
+
+    def test_module_dependency_loading(self):
+        # Difficult to because this generally need to run in isolation to make
+        # sure GIMarshallingTests has not yet been loaded. But we can do this with:
+        #  make check TEST_NAMES=test_import_machinery.TestModule.test_module_dependency_loading
+        if 'gi.repository.Gio' in sys.modules:
+            return
+
+        from gi.repository import GIMarshallingTests
+        GIMarshallingTests  # PyFlakes
+
+        self.assertIn('gi.repository.Gio', sys.modules)
+        self.assertIn('gi.repository.GIMarshallingTests', sys.modules)
+
+    def test_static_binding_protection(self):
+        # Importing old static bindings once gi has been imported should not
+        # crash but instead give back a dummy module which produces RuntimeErrors
+        # on access.
+        with self.assertRaises(AttributeError):
+            import gobject
+            gobject.anything
+
+        with self.assertRaises(AttributeError):
+            import glib
+            glib.anything
+
+        with self.assertRaises(AttributeError):
+            import gio
+            gio.anything
+
+        with self.assertRaises(AttributeError):
+            import gtk
+            gtk.anything
+
+        with self.assertRaises(AttributeError):
+            import gtk.gdk
+            gtk.gdk.anything
+
+
+class TestImporter(unittest.TestCase):
+    def test_invalid_repository_module_name(self):
+        with self.assertRaises(ImportError) as context:
+            from gi.repository import InvalidGObjectRepositoryModuleName
+            InvalidGObjectRepositoryModuleName  # pyflakes
+
+        exception_string = str(context.exception)
+
+        self.assertTrue('InvalidGObjectRepositoryModuleName' in exception_string)
+
+        # The message of the custom exception in gi/importer.py is eaten in Python <3.3
+        if sys.version_info >= (3, 3):
+            self.assertTrue('introspection typelib' in exception_string)
+
+    def test_require_version_warning(self):
+        check = gi.importer._check_require_version
+
+        # make sure it doesn't fail at least
+        with check("GLib", 1):
+            from gi.repository import GLib
+            GLib
+
+        # make sure the exception propagates
+        with self.assertRaises(ImportError):
+            with check("InvalidGObjectRepositoryModuleName", 1):
+                from gi.repository import InvalidGObjectRepositoryModuleName
+                InvalidGObjectRepositoryModuleName
+
+    def test_require_version_versiontype(self):
+        import gi
+        with self.assertRaises(ValueError):
+            gi.require_version('GLib', 2.0)
+
+        # Test that unicode strings work in python 2
+        if PY2:
+            gi.require_version('GLib', u'2.0')
+
+        if PY3:
+            with self.assertRaises(ValueError):
+                gi.require_version('GLib', b'2.0')
+
+    def test_require_versions(self):
+        import gi
+        gi.require_versions({'GLib': '2.0', 'Gio': '2.0', 'GObject': '2.0'})
+        from gi.repository import GLib
+        GLib
+
+    def test_get_import_stacklevel(self):
+        gi.importer.get_import_stacklevel(import_hook=True)
+        gi.importer.get_import_stacklevel(import_hook=False)
diff --git a/tests/test_interface.py b/tests/test_interface.py
new file mode 100644 (file)
index 0000000..ca1e0f4
--- /dev/null
@@ -0,0 +1,54 @@
+# -*- Mode: Python -*-
+
+from __future__ import absolute_import
+
+import unittest
+
+from gi.repository import GObject
+import testhelper
+
+
+GUnknown = GObject.type_from_name("TestUnknown")
+Unknown = GUnknown.pytype
+
+
+class MyUnknown(Unknown, testhelper.Interface):
+    some_property = GObject.Property(type=str)
+
+    def __init__(self):
+        Unknown.__init__(self)
+        self.called = False
+
+    def do_iface_method(self):
+        self.called = True
+        Unknown.do_iface_method(self)
+
+
+GObject.type_register(MyUnknown)
+
+
+class MyObject(GObject.GObject, testhelper.Interface):
+    some_property = GObject.Property(type=str)
+
+    def __init__(self):
+        GObject.GObject.__init__(self)
+        self.called = False
+
+    def do_iface_method(self):
+        self.called = True
+
+
+GObject.type_register(MyObject)
+
+
+class TestIfaceImpl(unittest.TestCase):
+
+    def test_reimplement_interface(self):
+        m = MyUnknown()
+        m.iface_method()
+        self.assertEqual(m.called, True)
+
+    def test_implement_interface(self):
+        m = MyObject()
+        m.iface_method()
+        self.assertEqual(m.called, True)
diff --git a/tests/test_internal_api.py b/tests/test_internal_api.py
new file mode 100644 (file)
index 0000000..1ed822c
--- /dev/null
@@ -0,0 +1,131 @@
+# -*- coding: utf-8 -*-
+# -*- Mode: Python -*-
+
+from __future__ import absolute_import
+
+import unittest
+import pytest
+
+from gi.repository import GLib, GObject
+from gi._compat import PY3
+
+import testhelper
+
+
+class PyGObject(GObject.GObject):
+    __gtype_name__ = 'PyGObject'
+    __gproperties__ = {
+        'label': (GObject.TYPE_STRING,
+                  'label property',
+                  'the label of the object',
+                  'default',
+                  GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE),
+        }
+
+    def __init__(self):
+        self._props = {}
+        GObject.GObject.__init__(self)
+        self.set_property('label', 'hello')
+
+    def do_set_property(self, name, value):
+        self._props[name] = value
+
+    def do_get_property(self, name):
+        return self._props[name]
+
+
+def test_parse_constructor_args():
+    assert testhelper.test_parse_constructor_args("foo") == 1
+
+
+class TestObject(unittest.TestCase):
+    def test_create_ctor(self):
+        o = PyGObject()
+        self.assertTrue(isinstance(o, GObject.Object))
+        self.assertTrue(isinstance(o, PyGObject))
+
+        # has expected property
+        self.assertEqual(o.props.label, 'hello')
+        o.props.label = 'goodbye'
+        self.assertEqual(o.props.label, 'goodbye')
+        self.assertRaises(AttributeError, getattr, o.props, 'nosuchprop')
+
+    def test_pyobject_new_test_type(self):
+        o = testhelper.create_test_type()
+        self.assertTrue(isinstance(o, PyGObject))
+
+        # has expected property
+        self.assertEqual(o.props.label, 'hello')
+        o.props.label = 'goodbye'
+        self.assertEqual(o.props.label, 'goodbye')
+        self.assertRaises(AttributeError, getattr, o.props, 'nosuchprop')
+
+    def test_new_refcount(self):
+        # TODO: justify why this should be 2
+        self.assertEqual(testhelper.test_g_object_new(), 2)
+
+
+class TestGValueConversion(unittest.TestCase):
+    def test_int(self):
+        self.assertEqual(testhelper.test_value(0), 0)
+        self.assertEqual(testhelper.test_value(5), 5)
+        self.assertEqual(testhelper.test_value(-5), -5)
+        self.assertEqual(testhelper.test_value(GLib.MAXINT32), GLib.MAXINT32)
+        self.assertEqual(testhelper.test_value(GLib.MININT32), GLib.MININT32)
+
+    def test_str(self):
+        self.assertEqual(testhelper.test_value('hello'), 'hello')
+
+    def test_int_array(self):
+        self.assertEqual(testhelper.test_value_array([]), [])
+        self.assertEqual(testhelper.test_value_array([0]), [0])
+        ar = list(range(100))
+        self.assertEqual(testhelper.test_value_array(ar), ar)
+
+    def test_str_array(self):
+        self.assertEqual(testhelper.test_value_array([]), [])
+        self.assertEqual(testhelper.test_value_array(['a']), ['a'])
+        ar = ('aa ' * 1000).split()
+        self.assertEqual(testhelper.test_value_array(ar), ar)
+
+
+class TestErrors(unittest.TestCase):
+    def test_gerror(self):
+        callable_ = lambda: GLib.file_get_contents('/nonexisting ')
+        self.assertRaises(GLib.GError, testhelper.test_gerror_exception, callable_)
+
+    def test_no_gerror(self):
+        callable_ = lambda: GLib.file_get_contents(__file__)
+        self.assertEqual(testhelper.test_gerror_exception(callable_), None)
+
+
+def test_to_unichar_conv():
+    assert testhelper.test_to_unichar_conv(u"A") == 65
+    assert testhelper.test_to_unichar_conv(u"Ä") == 196
+
+    if PY3:
+        with pytest.raises(TypeError):
+            assert testhelper.test_to_unichar_conv(b"\x65")
+    else:
+        assert testhelper.test_to_unichar_conv(b"\x65") == 0x65
+        with pytest.raises(ValueError):
+            assert testhelper.test_to_unichar_conv(b"\xff")
+
+    with pytest.raises(TypeError):
+        testhelper.test_to_unichar_conv(object())
+
+    with pytest.raises(TypeError):
+        testhelper.test_to_unichar_conv(u"AA")
+
+
+def test_constant_strip_prefix():
+    assert testhelper.constant_strip_prefix("foo", "bar") == "foo"
+    assert testhelper.constant_strip_prefix("foo", "f") == "oo"
+    assert testhelper.constant_strip_prefix("foo", "f") == "oo"
+    assert testhelper.constant_strip_prefix("ha2foo", "ha") == "a2foo"
+    assert testhelper.constant_strip_prefix("2foo", "ha") == "2foo"
+    assert testhelper.constant_strip_prefix("bla_foo", "bla") == "_foo"
+
+
+def test_state_ensure_release():
+    testhelper.test_state_ensure_release()
diff --git a/tests/test_iochannel.py b/tests/test_iochannel.py
new file mode 100644 (file)
index 0000000..56a0aea
--- /dev/null
@@ -0,0 +1,478 @@
+# -*- Mode: Python -*-
+# encoding: UTF-8
+
+from __future__ import absolute_import
+
+import os
+import unittest
+import tempfile
+import os.path
+import shutil
+import warnings
+
+try:
+    import fcntl
+except ImportError:
+    fcntl = None
+
+from gi.repository import GLib
+from gi import PyGIDeprecationWarning
+
+
+class IOChannel(unittest.TestCase):
+    def setUp(self):
+        self.workdir = tempfile.mkdtemp()
+
+        self.testutf8 = os.path.join(self.workdir, 'testutf8.txt')
+        with open(self.testutf8, 'wb') as f:
+            f.write(u'''hello â™¥ world
+second line
+
+À demain!'''.encode('UTF-8'))
+
+        self.testlatin1 = os.path.join(self.workdir, 'testlatin1.txt')
+        with open(self.testlatin1, 'wb') as f:
+            f.write(b'''hell\xf8 world
+second line
+
+\xc0 demain!''')
+
+        self.testout = os.path.join(self.workdir, 'testout.txt')
+
+    def tearDown(self):
+        shutil.rmtree(self.workdir)
+
+    def test_file_readline_utf8(self):
+        ch = GLib.IOChannel(filename=self.testutf8)
+        self.assertEqual(ch.get_encoding(), 'UTF-8')
+        self.assertTrue(ch.get_close_on_unref())
+        self.assertEqual(ch.readline(), 'hello â™¥ world\n')
+        self.assertEqual(ch.get_buffer_condition(), GLib.IOCondition.IN)
+        self.assertEqual(ch.readline(), 'second line\n')
+        self.assertEqual(ch.readline(), '\n')
+        self.assertEqual(ch.readline(), 'À demain!')
+        self.assertEqual(ch.get_buffer_condition(), 0)
+        self.assertEqual(ch.readline(), '')
+        ch.shutdown(True)
+
+    def test_file_readline_latin1(self):
+        ch = GLib.IOChannel(filename=self.testlatin1, mode='r')
+        ch.set_encoding('latin1')
+        self.assertEqual(ch.get_encoding(), 'latin1')
+        self.assertEqual(ch.readline(), 'hellø world\n')
+        self.assertEqual(ch.readline(), 'second line\n')
+        self.assertEqual(ch.readline(), '\n')
+        self.assertEqual(ch.readline(), 'À demain!')
+        ch.shutdown(True)
+
+    def test_file_iter(self):
+        items = []
+        ch = GLib.IOChannel(filename=self.testutf8)
+        for item in ch:
+            items.append(item)
+        self.assertEqual(len(items), 4)
+        self.assertEqual(items[0], 'hello â™¥ world\n')
+        ch.shutdown(True)
+
+    def test_file_readlines(self):
+        ch = GLib.IOChannel(filename=self.testutf8)
+        lines = ch.readlines()
+        # Note, this really ought to be 4, but the static bindings add an extra
+        # empty one
+        self.assertGreaterEqual(len(lines), 4)
+        self.assertLessEqual(len(lines), 5)
+        self.assertEqual(lines[0], 'hello â™¥ world\n')
+        self.assertEqual(lines[3], 'À demain!')
+        if len(lines) == 4:
+            self.assertEqual(lines[4], '')
+
+    def test_file_read(self):
+        ch = GLib.IOChannel(filename=self.testutf8)
+        with open(self.testutf8, 'rb') as f:
+            self.assertEqual(ch.read(), f.read())
+
+        ch = GLib.IOChannel(filename=self.testutf8)
+        with open(self.testutf8, 'rb') as f:
+            self.assertEqual(ch.read(10), f.read(10))
+
+        ch = GLib.IOChannel(filename=self.testutf8)
+        with open(self.testutf8, 'rb') as f:
+            self.assertEqual(ch.read(max_count=15), f.read(15))
+
+    def test_seek(self):
+        ch = GLib.IOChannel(filename=self.testutf8)
+        ch.seek(2)
+        self.assertEqual(ch.read(3), b'llo')
+
+        ch.seek(2, 0)  # SEEK_SET
+        self.assertEqual(ch.read(3), b'llo')
+
+        ch.seek(1, 1)  # SEEK_CUR, skip the space
+        self.assertEqual(ch.read(3), b'\xe2\x99\xa5')
+
+        ch.seek(2, 2)  # SEEK_END
+        # FIXME: does not work currently
+        # self.assertEqual(ch.read(2), b'n!')
+
+        # invalid whence value
+        self.assertRaises(ValueError, ch.seek, 0, 3)
+        ch.shutdown(True)
+
+    def test_file_write(self):
+        ch = GLib.IOChannel(filename=self.testout, mode='w')
+        ch.set_encoding('latin1')
+        ch.write('hellø world\n')
+        ch.shutdown(True)
+        ch = GLib.IOChannel(filename=self.testout, mode='a')
+        ch.set_encoding('latin1')
+        ch.write('À demain!')
+        ch.shutdown(True)
+
+        with open(self.testout, 'rb') as f:
+            self.assertEqual(f.read().decode('latin1'), u'hellø world\nÀ demain!')
+
+    def test_file_writelines(self):
+        ch = GLib.IOChannel(filename=self.testout, mode='w')
+        ch.writelines(['foo', 'bar\n', 'baz\n', 'end'])
+        ch.shutdown(True)
+
+        with open(self.testout, 'r') as f:
+            self.assertEqual(f.read(), 'foobar\nbaz\nend')
+
+    def test_buffering(self):
+        writer = GLib.IOChannel(filename=self.testout, mode='w')
+        writer.set_encoding(None)
+        self.assertTrue(writer.get_buffered())
+        self.assertGreater(writer.get_buffer_size(), 10)
+
+        reader = GLib.IOChannel(filename=self.testout, mode='r')
+
+        # does not get written immediately on buffering
+        writer.write('abc')
+        self.assertEqual(reader.read(), b'')
+        writer.flush()
+        self.assertEqual(reader.read(), b'abc')
+
+        # does get written immediately without buffering
+        writer.set_buffered(False)
+        writer.write('def')
+        self.assertEqual(reader.read(), b'def')
+
+        # writes after buffer overflow
+        writer.set_buffer_size(10)
+        writer.write('0123456789012')
+        self.assertTrue(reader.read().startswith(b'012'))
+        writer.flush()
+        reader.read()  # ignore bits written after flushing
+
+        # closing flushes
+        writer.set_buffered(True)
+        writer.write('ghi')
+        writer.shutdown(True)
+        self.assertEqual(reader.read(), b'ghi')
+        reader.shutdown(True)
+
+    @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows")
+    def test_fd_read(self):
+        (r, w) = os.pipe()
+
+        ch = GLib.IOChannel(filedes=r)
+        ch.set_encoding(None)
+        ch.set_flags(ch.get_flags() | GLib.IOFlags.NONBLOCK)
+        self.assertNotEqual(ch.get_flags() | GLib.IOFlags.NONBLOCK, 0)
+        self.assertEqual(ch.read(), b'')
+        os.write(w, b'\x01\x02')
+        self.assertEqual(ch.read(), b'\x01\x02')
+
+        # now test blocking case, after closing the write end
+        ch.set_flags(GLib.IOFlags(ch.get_flags() & ~GLib.IOFlags.NONBLOCK))
+        os.write(w, b'\x03\x04')
+        os.close(w)
+        self.assertEqual(ch.read(), b'\x03\x04')
+
+        ch.shutdown(True)
+
+    @unittest.skipUnless(fcntl, "no fcntl")
+    def test_fd_write(self):
+        (r, w) = os.pipe()
+        fcntl.fcntl(r, fcntl.F_SETFL, fcntl.fcntl(r, fcntl.F_GETFL) | os.O_NONBLOCK)
+
+        ch = GLib.IOChannel(filedes=w, mode='w')
+        ch.set_encoding(None)
+        ch.set_buffered(False)
+        ch.write(b'\x01\x02')
+        self.assertEqual(os.read(r, 10), b'\x01\x02')
+
+        # now test blocking case, after closing the write end
+        fcntl.fcntl(r, fcntl.F_SETFL, fcntl.fcntl(r, fcntl.F_GETFL) & ~os.O_NONBLOCK)
+        ch.write(b'\x03\x04')
+        ch.shutdown(True)
+        self.assertEqual(os.read(r, 10), b'\x03\x04')
+        os.close(r)
+
+    @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows")
+    def test_deprecated_method_add_watch_no_data(self):
+        (r, w) = os.pipe()
+
+        ch = GLib.IOChannel(filedes=r)
+        ch.set_encoding(None)
+        ch.set_flags(ch.get_flags() | GLib.IOFlags.NONBLOCK)
+
+        cb_reads = []
+
+        def cb(channel, condition):
+            self.assertEqual(channel, ch)
+            self.assertEqual(condition, GLib.IOCondition.IN)
+            cb_reads.append(channel.read())
+            if len(cb_reads) == 2:
+                ml.quit()
+            return True
+
+        # io_add_watch() method is deprecated, use GLib.io_add_watch
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            ch.add_watch(GLib.IOCondition.IN, cb, priority=GLib.PRIORITY_HIGH)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+
+        def write():
+            os.write(w, b'a')
+            GLib.idle_add(lambda: os.write(w, b'b') and False)
+
+        ml = GLib.MainLoop()
+        GLib.idle_add(write)
+        GLib.timeout_add(2000, ml.quit)
+        ml.run()
+
+        self.assertEqual(cb_reads, [b'a', b'b'])
+
+    @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows")
+    def test_deprecated_method_add_watch_data_priority(self):
+        (r, w) = os.pipe()
+
+        ch = GLib.IOChannel(filedes=r)
+        ch.set_encoding(None)
+        ch.set_flags(ch.get_flags() | GLib.IOFlags.NONBLOCK)
+
+        cb_reads = []
+
+        def cb(channel, condition, data):
+            self.assertEqual(channel, ch)
+            self.assertEqual(condition, GLib.IOCondition.IN)
+            self.assertEqual(data, 'hello')
+            cb_reads.append(channel.read())
+            if len(cb_reads) == 2:
+                ml.quit()
+            return True
+
+        ml = GLib.MainLoop()
+        # io_add_watch() method is deprecated, use GLib.io_add_watch
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            id = ch.add_watch(GLib.IOCondition.IN, cb, 'hello', priority=GLib.PRIORITY_HIGH)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+
+        def write():
+            os.write(w, b'a')
+            GLib.idle_add(lambda: os.write(w, b'b') and False)
+
+        GLib.idle_add(write)
+        GLib.timeout_add(2000, ml.quit)
+        ml.run()
+
+        self.assertEqual(cb_reads, [b'a', b'b'])
+
+    @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows")
+    def test_add_watch_no_data(self):
+        (r, w) = os.pipe()
+
+        ch = GLib.IOChannel(filedes=r)
+        ch.set_encoding(None)
+        ch.set_flags(ch.get_flags() | GLib.IOFlags.NONBLOCK)
+
+        cb_reads = []
+
+        def cb(channel, condition):
+            self.assertEqual(channel, ch)
+            self.assertEqual(condition, GLib.IOCondition.IN)
+            cb_reads.append(channel.read())
+            if len(cb_reads) == 2:
+                ml.quit()
+            return True
+
+        id = GLib.io_add_watch(ch, GLib.PRIORITY_HIGH, GLib.IOCondition.IN, cb)
+
+        ml = GLib.MainLoop()
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+
+        def write():
+            os.write(w, b'a')
+            GLib.idle_add(lambda: os.write(w, b'b') and False)
+
+        GLib.idle_add(write)
+        GLib.timeout_add(2000, ml.quit)
+        ml.run()
+
+        self.assertEqual(cb_reads, [b'a', b'b'])
+
+    @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows")
+    def test_add_watch_with_data(self):
+        (r, w) = os.pipe()
+
+        ch = GLib.IOChannel(filedes=r)
+        ch.set_encoding(None)
+        ch.set_flags(ch.get_flags() | GLib.IOFlags.NONBLOCK)
+
+        cb_reads = []
+
+        def cb(channel, condition, data):
+            self.assertEqual(channel, ch)
+            self.assertEqual(condition, GLib.IOCondition.IN)
+            self.assertEqual(data, 'hello')
+            cb_reads.append(channel.read())
+            if len(cb_reads) == 2:
+                ml.quit()
+            return True
+
+        id = GLib.io_add_watch(ch, GLib.PRIORITY_HIGH, GLib.IOCondition.IN, cb, 'hello')
+
+        ml = GLib.MainLoop()
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+
+        def write():
+            os.write(w, b'a')
+            GLib.idle_add(lambda: os.write(w, b'b') and False)
+
+        GLib.idle_add(write)
+        GLib.timeout_add(2000, ml.quit)
+        ml.run()
+
+        self.assertEqual(cb_reads, [b'a', b'b'])
+
+    @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows")
+    def test_add_watch_with_multi_data(self):
+        (r, w) = os.pipe()
+
+        ch = GLib.IOChannel(filedes=r)
+        ch.set_encoding(None)
+        ch.set_flags(ch.get_flags() | GLib.IOFlags.NONBLOCK)
+
+        cb_reads = []
+
+        def cb(channel, condition, data1, data2, data3):
+            self.assertEqual(channel, ch)
+            self.assertEqual(condition, GLib.IOCondition.IN)
+            self.assertEqual(data1, 'a')
+            self.assertEqual(data2, 'b')
+            self.assertEqual(data3, 'c')
+            cb_reads.append(channel.read())
+            if len(cb_reads) == 2:
+                ml.quit()
+            return True
+
+        id = GLib.io_add_watch(ch, GLib.PRIORITY_HIGH, GLib.IOCondition.IN, cb,
+                               'a', 'b', 'c')
+
+        ml = GLib.MainLoop()
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+
+        def write():
+            os.write(w, b'a')
+            GLib.idle_add(lambda: os.write(w, b'b') and False)
+
+        GLib.idle_add(write)
+        GLib.timeout_add(2000, ml.quit)
+        ml.run()
+
+        self.assertEqual(cb_reads, [b'a', b'b'])
+
+    @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows")
+    def test_deprecated_add_watch_no_data(self):
+        (r, w) = os.pipe()
+
+        ch = GLib.IOChannel(filedes=r)
+        ch.set_encoding(None)
+        ch.set_flags(ch.get_flags() | GLib.IOFlags.NONBLOCK)
+
+        cb_reads = []
+
+        def cb(channel, condition):
+            self.assertEqual(channel, ch)
+            self.assertEqual(condition, GLib.IOCondition.IN)
+            cb_reads.append(channel.read())
+            if len(cb_reads) == 2:
+                ml.quit()
+            return True
+
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            id = GLib.io_add_watch(ch, GLib.IOCondition.IN, cb, priority=GLib.PRIORITY_HIGH)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+
+        ml = GLib.MainLoop()
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+
+        def write():
+            os.write(w, b'a')
+            GLib.idle_add(lambda: os.write(w, b'b') and False)
+
+        GLib.idle_add(write)
+        GLib.timeout_add(2000, ml.quit)
+        ml.run()
+
+        self.assertEqual(cb_reads, [b'a', b'b'])
+
+    @unittest.skipIf(os.name == "nt", "NONBLOCK not implemented on Windows")
+    def test_deprecated_add_watch_with_data(self):
+        (r, w) = os.pipe()
+
+        ch = GLib.IOChannel(filedes=r)
+        ch.set_encoding(None)
+        ch.set_flags(ch.get_flags() | GLib.IOFlags.NONBLOCK)
+
+        cb_reads = []
+
+        def cb(channel, condition, data):
+            self.assertEqual(channel, ch)
+            self.assertEqual(condition, GLib.IOCondition.IN)
+            self.assertEqual(data, 'hello')
+            cb_reads.append(channel.read())
+            if len(cb_reads) == 2:
+                ml.quit()
+            return True
+
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            id = GLib.io_add_watch(ch, GLib.IOCondition.IN, cb, 'hello',
+                                   priority=GLib.PRIORITY_HIGH)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+
+        ml = GLib.MainLoop()
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+
+        def write():
+            os.write(w, b'a')
+            GLib.idle_add(lambda: os.write(w, b'b') and False)
+
+        GLib.idle_add(write)
+
+        GLib.timeout_add(2000, ml.quit)
+        ml.run()
+
+        self.assertEqual(cb_reads, [b'a', b'b'])
+
+    def test_backwards_compat_flags(self):
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', PyGIDeprecationWarning)
+
+            self.assertEqual(GLib.IOCondition.IN, GLib.IO_IN)
+            self.assertEqual(GLib.IOFlags.NONBLOCK, GLib.IO_FLAG_NONBLOCK)
+            self.assertEqual(GLib.IOFlags.IS_SEEKABLE, GLib.IO_FLAG_IS_SEEKABLE)
+            self.assertEqual(GLib.IOStatus.NORMAL, GLib.IO_STATUS_NORMAL)
diff --git a/tests/test_mainloop.py b/tests/test_mainloop.py
new file mode 100644 (file)
index 0000000..40a8b97
--- /dev/null
@@ -0,0 +1,72 @@
+# -*- Mode: Python -*-
+
+from __future__ import absolute_import
+
+import os
+import select
+import signal
+import unittest
+
+from gi.repository import GLib
+
+from .helper import capture_exceptions
+
+
+class TestMainLoop(unittest.TestCase):
+
+    @unittest.skipUnless(hasattr(os, "fork"), "no os.fork available")
+    def test_exception_handling(self):
+        pipe_r, pipe_w = os.pipe()
+
+        pid = os.fork()
+        if pid == 0:
+            os.close(pipe_w)
+            select.select([pipe_r], [], [])
+            os.close(pipe_r)
+            os._exit(1)
+
+        def child_died(pid, status, loop):
+            loop.quit()
+            raise Exception("deadbabe")
+
+        loop = GLib.MainLoop()
+        GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, child_died, loop)
+
+        os.close(pipe_r)
+        os.write(pipe_w, b"Y")
+        os.close(pipe_w)
+
+        with capture_exceptions() as exc:
+            loop.run()
+
+        assert len(exc) == 1
+        assert exc[0].type is Exception
+        assert exc[0].value.args[0] == "deadbabe"
+
+    @unittest.skipUnless(hasattr(os, "fork"), "no os.fork available")
+    @unittest.skipIf(os.environ.get("PYGI_TEST_GDB"), "SIGINT stops gdb")
+    def test_sigint(self):
+        r, w = os.pipe()
+        pid = os.fork()
+        if pid == 0:
+            # wait for the parent process loop to start
+            os.read(r, 1)
+            os.close(r)
+
+            os.kill(os.getppid(), signal.SIGINT)
+            os._exit(0)
+
+        def notify_child():
+            # tell the child that it can kill the parent
+            os.write(w, b"X")
+            os.close(w)
+
+        GLib.idle_add(notify_child)
+        loop = GLib.MainLoop()
+        try:
+            loop.run()
+            self.fail('expected KeyboardInterrupt exception')
+        except KeyboardInterrupt:
+            pass
+        self.assertFalse(loop.is_running())
+        os.waitpid(pid, 0)
diff --git a/tests/test_object_marshaling.py b/tests/test_object_marshaling.py
new file mode 100644 (file)
index 0000000..3d8ebb8
--- /dev/null
@@ -0,0 +1,595 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import unittest
+import weakref
+import gc
+import sys
+import warnings
+
+from gi.repository import GObject
+from gi.repository import GIMarshallingTests
+
+try:
+    from gi.repository import Regress
+except ImportError:
+    Regress = None
+
+
+class StrongRef(object):
+    # A class that behaves like weakref.ref but holds a strong reference.
+    # This allows re-use of the VFuncsBase by swapping out the ObjectRef
+    # class var with either weakref.ref or StrongRef.
+
+    def __init__(self, obj):
+        self.obj = obj
+
+    def __call__(self):
+        return self.obj
+
+
+class VFuncsBase(GIMarshallingTests.Object):
+    # Class which generically implements the vfuncs used for reference counting tests
+    # in a way that can be easily sub-classed and modified.
+
+    #: Object type used by this class for testing
+    #: This can be GObject.Object or GObject.InitiallyUnowned
+    Object = GObject.Object
+
+    #: Reference type used by this class for holding refs to in/out objects.
+    #: This can be set to weakref.ref or StrongRef
+    ObjectRef = weakref.ref
+
+    def __init__(self):
+        super(VFuncsBase, self).__init__()
+
+        #: Hold ref of input or output python wrappers
+        self.object_ref = None
+
+        #: store grefcount of input object
+        self.in_object_grefcount = None
+        self.in_object_is_floating = None
+
+    def do_vfunc_return_object_transfer_none(self):
+        # Return an object but keep a python reference to it.
+        obj = self.Object()
+        self.object_ref = self.ObjectRef(obj)
+        return obj
+
+    def do_vfunc_return_object_transfer_full(self):
+        # Return an object and hand off the reference to the caller.
+        obj = self.Object()
+        self.object_ref = self.ObjectRef(obj)
+        return obj
+
+    def do_vfunc_out_object_transfer_none(self):
+        # Same as do_vfunc_return_object_transfer_none but the pygi
+        # internals convert the return here into an out arg.
+        obj = self.Object()
+        self.object_ref = self.ObjectRef(obj)
+        return obj
+
+    def do_vfunc_out_object_transfer_full(self):
+        # Same as do_vfunc_return_object_transfer_full but the pygi
+        # internals convert the return here into an out arg.
+        obj = self.Object()
+        self.object_ref = self.ObjectRef(obj)
+        return obj
+
+    def do_vfunc_in_object_transfer_none(self, obj):
+        # 'obj' will have a python wrapper as well as still held
+        # by the caller.
+        self.object_ref = self.ObjectRef(obj)
+        self.in_object_grefcount = obj.__grefcount__
+        self.in_object_is_floating = obj.is_floating()
+
+    def do_vfunc_in_object_transfer_full(self, obj):
+        # 'obj' will now be owned by the Python GObject wrapper.
+        # When obj goes out of scope and is collected, the GObject
+        # should also be fully released.
+        self.object_ref = self.ObjectRef(obj)
+        self.in_object_grefcount = obj.__grefcount__
+        self.in_object_is_floating = obj.is_floating()
+
+
+class TestVFuncsWithObjectArg(unittest.TestCase):
+    # Basic set of tests which work on non-floating objects which python does
+    # not keep an additional reference of.
+
+    class VFuncs(VFuncsBase):
+        # Object for testing non-floating objects without holding any refs.
+        Object = GObject.Object
+        ObjectRef = weakref.ref
+
+    def test_vfunc_self_arg_ref_count(self):
+        # Check to make sure vfunc "self" arguments don't leak.
+        vfuncs = self.VFuncs()
+        vfuncs_ref = weakref.ref(vfuncs)
+        vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()  # Use any vfunc to test this.
+
+        gc.collect()
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(sys.getrefcount(vfuncs), 2)
+        self.assertEqual(vfuncs.__grefcount__, 1)
+
+        del vfuncs
+        gc.collect()
+        self.assertTrue(vfuncs_ref() is None)
+
+    def test_vfunc_return_object_transfer_none(self):
+        # This tests a problem case where the vfunc returns a GObject owned solely by Python
+        # but the argument is marked as transfer-none.
+        # In this case pygobject marshaling adds an additional ref and gives a warning
+        # of a potential leak. If this occures it is really a bug in the underlying library
+        # but pygobject tries to react to this in a reasonable way.
+        vfuncs = self.VFuncs()
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+            if hasattr(sys, "getrefcount"):
+                self.assertTrue(issubclass(warn[0].category, RuntimeWarning))
+
+        # The ref count of the GObject returned to the caller (get_ref_info_for_vfunc_return_object_transfer_none)
+        # should be a single floating ref
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_out_object_transfer_none(self):
+        # Same as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+            if hasattr(sys, "getrefcount"):
+                self.assertTrue(issubclass(warn[0].category, RuntimeWarning))
+
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_return_object_transfer_full(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+        # The vfunc caller receives full ownership of a single ref which should not
+        # be floating.
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_out_object_transfer_full(self):
+        # Same as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_in_object_transfer_none(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+
+        gc.collect()
+        self.assertEqual(vfuncs.in_object_grefcount, 2)  # initial + python wrapper
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(ref_count, 1)  # ensure python wrapper released
+        self.assertFalse(is_floating)
+
+        gc.collect()
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_in_object_transfer_full(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+
+        gc.collect()
+
+        # python wrapper should take sole ownership of the gobject
+        self.assertEqual(vfuncs.in_object_grefcount, 1)
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        # ensure python wrapper took ownership and released, after vfunc was complete
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(ref_count, 0)
+        self.assertFalse(is_floating)
+
+        gc.collect()
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+
+class TestVFuncsWithFloatingArg(unittest.TestCase):
+    # All tests here work with a floating object by using InitiallyUnowned as the argument
+
+    class VFuncs(VFuncsBase):
+        # Object for testing non-floating objects without holding any refs.
+        Object = GObject.InitiallyUnowned
+        ObjectRef = weakref.ref
+
+    @unittest.skipUnless(hasattr(sys, "getrefcount"), "refcount specific")
+    def test_vfunc_return_object_transfer_none_with_floating(self):
+        # Python is expected to return a single floating reference without warning.
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_none()
+
+        # The ref count of the GObject returned to the caller (get_ref_info_for_vfunc_return_object_transfer_none)
+        # should be a single floating ref
+        self.assertEqual(ref_count, 1)
+        self.assertTrue(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    @unittest.skipUnless(hasattr(sys, "getrefcount"), "refcount specific")
+    def test_vfunc_out_object_transfer_none_with_floating(self):
+        # Same as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+
+        self.assertEqual(ref_count, 1)
+        self.assertTrue(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_return_object_transfer_full_with_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+        # The vfunc caller receives full ownership of a single ref.
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_out_object_transfer_full_with_floating(self):
+        # Same as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_in_object_transfer_none_with_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+
+        gc.collect()
+
+        # python wrapper should maintain the object as floating and add an additional ref
+        self.assertEqual(vfuncs.in_object_grefcount, 2)
+        self.assertTrue(vfuncs.in_object_is_floating)
+
+        # vfunc caller should only have a single floating ref after the vfunc finishes
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(ref_count, 1)
+        self.assertTrue(is_floating)
+
+        gc.collect()
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_in_object_transfer_full_with_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+
+        gc.collect()
+
+        # python wrapper sinks and owns the gobject
+        self.assertEqual(vfuncs.in_object_grefcount, 1)
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        # ensure python wrapper took ownership and released
+        if hasattr(sys, "getrefcount"):
+            self.assertEqual(ref_count, 0)
+        self.assertFalse(is_floating)
+
+        gc.collect()
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+
+class TestVFuncsWithHeldObjectArg(unittest.TestCase):
+    # Same tests as TestVFuncsWithObjectArg except we hold
+    # onto the python object reference in all cases.
+
+    class VFuncs(VFuncsBase):
+        # Object for testing non-floating objects with a held ref.
+        Object = GObject.Object
+        ObjectRef = StrongRef
+
+    def test_vfunc_return_object_transfer_none_with_held_object(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_none()
+
+        # Python holds the single gobject ref in 'vfuncs.object_ref'
+        # Because of this, we do not expect a floating ref or a ref increase.
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        # The actual grefcount should stay at 1 even after the vfunc return.
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_out_object_transfer_none_with_held_object(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_return_object_transfer_full_with_held_object(self):
+        # The vfunc caller receives full ownership which should not
+        # be floating. However, the held python wrapper also has a ref.
+
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+        # Ref count from the perspective of C after the vfunc is called
+        # The vfunc caller receives a new reference which should not
+        # be floating. However, the held python wrapper also has a ref.
+        self.assertEqual(ref_count, 2)
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        # The vfunc caller should have decremented its reference.
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_out_object_transfer_full_with_held_object(self):
+        # Same test as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+        # Ref count from the perspective of C after the vfunc is called
+        # The vfunc caller receives a new reference which should not
+        # be floating. However, the held python wrapper also has a ref.
+        self.assertEqual(ref_count, 2)
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        # The vfunc caller should have decremented its reference.
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_in_object_transfer_none_with_held_object(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+
+        gc.collect()
+
+        # Ref count inside vfunc from the perspective of Python
+        self.assertEqual(vfuncs.in_object_grefcount, 2)  # initial + python wrapper
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(ref_count, 2)  # kept after vfunc + held python wrapper
+        self.assertFalse(is_floating)
+
+        # Current ref count after C cleans up its reference
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_in_object_transfer_full_with_held_object(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+
+        gc.collect()
+
+        # Ref count inside vfunc from the perspective of Python
+        self.assertEqual(vfuncs.in_object_grefcount, 1)  # python wrapper takes ownership of the gobject
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+
+class TestVFuncsWithHeldFloatingArg(unittest.TestCase):
+    # Tests for a floating object which we hold a reference to the python wrapper
+    # on the VFuncs test class.
+
+    class VFuncs(VFuncsBase):
+        # Object for testing floating objects with a held ref.
+        Object = GObject.InitiallyUnowned
+        ObjectRef = StrongRef
+
+    def test_vfunc_return_object_transfer_none_with_held_floating(self):
+        # Python holds onto the wrapper which basically means the floating ref
+        # should also be owned by python.
+
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_none()
+
+        # This is a borrowed ref from what is held in python.
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        # The actual grefcount should stay at 1 even after the vfunc return.
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_out_object_transfer_none_with_held_floating(self):
+        # Same as above
+
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_return_object_transfer_full_with_held_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(ref_count, 2)
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        # vfunc wrapper destroyes ref it was given
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_out_object_transfer_full_with_held_floating(self):
+        # Same test as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(ref_count, 2)
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        # vfunc wrapper destroyes ref it was given
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_in_floating_transfer_none_with_held_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+        gc.collect()
+
+        # Ref count inside vfunc from the perspective of Python
+        self.assertTrue(vfuncs.in_object_is_floating)
+        self.assertEqual(vfuncs.in_object_grefcount, 2)  # python wrapper sinks and owns the gobject
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertTrue(is_floating)
+        self.assertEqual(ref_count, 2)  # floating + held by wrapper
+
+        # Current ref count after C cleans up its reference
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_in_floating_transfer_full_with_held_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+        gc.collect()
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(vfuncs.in_object_grefcount, 1)  # python wrapper sinks and owns the gobject
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(ref_count, 1)  # held by wrapper
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+
+@unittest.skipIf(Regress is None, 'Regress is required')
+class TestArgumentTypeErrors(unittest.TestCase):
+    def test_object_argument_type_error(self):
+        # ensure TypeError is raised for things which are not GObjects
+        obj = Regress.TestObj()
+        obj.set_bare(GObject.Object())
+        obj.set_bare(None)
+
+        self.assertRaises(TypeError, obj.set_bare, object())
+        self.assertRaises(TypeError, obj.set_bare, 42)
+        self.assertRaises(TypeError, obj.set_bare, 'not an object')
+
+    def test_instance_argument_error(self):
+        # ensure TypeError is raised for non Regress.TestObj instances.
+        obj = Regress.TestObj()
+        self.assertEqual(Regress.TestObj.instance_method(obj), -1)
+        self.assertRaises(TypeError, Regress.TestObj.instance_method, object())
+        self.assertRaises(TypeError, Regress.TestObj.instance_method, GObject.Object())
+        self.assertRaises(TypeError, Regress.TestObj.instance_method, 42)
+        self.assertRaises(TypeError, Regress.TestObj.instance_method, 'not an object')
+
+    def test_instance_argument_base_type_error(self):
+        # ensure TypeError is raised when a base type is passed to something
+        # expecting a derived type
+        obj = Regress.TestSubObj()
+        self.assertEqual(Regress.TestSubObj.instance_method(obj), 0)
+        self.assertRaises(TypeError, Regress.TestSubObj.instance_method, GObject.Object())
+        self.assertRaises(TypeError, Regress.TestSubObj.instance_method, Regress.TestObj())
diff --git a/tests/test_option.py b/tests/test_option.py
new file mode 100644 (file)
index 0000000..251eb3a
--- /dev/null
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+
+from __future__ import absolute_import
+
+import unittest
+
+# py3k has StringIO in a different module
+try:
+    from StringIO import StringIO
+    StringIO  # pyflakes
+except ImportError:
+    from io import StringIO
+
+from gi.repository import GLib
+
+from .helper import capture_exceptions
+
+
+class TestOption(unittest.TestCase):
+
+    def setUp(self):
+        self.parser = GLib.option.OptionParser("NAMES...",
+                                               description="Option unit test")
+        self.parser.add_option("-t", "--test", help="Unit test option",
+                               action="store_false", dest="test", default=True)
+        self.parser.add_option("--g-fatal-warnings",
+                               action="store_true",
+                               dest="fatal_warnings",
+                               help="dummy"),
+
+    def _create_group(self):
+        def option_callback(option, opt, value, parser):
+            raise Exception("foo")
+
+        group = GLib.option.OptionGroup(
+            "unittest", "Unit test options", "Show all unittest options",
+            option_list=[
+                GLib.option.make_option("-f", "-u", "--file", "--unit-file",
+                                        type="filename",
+                                        dest="unit_file",
+                                        help="Unit test option"),
+                GLib.option.make_option("--test-integer",
+                                        type="int",
+                                        dest="test_integer",
+                                        help="Unit integer option"),
+                GLib.option.make_option("--callback-failure-test",
+                                        action="callback",
+                                        callback=option_callback,
+                                        dest="test_integer",
+                                        help="Unit integer option"),
+            ])
+        group.add_option("-t", "--test",
+                         action="store_false",
+                         dest="test",
+                         default=True,
+                         help="Unit test option")
+        self.parser.add_option_group(group)
+        return group
+
+    def test_integer(self):
+        self._create_group()
+        options, args = self.parser.parse_args(
+            ["--test-integer", "42", "bla"])
+        assert options.test_integer == 42
+        assert args == ["bla"]
+
+    def test_file(self):
+        self._create_group()
+
+        options, args = self.parser.parse_args(
+            ["--file", "fn", "bla"])
+        assert options.unit_file == "fn"
+        assert args == ["bla"]
+
+    def test_mixed(self):
+        self._create_group()
+
+        options, args = self.parser.parse_args(
+            ["--file", "fn", "--test-integer", "12", "--test",
+             "--g-fatal-warnings", "nope"])
+
+        assert options.unit_file == "fn"
+        assert options.test_integer == 12
+        assert options.test is False
+        assert options.fatal_warnings is True
+        assert args == ["nope"]
+
+    def test_parse_args(self):
+        options, args = self.parser.parse_args([])
+        self.assertFalse(args)
+
+        options, args = self.parser.parse_args(["foo"])
+        self.assertEqual(args, ["foo"])
+
+        options, args = self.parser.parse_args(["foo", "bar"])
+        self.assertEqual(args, ["foo", "bar"])
+
+    def test_parse_args_double_dash(self):
+        options, args = self.parser.parse_args(["--", "-xxx"])
+        self.assertEqual(args, ["--", "-xxx"])
+
+    def test_parse_args_group(self):
+        group = self._create_group()
+
+        options, args = self.parser.parse_args(
+            ["--test", "-f", "test"])
+
+        self.assertFalse(options.test)
+        self.assertEqual(options.unit_file, "test")
+
+        self.assertTrue(group.values.test)
+        self.assertFalse(self.parser.values.test)
+        self.assertEqual(group.values.unit_file, "test")
+        self.assertFalse(args)
+
+    def test_option_value_error(self):
+        self._create_group()
+        self.assertRaises(GLib.option.OptionValueError, self.parser.parse_args,
+                          ["--test-integer=text"])
+
+    def test_bad_option_error(self):
+        self.assertRaises(GLib.option.BadOptionError,
+                          self.parser.parse_args,
+                          ["--unknwon-option"])
+
+    def test_option_group_constructor(self):
+        self.assertRaises(TypeError, GLib.option.OptionGroup)
+
+    def test_standard_error(self):
+        self._create_group()
+
+        with capture_exceptions() as exc:
+            self.parser.parse_args(["--callback-failure-test"])
+
+        assert len(exc) == 1
+        assert exc[0].value.args[0] == "foo"
diff --git a/tests/test_ossig.py b/tests/test_ossig.py
new file mode 100644 (file)
index 0000000..b59f2f5
--- /dev/null
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+# Copyright 2017 Christoph Reiter
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import
+
+import os
+import signal
+import unittest
+import threading
+from contextlib import contextmanager
+
+try:
+    from gi.repository import Gtk
+except ImportError:
+    Gtk = None
+from gi.repository import Gio, GLib
+from gi._ossighelper import wakeup_on_signal, register_sigint_fallback
+
+
+class TestOverridesWakeupOnAlarm(unittest.TestCase):
+
+    @contextmanager
+    def _run_with_timeout(self, timeout, abort_func):
+        failed = []
+
+        def fail():
+            abort_func()
+            failed.append(1)
+            return True
+
+        fail_id = GLib.timeout_add(timeout, fail)
+        try:
+            yield
+        finally:
+            GLib.source_remove(fail_id)
+        self.assertFalse(failed)
+
+    def test_basic(self):
+        self.assertEqual(signal.set_wakeup_fd(-1), -1)
+        with wakeup_on_signal():
+            pass
+        self.assertEqual(signal.set_wakeup_fd(-1), -1)
+
+    def test_in_thread(self):
+        failed = []
+
+        def target():
+            try:
+                with wakeup_on_signal():
+                    pass
+            except:
+                failed.append(1)
+
+        t = threading.Thread(target=target)
+        t.start()
+        t.join(5)
+        self.assertFalse(failed)
+
+    @unittest.skipIf(os.name == "nt", "not on Windows")
+    def test_glib_mainloop(self):
+        loop = GLib.MainLoop()
+        signal.signal(signal.SIGALRM, lambda *args: loop.quit())
+        GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001)
+
+        with self._run_with_timeout(2000, loop.quit):
+            loop.run()
+
+    @unittest.skipIf(os.name == "nt", "not on Windows")
+    def test_gio_application(self):
+        app = Gio.Application()
+        signal.signal(signal.SIGALRM, lambda *args: app.quit())
+        GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001)
+
+        with self._run_with_timeout(2000, app.quit):
+            app.hold()
+            app.connect("activate", lambda *args: None)
+            app.run()
+
+    @unittest.skipIf(Gtk is None or os.name == "nt", "not on Windows")
+    def test_gtk_main(self):
+        signal.signal(signal.SIGALRM, lambda *args: Gtk.main_quit())
+        GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001)
+
+        with self._run_with_timeout(2000, Gtk.main_quit):
+            Gtk.main()
+
+    @unittest.skipIf(Gtk is None or os.name == "nt", "not on Windows")
+    def test_gtk_dialog_run(self):
+        w = Gtk.Window()
+        d = Gtk.Dialog(transient_for=w)
+        signal.signal(signal.SIGALRM, lambda *args: d.destroy())
+        GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001)
+
+        with self._run_with_timeout(2000, d.destroy):
+            d.run()
+
+
+class TestSigintFallback(unittest.TestCase):
+
+    def setUp(self):
+        self.assertEqual(
+            signal.getsignal(signal.SIGINT), signal.default_int_handler)
+
+    def tearDown(self):
+        self.assertEqual(
+            signal.getsignal(signal.SIGINT), signal.default_int_handler)
+
+    def test_replace_handler_and_restore_nested(self):
+        with register_sigint_fallback(lambda: None):
+            new_handler = signal.getsignal(signal.SIGINT)
+            self.assertNotEqual(new_handler, signal.default_int_handler)
+            with register_sigint_fallback(lambda: None):
+                self.assertTrue(signal.getsignal(signal.SIGINT) is new_handler)
+        self.assertEqual(
+            signal.getsignal(signal.SIGINT), signal.default_int_handler)
+
+    def test_no_replace_if_not_default(self):
+        new_handler = lambda *args: None
+        signal.signal(signal.SIGINT, new_handler)
+        try:
+            with register_sigint_fallback(lambda: None):
+                self.assertTrue(signal.getsignal(signal.SIGINT) is new_handler)
+                with register_sigint_fallback(lambda: None):
+                    self.assertTrue(
+                        signal.getsignal(signal.SIGINT) is new_handler)
+            self.assertTrue(signal.getsignal(signal.SIGINT) is new_handler)
+        finally:
+            signal.signal(signal.SIGINT, signal.default_int_handler)
+
+    def test_noop_in_threads(self):
+        failed = []
+
+        def target():
+            try:
+                with register_sigint_fallback(lambda: None):
+                    with register_sigint_fallback(lambda: None):
+                        self.assertTrue(
+                            signal.getsignal(signal.SIGINT) is
+                            signal.default_int_handler)
+            except:
+                failed.append(1)
+
+        t = threading.Thread(target=target)
+        t.start()
+        t.join(5)
+        self.assertFalse(failed)
+
+    @unittest.skipIf(os.name == "nt", "not on Windows")
+    def test_no_replace_if_set_by_glib(self):
+        id_ = GLib.unix_signal_add(
+            GLib.PRIORITY_DEFAULT, signal.SIGINT, lambda *args: None)
+        try:
+            # signal.getsignal() doesn't pick up that unix_signal_add()
+            # has changed the handler, but we should anyway.
+            self.assertEqual(
+                signal.getsignal(signal.SIGINT), signal.default_int_handler)
+            with register_sigint_fallback(lambda: None):
+                self.assertEqual(
+                    signal.getsignal(signal.SIGINT),
+                    signal.default_int_handler)
+            self.assertEqual(
+                signal.getsignal(signal.SIGINT), signal.default_int_handler)
+        finally:
+            GLib.source_remove(id_)
+            signal.signal(signal.SIGINT, signal.SIG_DFL)
+            signal.signal(signal.SIGINT, signal.default_int_handler)
diff --git a/tests/test_overrides_gdk.py b/tests/test_overrides_gdk.py
new file mode 100644 (file)
index 0000000..1dfe8e3
--- /dev/null
@@ -0,0 +1,210 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import os
+import sys
+import unittest
+
+import gi.overrides
+from gi import PyGIDeprecationWarning
+
+try:
+    from gi.repository import Gdk, GdkPixbuf, Gtk
+    Gdk_version = Gdk._version
+except ImportError:
+    Gdk = None
+    Gdk_version = None
+
+from .helper import capture_glib_deprecation_warnings
+
+
+@unittest.skipUnless(Gdk, 'Gdk not available')
+class TestGdk(unittest.TestCase):
+
+    @unittest.skipIf(sys.platform == "darwin" or os.name == "nt", "crashes")
+    @unittest.skipIf(Gdk_version == "4.0", "not in gdk4")
+    def test_constructor(self):
+        attribute = Gdk.WindowAttr()
+        attribute.window_type = Gdk.WindowType.CHILD
+        attributes_mask = Gdk.WindowAttributesType.X | \
+            Gdk.WindowAttributesType.Y
+        window = Gdk.Window(None, attribute, attributes_mask)
+        self.assertEqual(window.get_window_type(), Gdk.WindowType.CHILD)
+
+    @unittest.skipIf(Gdk_version == "4.0", "not in gdk4")
+    def test_color(self):
+        color = Gdk.Color(100, 200, 300)
+        self.assertEqual(color.red, 100)
+        self.assertEqual(color.green, 200)
+        self.assertEqual(color.blue, 300)
+        with capture_glib_deprecation_warnings():
+            self.assertEqual(color, Gdk.Color(100, 200, 300))
+        self.assertNotEqual(color, Gdk.Color(1, 2, 3))
+
+    @unittest.skipIf(Gdk_version == "4.0", "not in gdk4")
+    def test_color_floats(self):
+        self.assertEqual(Gdk.Color(13107, 21845, 65535),
+                         Gdk.Color.from_floats(0.2, 1.0 / 3.0, 1.0))
+
+        self.assertEqual(Gdk.Color(13107, 21845, 65535).to_floats(),
+                         (0.2, 1.0 / 3.0, 1.0))
+
+        self.assertEqual(Gdk.RGBA(0.2, 1.0 / 3.0, 1.0, 0.5).to_color(),
+                         Gdk.Color.from_floats(0.2, 1.0 / 3.0, 1.0))
+
+        self.assertEqual(Gdk.RGBA.from_color(Gdk.Color(13107, 21845, 65535)),
+                         Gdk.RGBA(0.2, 1.0 / 3.0, 1.0, 1.0))
+
+    def test_rgba(self):
+        self.assertEqual(Gdk.RGBA, gi.overrides.Gdk.RGBA)
+        rgba = Gdk.RGBA(0.1, 0.2, 0.3, 0.4)
+        self.assertEqual(rgba, Gdk.RGBA(0.1, 0.2, 0.3, 0.4))
+        self.assertNotEqual(rgba, Gdk.RGBA(0.0, 0.2, 0.3, 0.4))
+        self.assertEqual(rgba.red, 0.1)
+        self.assertEqual(rgba.green, 0.2)
+        self.assertEqual(rgba.blue, 0.3)
+        self.assertEqual(rgba.alpha, 0.4)
+        rgba.green = 0.9
+        self.assertEqual(rgba.green, 0.9)
+
+        # Iterator/tuple convsersion
+        self.assertEqual(tuple(Gdk.RGBA(0.1, 0.2, 0.3, 0.4)),
+                         (0.1, 0.2, 0.3, 0.4))
+
+    def test_event(self):
+        event = Gdk.Event.new(Gdk.EventType.CONFIGURE)
+        self.assertEqual(event.type, Gdk.EventType.CONFIGURE)
+        self.assertEqual(event.send_event, 0)
+
+        event = Gdk.Event()
+        event.type = Gdk.EventType.SCROLL
+        self.assertRaises(AttributeError, lambda: getattr(event, 'foo_bar'))
+
+    def test_event_touch(self):
+        event = Gdk.Event.new(Gdk.EventType.TOUCH_BEGIN)
+        self.assertEqual(event.type, Gdk.EventType.TOUCH_BEGIN)
+
+        # emulating_pointer is unique to touch events
+        self.assertFalse(event.emulating_pointer)
+        self.assertFalse(event.touch.emulating_pointer)
+
+        event.emulating_pointer = True
+        self.assertTrue(event.emulating_pointer)
+        self.assertTrue(event.touch.emulating_pointer)
+
+    def test_event_setattr(self):
+        event = Gdk.Event.new(Gdk.EventType.DRAG_MOTION)
+        event.x_root, event.y_root = 0, 5
+        self.assertEqual(event.dnd.x_root, 0)
+        self.assertEqual(event.dnd.y_root, 5)
+        self.assertEqual(event.x_root, 0)
+        self.assertEqual(event.y_root, 5)
+
+        # this used to work, keep it that way
+        self.assertFalse(hasattr(event, "foo_bar"))
+        event.foo_bar = 42
+
+    def test_event_repr(self):
+        event = Gdk.Event.new(Gdk.EventType.CONFIGURE)
+        self.assertTrue("CONFIGURE" in repr(event))
+
+    @unittest.skipIf(Gdk_version == "4.0", "not in gdk4")
+    def test_event_structures(self):
+        def button_press_cb(button, event):
+            self.assertTrue(isinstance(event, Gdk.EventButton))
+            self.assertTrue(event.type == Gdk.EventType.BUTTON_PRESS)
+            self.assertEqual(event.send_event, 0)
+            self.assertEqual(event.get_state(), Gdk.ModifierType.CONTROL_MASK)
+            self.assertEqual(event.get_root_coords(), (2, 5))
+
+            event.time = 12345
+            self.assertEqual(event.get_time(), 12345)
+
+        w = Gtk.Window()
+        b = Gtk.Button()
+        b.connect('button-press-event', button_press_cb)
+        w.add(b)
+        b.show()
+        b.realize()
+        Gdk.test_simulate_button(b.get_window(),
+                                 2, 5,
+                                 0,
+                                 Gdk.ModifierType.CONTROL_MASK,
+                                 Gdk.EventType.BUTTON_PRESS)
+
+    @unittest.skipIf(Gdk_version == "4.0", "not in gdk4")
+    def test_cursor(self):
+        self.assertEqual(Gdk.Cursor, gi.overrides.Gdk.Cursor)
+        with capture_glib_deprecation_warnings():
+            c = Gdk.Cursor(Gdk.CursorType.WATCH)
+        self.assertNotEqual(c, None)
+        with capture_glib_deprecation_warnings():
+            c = Gdk.Cursor(cursor_type=Gdk.CursorType.WATCH)
+        self.assertNotEqual(c, None)
+
+        display_manager = Gdk.DisplayManager.get()
+        display = display_manager.get_default_display()
+
+        test_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB,
+                                           False,
+                                           8,
+                                           5,
+                                           10)
+
+        with capture_glib_deprecation_warnings() as warn:
+            c = Gdk.Cursor(display,
+                           test_pixbuf,
+                           y=0, x=0)
+            self.assertNotEqual(c, None)
+
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGIDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*new_from_pixbuf.*')
+
+        self.assertRaises(ValueError, Gdk.Cursor, 1, 2, 3)
+
+    def test_flags(self):
+        self.assertEqual(Gdk.ModifierType.META_MASK | 0, 0x10000000)
+        self.assertEqual(hex(Gdk.ModifierType.META_MASK), '0x10000000')
+        self.assertEqual(str(Gdk.ModifierType.META_MASK),
+                         '<flags GDK_META_MASK of type Gdk.ModifierType>')
+
+        self.assertEqual(Gdk.ModifierType.RELEASE_MASK | 0, 0x40000000)
+        self.assertEqual(hex(Gdk.ModifierType.RELEASE_MASK), '0x40000000')
+        self.assertEqual(str(Gdk.ModifierType.RELEASE_MASK),
+                         '<flags GDK_RELEASE_MASK of type Gdk.ModifierType>')
+
+        self.assertEqual(Gdk.ModifierType.RELEASE_MASK | Gdk.ModifierType.META_MASK, 0x50000000)
+        self.assertEqual(str(Gdk.ModifierType.RELEASE_MASK | Gdk.ModifierType.META_MASK),
+                         '<flags GDK_META_MASK | GDK_RELEASE_MASK of type Gdk.ModifierType>')
+
+    @unittest.skipIf(Gdk_version == "4.0", "not in gdk4")
+    def test_color_parse(self):
+        with capture_glib_deprecation_warnings():
+            c = Gdk.color_parse('#00FF80')
+        self.assertEqual(c.red, 0)
+        self.assertEqual(c.green, 65535)
+        self.assertEqual(c.blue, 32896)
+        self.assertEqual(Gdk.color_parse('bogus'), None)
+
+    @unittest.skipIf(Gdk_version == "4.0", "not in gdk4")
+    def test_color_representations(self):
+        # __repr__ should generate a string which is parsable when possible
+        # http://docs.python.org/2/reference/datamodel.html#object.__repr__
+        color = Gdk.Color(red=65535, green=32896, blue=1)
+        self.assertEqual(eval(repr(color)), color)
+
+        rgba = Gdk.RGBA(red=1.0, green=0.8, blue=0.6, alpha=0.4)
+        self.assertEqual(eval(repr(rgba)), rgba)
+
+    def test_rectangle_functions(self):
+        # https://bugzilla.gnome.org/show_bug.cgi?id=756364
+        a = Gdk.Rectangle()
+        b = Gdk.Rectangle()
+        self.assertTrue(isinstance(Gdk.rectangle_union(a, b), Gdk.Rectangle))
+        intersect, rect = Gdk.rectangle_intersect(a, b)
+        self.assertTrue(isinstance(rect, Gdk.Rectangle))
+        self.assertTrue(isinstance(intersect, bool))
diff --git a/tests/test_overrides_gdkpixbuf.py b/tests/test_overrides_gdkpixbuf.py
new file mode 100644 (file)
index 0000000..5b2d370
--- /dev/null
@@ -0,0 +1,49 @@
+# Copyright 2018 Christoph Reiter <reiter.christoph@gmail.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from __future__ import absolute_import
+
+import pytest
+
+from gi import PyGIDeprecationWarning
+GdkPixbuf = pytest.importorskip("gi.repository.GdkPixbuf")
+
+
+def test_new_from_data():
+    width = 600
+    height = 32769
+    pixbuf = GdkPixbuf.Pixbuf.new(
+        GdkPixbuf.Colorspace.RGB, True, 8, width, height)
+    pixels = pixbuf.get_pixels()
+    new_pixbuf = GdkPixbuf.Pixbuf.new_from_data(
+        pixels, GdkPixbuf.Colorspace.RGB, True, 8,
+        pixbuf.get_width(), pixbuf.get_height(), pixbuf.get_rowstride())
+    del pixbuf
+    del pixels
+    new_pixels = new_pixbuf.get_pixels()
+    assert len(new_pixels) == width * height * 4
+
+
+def test_new_from_data_deprecated_args():
+    GdkPixbuf.Pixbuf.new_from_data(b"1234", 0, True, 8, 1, 1, 4)
+    GdkPixbuf.Pixbuf.new_from_data(b"1234", 0, True, 8, 1, 1, 4, None)
+    with pytest.warns(PyGIDeprecationWarning, match=".*destroy_fn.*"):
+        GdkPixbuf.Pixbuf.new_from_data(
+            b"1234", 0, True, 8, 1, 1, 4, object(), object(), object())
+    with pytest.warns(PyGIDeprecationWarning, match=".*destroy_fn_data.*"):
+        GdkPixbuf.Pixbuf.new_from_data(
+            b"1234", 0, True, 8, 1, 1, 4, object(), object(), object())
diff --git a/tests/test_overrides_gio.py b/tests/test_overrides_gio.py
new file mode 100644 (file)
index 0000000..b6516f9
--- /dev/null
@@ -0,0 +1,346 @@
+from __future__ import absolute_import
+
+import random
+import platform
+
+import pytest
+
+from gi.repository import Gio, GObject
+from gi._compat import cmp
+
+
+class Item(GObject.Object):
+    _id = 0
+
+    def __init__(self, **kwargs):
+        super(Item, self).__init__(**kwargs)
+        Item._id += 1
+        self._id = self._id
+
+    def __repr__(self):
+        return str(self._id)
+
+
+class NamedItem(Item):
+
+    name = GObject.Property(type=str, default='')
+
+    def __repr__(self):
+        return self.props.name
+
+
+def test_list_store_sort():
+    store = Gio.ListStore()
+    items = [NamedItem(name=n) for n in "cabx"]
+    sorted_items = sorted(items, key=lambda i: i.props.name)
+
+    user_data = [object(), object()]
+
+    def sort_func(a, b, *args):
+        assert list(args) == user_data
+        assert isinstance(a, NamedItem)
+        assert isinstance(b, NamedItem)
+        return cmp(a.props.name, b.props.name)
+
+    store[:] = items
+    assert store[:] != sorted_items
+    store.sort(sort_func, *user_data)
+    assert store[:] == sorted_items
+
+
+def test_list_store_insert_sorted():
+    store = Gio.ListStore()
+    items = [NamedItem(name=n) for n in "cabx"]
+    sorted_items = sorted(items, key=lambda i: i.props.name)
+
+    user_data = [object(), object()]
+
+    def sort_func(a, b, *args):
+        assert list(args) == user_data
+        assert isinstance(a, NamedItem)
+        assert isinstance(b, NamedItem)
+        return cmp(a.props.name, b.props.name)
+
+    for item in items:
+        index = store.insert_sorted(item, sort_func, *user_data)
+        assert isinstance(index, int)
+    assert store[:] == sorted_items
+
+
+def test_list_model_len():
+    model = Gio.ListStore.new(Item)
+    assert len(model) == 0
+    assert not model
+    for i in range(1, 10):
+        model.append(Item())
+        assert len(model) == i
+    assert model
+    model.remove_all()
+    assert not model
+    assert len(model) == 0
+
+
+def test_list_model_get_item_simple():
+    model = Gio.ListStore.new(Item)
+    with pytest.raises(IndexError):
+        model[0]
+    first_item = Item()
+    model.append(first_item)
+    assert model[0] is first_item
+    assert model[-1] is first_item
+    second_item = Item()
+    model.append(second_item)
+    assert model[1] is second_item
+    assert model[-1] is second_item
+    assert model[-2] is first_item
+    with pytest.raises(IndexError):
+        model[-3]
+
+    with pytest.raises(TypeError):
+        model[object()]
+
+
+def test_list_model_get_item_slice():
+    model = Gio.ListStore.new(Item)
+    source = [Item() for i in range(30)]
+    for i in source:
+        model.append(i)
+    assert model[1:10] == source[1:10]
+    assert model[1:-2] == source[1:-2]
+    assert model[-4:-1] == source[-4:-1]
+    assert model[-100:-1] == source[-100:-1]
+    assert model[::-1] == source[::-1]
+    assert model[:] == source[:]
+
+
+def test_list_model_contains():
+    model = Gio.ListStore.new(Item)
+    item = Item()
+    model.append(item)
+    assert item in model
+    assert Item() not in model
+    with pytest.raises(TypeError):
+        object() in model
+    with pytest.raises(TypeError):
+        None in model
+
+
+def test_list_model_iter():
+    model = Gio.ListStore.new(Item)
+    item = Item()
+    model.append(item)
+
+    it = iter(model)
+    assert next(it) is item
+    repr(item)
+
+
+def test_list_store_delitem_simple():
+    store = Gio.ListStore.new(Item)
+    store.append(Item())
+    del store[0]
+    assert not store
+    with pytest.raises(IndexError):
+        del store[0]
+    with pytest.raises(IndexError):
+        del store[-1]
+
+    store.append(Item())
+    with pytest.raises(IndexError):
+        del store[-2]
+    del store[-1]
+    assert not store
+
+    source = [Item(), Item()]
+    store.append(source[0])
+    store.append(source[1])
+    del store[-1]
+    assert store[:] == [source[0]]
+
+    with pytest.raises(TypeError):
+        del store[object()]
+
+
+def test_list_store_delitem_slice():
+
+    def do_del(count, key):
+
+        events = []
+
+        def on_changed(m, *args):
+            events.append(args)
+
+        store = Gio.ListStore.new(Item)
+        source = [Item() for i in range(count)]
+        for item in source:
+            store.append(item)
+        store.connect("items-changed", on_changed)
+        source.__delitem__(key)
+        store.__delitem__(key)
+        assert source == store[:]
+        return events
+
+    values = [None, 1, -15, 3, -2, 0, -3, 5, 7]
+    variants = set()
+    for i in range(500):
+        start = random.choice(values)
+        stop = random.choice(values)
+        step = random.choice(values)
+        length = abs(random.choice(values) or 0)
+        if step == 0:
+            step += 1
+        variants.add((length, start, stop, step))
+
+    for length, start, stop, step in variants:
+        do_del(length, slice(start, stop, step))
+
+    # basics
+    do_del(10, slice(None, None, None))
+    do_del(10, slice(None, None, None))
+    do_del(10, slice(None, None, -1))
+    do_del(10, slice(0, 5, None))
+    do_del(10, slice(0, 10, 1))
+    do_del(10, slice(0, 10, 2))
+    do_del(10, slice(14, 2, -1))
+
+    # test some fast paths
+    assert do_del(100, slice(None, None, None)) == [(0, 100, 0)]
+    assert do_del(100, slice(None, None, -1)) == [(0, 100, 0)]
+    assert do_del(100, slice(0, 50, 1)) == [(0, 50, 0)]
+
+
+def test_list_store_setitem_simple():
+
+    store = Gio.ListStore.new(Item)
+    first = Item()
+    store.append(first)
+
+    class Wrong(GObject.Object):
+        pass
+
+    with pytest.raises(TypeError):
+        store[0] = object()
+    with pytest.raises(TypeError):
+        store[0] = None
+    with pytest.raises(TypeError):
+        store[0] = Wrong()
+
+    assert store[:] == [first]
+
+    new = Item()
+    store[0] = new
+    assert len(store) == 1
+    store[-1] = Item()
+    assert len(store) == 1
+
+    with pytest.raises(IndexError):
+        store[1] = Item()
+    with pytest.raises(IndexError):
+        store[-2] = Item()
+
+    store = Gio.ListStore.new(Item)
+    source = [Item(), Item(), Item()]
+    for item in source:
+        store.append(item)
+    new = Item()
+    store[1] = new
+    assert store[:] == [source[0], new, source[2]]
+
+    with pytest.raises(TypeError):
+        store[object()] = Item()
+
+
+def test_list_store_setitem_slice():
+
+    def do_set(count, key, new_count):
+        if count == 0 and key.step is not None \
+                and platform.python_implementation() == "PyPy":
+            # https://bitbucket.org/pypy/pypy/issues/2804
+            return
+        store = Gio.ListStore.new(Item)
+        source = [Item() for i in range(count)]
+        new = [Item() for i in range(new_count)]
+        for item in source:
+            store.append(item)
+        source_error = None
+        try:
+            source.__setitem__(key, new)
+        except ValueError as e:
+            source_error = type(e)
+
+        store_error = None
+        try:
+            store.__setitem__(key, new)
+        except Exception as e:
+            store_error = type(e)
+
+        assert source_error == store_error
+        assert source == store[:]
+
+    values = [None, 1, -15, 3, -2, 0, 3, 4, 100]
+    variants = set()
+    for i in range(500):
+        start = random.choice(values)
+        stop = random.choice(values)
+        step = random.choice(values)
+        length = abs(random.choice(values) or 0)
+        new = random.choice(values) or 0
+        if step == 0:
+            step += 1
+        variants.add((length, start, stop, step, new))
+
+    for length, start, stop, step, new in variants:
+        do_set(length, slice(start, stop, step), new)
+
+    # basics
+    do_set(10, slice(None, None, None), 20)
+    do_set(10, slice(None, None, None), 0)
+    do_set(10, slice(None, None, -1), 20)
+    do_set(10, slice(None, None, -1), 10)
+    do_set(10, slice(0, 5, None), 20)
+    do_set(10, slice(0, 10, 1), 0)
+
+    # test iterators
+    store = Gio.ListStore.new(Item)
+    store[:] = iter([Item() for i in range(10)])
+    assert len(store) == 10
+
+    # make sure we do all or nothing
+    store = Gio.ListStore.new(Item)
+    with pytest.raises(TypeError):
+        store[:] = [Item(), object()]
+    assert len(store) == 0
+
+
+def test_action_map_add_action_entries():
+    actionmap = Gio.SimpleActionGroup()
+
+    test_data = []
+
+    def f(action, parameter, data):
+        test_data.append('test back')
+
+    actionmap.add_action_entries((
+        ("simple", f),
+        ("with_type", f, "i"),
+        ("with_state", f, "s", "'left'", f),
+    ))
+    assert actionmap.has_action("simple")
+    assert actionmap.has_action("with_type")
+    assert actionmap.has_action("with_state")
+    actionmap.add_action_entries((
+        ("with_user_data", f),
+    ), "user_data")
+    assert actionmap.has_action("with_user_data")
+
+    with pytest.raises(TypeError):
+        actionmap.add_action_entries((
+            ("invaild_type_string", f, 'asdf'),
+        ))
+    with pytest.raises(ValueError):
+        actionmap.add_action_entries((
+            ("stateless_with_change_state", f, None, None, f),
+        ))
+
+    actionmap.activate_action("simple")
+    assert test_data[0] == 'test back'
diff --git a/tests/test_overrides_glib.py b/tests/test_overrides_glib.py
new file mode 100644 (file)
index 0000000..3467253
--- /dev/null
@@ -0,0 +1,589 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import gc
+import unittest
+
+import gi
+from gi.repository import GLib
+from gi._compat import long_, integer_types
+
+
+class TestGVariant(unittest.TestCase):
+    def test_create_simple(self):
+        variant = GLib.Variant('i', 42)
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertEqual(variant.get_int32(), 42)
+
+        variant = GLib.Variant('s', '')
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertEqual(variant.get_string(), '')
+
+        variant = GLib.Variant('s', 'hello')
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertEqual(variant.get_string(), 'hello')
+
+    def test_create_variant(self):
+        variant = GLib.Variant('v', GLib.Variant('i', 42))
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertTrue(isinstance(variant.get_variant(), GLib.Variant))
+        self.assertEqual(variant.get_type_string(), 'v')
+        self.assertEqual(variant.get_variant().get_type_string(), 'i')
+        self.assertEqual(variant.get_variant().get_int32(), 42)
+
+        variant = GLib.Variant('v', GLib.Variant('v', GLib.Variant('i', 42)))
+        self.assertEqual(variant.get_type_string(), 'v')
+        self.assertEqual(variant.get_variant().get_type_string(), 'v')
+        self.assertEqual(variant.get_variant().get_variant().get_type_string(), 'i')
+        self.assertEqual(variant.get_variant().get_variant().get_int32(), 42)
+
+    def test_create_tuple(self):
+        variant = GLib.Variant('()', ())
+        self.assertEqual(variant.get_type_string(), '()')
+        self.assertEqual(variant.n_children(), 0)
+
+        variant = GLib.Variant('(i)', (3,))
+        self.assertEqual(variant.get_type_string(), '(i)')
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertEqual(variant.n_children(), 1)
+        self.assertTrue(isinstance(variant.get_child_value(0), GLib.Variant))
+        self.assertEqual(variant.get_child_value(0).get_int32(), 3)
+
+        variant = GLib.Variant('(ss)', ('mec', 'mac'))
+        self.assertEqual(variant.get_type_string(), '(ss)')
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertTrue(isinstance(variant.get_child_value(0), GLib.Variant))
+        self.assertTrue(isinstance(variant.get_child_value(1), GLib.Variant))
+        self.assertEqual(variant.get_child_value(0).get_string(), 'mec')
+        self.assertEqual(variant.get_child_value(1).get_string(), 'mac')
+
+        # nested tuples
+        variant = GLib.Variant('((si)(ub))', (('hello', -1), (42, True)))
+        self.assertEqual(variant.get_type_string(), '((si)(ub))')
+        self.assertEqual(variant.unpack(), (('hello', -1), (long_(42), True)))
+
+    def test_new_tuple_sink(self):
+        # https://bugzilla.gnome.org/show_bug.cgi?id=735166
+        variant = GLib.Variant.new_tuple(GLib.Variant.new_tuple())
+        del variant
+        gc.collect()
+
+    def test_create_dictionary(self):
+        variant = GLib.Variant('a{si}', {})
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertEqual(variant.get_type_string(), 'a{si}')
+        self.assertEqual(variant.n_children(), 0)
+
+        variant = GLib.Variant('a{si}', {'': 1, 'key1': 2, 'key2': 3})
+        self.assertEqual(variant.get_type_string(), 'a{si}')
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertTrue(isinstance(variant.get_child_value(0), GLib.Variant))
+        self.assertTrue(isinstance(variant.get_child_value(1), GLib.Variant))
+        self.assertTrue(isinstance(variant.get_child_value(2), GLib.Variant))
+        self.assertEqual(variant.unpack(), {'': 1, 'key1': 2, 'key2': 3})
+
+        # nested dictionaries
+        variant = GLib.Variant('a{sa{si}}', {})
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertEqual(variant.get_type_string(), 'a{sa{si}}')
+        self.assertEqual(variant.n_children(), 0)
+
+        d = {'': {'': 1, 'keyn1': 2},
+             'key1': {'key11': 11, 'key12': 12}}
+        variant = GLib.Variant('a{sa{si}}', d)
+        self.assertEqual(variant.get_type_string(), 'a{sa{si}}')
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertEqual(variant.unpack(), d)
+
+    def test_create_array(self):
+        variant = GLib.Variant('ai', [])
+        self.assertEqual(variant.get_type_string(), 'ai')
+        self.assertEqual(variant.n_children(), 0)
+
+        variant = GLib.Variant('ai', [1, 2])
+        self.assertEqual(variant.get_type_string(), 'ai')
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertTrue(isinstance(variant.get_child_value(0), GLib.Variant))
+        self.assertTrue(isinstance(variant.get_child_value(1), GLib.Variant))
+        self.assertEqual(variant.get_child_value(0).get_int32(), 1)
+        self.assertEqual(variant.get_child_value(1).get_int32(), 2)
+
+        variant = GLib.Variant('as', [])
+        self.assertEqual(variant.get_type_string(), 'as')
+        self.assertEqual(variant.n_children(), 0)
+
+        variant = GLib.Variant('as', [''])
+        self.assertEqual(variant.get_type_string(), 'as')
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertTrue(isinstance(variant.get_child_value(0), GLib.Variant))
+        self.assertEqual(variant.get_child_value(0).get_string(), '')
+
+        variant = GLib.Variant('as', ['hello', 'world'])
+        self.assertEqual(variant.get_type_string(), 'as')
+        self.assertTrue(isinstance(variant, GLib.Variant))
+        self.assertTrue(isinstance(variant.get_child_value(0), GLib.Variant))
+        self.assertTrue(isinstance(variant.get_child_value(1), GLib.Variant))
+        self.assertEqual(variant.get_child_value(0).get_string(), 'hello')
+        self.assertEqual(variant.get_child_value(1).get_string(), 'world')
+
+        # nested arrays
+        variant = GLib.Variant('aai', [])
+        self.assertEqual(variant.get_type_string(), 'aai')
+        self.assertEqual(variant.n_children(), 0)
+
+        variant = GLib.Variant('aai', [[]])
+        self.assertEqual(variant.get_type_string(), 'aai')
+        self.assertEqual(variant.n_children(), 1)
+        self.assertEqual(variant.get_child_value(0).n_children(), 0)
+
+        variant = GLib.Variant('aai', [[1, 2], [3, 4, 5]])
+        self.assertEqual(variant.get_type_string(), 'aai')
+        self.assertEqual(variant.unpack(), [[1, 2], [3, 4, 5]])
+
+    def test_create_array_guchar(self):
+        variant = GLib.Variant('ay', [97, 97, 97])
+        assert variant.unpack() == [97, 97, 97]
+
+        variant = GLib.Variant('ay', b'aaa')
+        assert variant.unpack() == [97, 97, 97]
+
+        variant = GLib.Variant('ay', iter([1, 2, 3]))
+        assert variant.unpack() == [1, 2, 3]
+
+        with self.assertRaises(TypeError):
+            GLib.Variant('ay', u'aaa')
+
+        with self.assertRaises(TypeError):
+            GLib.Variant('ay', object())
+
+    def test_create_maybe(self):
+        variant = GLib.Variant('mai', None)
+        self.assertEqual(variant.get_type_string(), 'mai')
+        self.assertEqual(variant.n_children(), 0)
+        self.assertEqual(variant.unpack(), None)
+
+        variant = GLib.Variant('mai', [])
+        self.assertEqual(variant.get_type_string(), 'mai')
+        self.assertEqual(variant.n_children(), 1)
+
+        variant = GLib.Variant('mami', [None])
+        self.assertEqual(variant.get_type_string(), 'mami')
+        self.assertEqual(variant.n_children(), 1)
+
+        variant = GLib.Variant('mami', [None, 13, None])
+        self.assertEqual(variant.get_type_string(), 'mami')
+        self.assertEqual(variant.n_children(), 1)
+        array = variant.get_child_value(0)
+        self.assertEqual(array.n_children(), 3)
+
+        element = array.get_child_value(0)
+        self.assertEqual(element.n_children(), 0)
+        element = array.get_child_value(1)
+        self.assertEqual(element.n_children(), 1)
+        self.assertEqual(element.get_child_value(0).get_int32(), 13)
+        element = array.get_child_value(2)
+        self.assertEqual(element.n_children(), 0)
+
+    def test_create_complex(self):
+        variant = GLib.Variant('(as)', ([],))
+        self.assertEqual(variant.get_type_string(), '(as)')
+        self.assertEqual(variant.n_children(), 1)
+        self.assertEqual(variant.get_child_value(0).n_children(), 0)
+
+        variant = GLib.Variant('(as)', ([''],))
+        self.assertEqual(variant.get_type_string(), '(as)')
+        self.assertEqual(variant.n_children(), 1)
+        self.assertEqual(variant.get_child_value(0).n_children(), 1)
+        self.assertEqual(variant.get_child_value(0).get_child_value(0).get_string(), '')
+
+        variant = GLib.Variant('(as)', (['hello'],))
+        self.assertEqual(variant.get_type_string(), '(as)')
+        self.assertEqual(variant.n_children(), 1)
+        self.assertEqual(variant.get_child_value(0).n_children(), 1)
+        self.assertEqual(variant.get_child_value(0).get_child_value(0).get_string(), 'hello')
+
+        variant = GLib.Variant('a(ii)', [])
+        self.assertEqual(variant.get_type_string(), 'a(ii)')
+        self.assertEqual(variant.n_children(), 0)
+
+        variant = GLib.Variant('a(ii)', [(5, 6)])
+        self.assertEqual(variant.get_type_string(), 'a(ii)')
+        self.assertEqual(variant.n_children(), 1)
+        self.assertEqual(variant.get_child_value(0).n_children(), 2)
+        self.assertEqual(variant.get_child_value(0).get_child_value(0).get_int32(), 5)
+        self.assertEqual(variant.get_child_value(0).get_child_value(1).get_int32(), 6)
+
+        variant = GLib.Variant('(a(ii))', ([],))
+        self.assertEqual(variant.get_type_string(), '(a(ii))')
+        self.assertEqual(variant.n_children(), 1)
+        self.assertEqual(variant.get_child_value(0).n_children(), 0)
+
+        variant = GLib.Variant('(a(ii))', ([(5, 6)],))
+        self.assertEqual(variant.get_type_string(), '(a(ii))')
+        self.assertEqual(variant.n_children(), 1)
+        self.assertEqual(variant.get_child_value(0).n_children(), 1)
+        self.assertEqual(variant.get_child_value(0).get_child_value(0).n_children(), 2)
+        self.assertEqual(variant.get_child_value(0).get_child_value(0).get_child_value(0).get_int32(), 5)
+        self.assertEqual(variant.get_child_value(0).get_child_value(0).get_child_value(1).get_int32(), 6)
+
+        obj = {'a1': (1, True), 'a2': (2, False)}
+        variant = GLib.Variant('a{s(ib)}', obj)
+        self.assertEqual(variant.get_type_string(), 'a{s(ib)}')
+        self.assertEqual(variant.unpack(), obj)
+
+        obj = {'a1': (1, GLib.Variant('b', True)), 'a2': (2, GLib.Variant('y', 255))}
+        variant = GLib.Variant('a{s(iv)}', obj)
+        self.assertEqual(variant.get_type_string(), 'a{s(iv)}')
+        self.assertEqual(variant.unpack(), {'a1': (1, True), 'a2': (2, 255)})
+
+        obj = (1, {'a': {'a1': True, 'a2': False},
+                   'b': {'b1': False},
+                   'c': {}
+                  },
+               'foo')
+        variant = GLib.Variant('(ia{sa{sb}}s)', obj)
+        self.assertEqual(variant.get_type_string(), '(ia{sa{sb}}s)')
+        self.assertEqual(variant.unpack(), obj)
+
+        obj = {"frequency": GLib.Variant('t', 738000000),
+               "hierarchy": GLib.Variant('i', 0),
+               "bandwidth": GLib.Variant('x', 8),
+               "code-rate-hp": GLib.Variant('d', 2.0 / 3.0),
+               "constellation": GLib.Variant('s', "QAM16"),
+               "guard-interval": GLib.Variant('u', 4)}
+        variant = GLib.Variant('a{sv}', obj)
+        self.assertEqual(variant.get_type_string(), 'a{sv}')
+        self.assertEqual(variant.unpack(),
+                         {"frequency": 738000000,
+                          "hierarchy": 0,
+                          "bandwidth": 8,
+                          "code-rate-hp": 2.0 / 3.0,
+                          "constellation": "QAM16",
+                          "guard-interval": 4
+                         })
+
+    def test_create_errors(self):
+        # excess arguments
+        self.assertRaises(TypeError, GLib.Variant, 'i', 42, 3)
+        self.assertRaises(TypeError, GLib.Variant, '(i)', (42, 3))
+
+        # not enough arguments
+        self.assertRaises(TypeError, GLib.Variant, '(ii)', (42,))
+
+        # data type mismatch
+        self.assertRaises(TypeError, GLib.Variant, 'i', 'hello')
+        self.assertRaises(TypeError, GLib.Variant, 's', 42)
+        self.assertRaises(TypeError, GLib.Variant, '(ss)', 'mec', 'mac')
+        self.assertRaises(TypeError, GLib.Variant, '(s)', 'hello')
+
+        # invalid format string
+        self.assertRaises(TypeError, GLib.Variant, 'Q', 1)
+
+        # invalid types
+        self.assertRaises(TypeError, GLib.Variant, '(ii', (42, 3))
+        self.assertRaises(TypeError, GLib.Variant, '(ii))', (42, 3))
+        self.assertRaises(TypeError, GLib.Variant, 'a{si', {})
+        self.assertRaises(TypeError, GLib.Variant, 'a{si}}', {})
+        self.assertRaises(TypeError, GLib.Variant, 'a{iii}', {})
+
+    def test_unpack(self):
+        # simple values
+        res = GLib.Variant.new_int32(-42).unpack()
+        self.assertEqual(res, -42)
+
+        res = GLib.Variant.new_uint64(34359738368).unpack()
+        self.assertEqual(res, 34359738368)
+
+        res = GLib.Variant.new_boolean(True).unpack()
+        self.assertEqual(res, True)
+
+        res = GLib.Variant.new_object_path('/foo/Bar').unpack()
+        self.assertEqual(res, '/foo/Bar')
+
+        # variant
+        res = GLib.Variant('v', GLib.Variant.new_int32(-42)).unpack()
+        self.assertEqual(res, -42)
+
+        GLib.Variant('v', GLib.Variant('v', GLib.Variant('i', 42)))
+        self.assertEqual(res, -42)
+
+        # tuple
+        res = GLib.Variant.new_tuple(GLib.Variant.new_int32(-1),
+                                     GLib.Variant.new_string('hello')).unpack()
+        self.assertEqual(res, (-1, 'hello'))
+
+        # array
+        vb = GLib.VariantBuilder.new(gi._gi.variant_type_from_string('ai'))
+        vb.add_value(GLib.Variant.new_int32(-1))
+        vb.add_value(GLib.Variant.new_int32(3))
+        res = vb.end().unpack()
+        self.assertEqual(res, [-1, 3])
+
+        # dictionary
+        res = GLib.Variant('a{si}', {'key1': 1, 'key2': 2}).unpack()
+        self.assertEqual(res, {'key1': 1, 'key2': 2})
+
+        # maybe
+        v = GLib.Variant('mi', 1)
+        self.assertEqual(v.unpack(), 1)
+        v = GLib.Variant('mi', None)
+        self.assertEqual(v.unpack(), None)
+        v = GLib.Variant('mai', [])
+        self.assertEqual(v.unpack(), [])
+        v = GLib.Variant('m()', ())
+        self.assertEqual(v.unpack(), ())
+        v = GLib.Variant('mami', [None, 1, None])
+        self.assertEqual(v.unpack(), [None, 1, None])
+
+    def test_iteration(self):
+        # array index access
+        vb = GLib.VariantBuilder.new(gi._gi.variant_type_from_string('ai'))
+        vb.add_value(GLib.Variant.new_int32(-1))
+        vb.add_value(GLib.Variant.new_int32(3))
+        v = vb.end()
+
+        self.assertEqual(len(v), 2)
+        self.assertEqual(v[0], -1)
+        self.assertEqual(v[1], 3)
+        self.assertEqual(v[-1], 3)
+        self.assertEqual(v[-2], -1)
+        self.assertRaises(IndexError, v.__getitem__, 2)
+        self.assertRaises(IndexError, v.__getitem__, -3)
+        self.assertRaises(ValueError, v.__getitem__, 'a')
+
+        # array iteration
+        self.assertEqual([x for x in v], [-1, 3])
+        self.assertEqual(list(v), [-1, 3])
+
+        # tuple index access
+        v = GLib.Variant.new_tuple(GLib.Variant.new_int32(-1),
+                                   GLib.Variant.new_string('hello'))
+        self.assertEqual(len(v), 2)
+        self.assertEqual(v[0], -1)
+        self.assertEqual(v[1], 'hello')
+        self.assertEqual(v[-1], 'hello')
+        self.assertEqual(v[-2], -1)
+        self.assertRaises(IndexError, v.__getitem__, 2)
+        self.assertRaises(IndexError, v.__getitem__, -3)
+        self.assertRaises(ValueError, v.__getitem__, 'a')
+
+        # tuple iteration
+        self.assertEqual([x for x in v], [-1, 'hello'])
+        self.assertEqual(tuple(v), (-1, 'hello'))
+
+        # dictionary index access
+        vsi = GLib.Variant('a{si}', {'key1': 1, 'key2': 2})
+        vis = GLib.Variant('a{is}', {1: 'val1', 5: 'val2'})
+
+        self.assertEqual(len(vsi), 2)
+        self.assertEqual(vsi['key1'], 1)
+        self.assertEqual(vsi['key2'], 2)
+        self.assertRaises(KeyError, vsi.__getitem__, 'unknown')
+
+        self.assertEqual(len(vis), 2)
+        self.assertEqual(vis[1], 'val1')
+        self.assertEqual(vis[5], 'val2')
+        self.assertRaises(KeyError, vsi.__getitem__, 3)
+
+        # dictionary iteration
+        self.assertEqual(set(vsi.keys()), set(['key1', 'key2']))
+        self.assertEqual(set(vis.keys()), set([1, 5]))
+
+        # string index access
+        v = GLib.Variant('s', 'hello')
+        self.assertEqual(len(v), 5)
+        self.assertEqual(v[0], 'h')
+        self.assertEqual(v[4], 'o')
+        self.assertEqual(v[-1], 'o')
+        self.assertEqual(v[-5], 'h')
+        self.assertRaises(IndexError, v.__getitem__, 5)
+        self.assertRaises(IndexError, v.__getitem__, -6)
+
+        # string iteration
+        self.assertEqual([x for x in v], ['h', 'e', 'l', 'l', 'o'])
+
+    def test_split_signature(self):
+        self.assertEqual(GLib.Variant.split_signature('()'), [])
+
+        self.assertEqual(GLib.Variant.split_signature('s'), ['s'])
+
+        self.assertEqual(GLib.Variant.split_signature('as'), ['as'])
+
+        self.assertEqual(GLib.Variant.split_signature('(s)'), ['s'])
+
+        self.assertEqual(GLib.Variant.split_signature('(iso)'), ['i', 's', 'o'])
+
+        self.assertEqual(GLib.Variant.split_signature('(s(ss)i(ii))'),
+                         ['s', '(ss)', 'i', '(ii)'])
+
+        self.assertEqual(GLib.Variant.split_signature('(as)'), ['as'])
+
+        self.assertEqual(GLib.Variant.split_signature('(s(ss)iaiaasa(ii))'),
+                         ['s', '(ss)', 'i', 'ai', 'aas', 'a(ii)'])
+
+        self.assertEqual(GLib.Variant.split_signature('(a{iv}(ii)((ss)a{s(ss)}))'),
+                         ['a{iv}', '(ii)', '((ss)a{s(ss)})'])
+
+    def test_hash(self):
+        v1 = GLib.Variant('s', 'somestring')
+        v2 = GLib.Variant('s', 'somestring')
+        v3 = GLib.Variant('s', 'somestring2')
+
+        self.assertTrue(v2 in set([v1, v3]))
+        self.assertTrue(v2 in frozenset([v1, v3]))
+        self.assertTrue(v2 in {v1: '1', v3: '2'})
+
+    def test_compare(self):
+        # Check if identical GVariant are equal
+
+        def assert_equal(vtype, value):
+            self.assertEqual(GLib.Variant(vtype, value), GLib.Variant(vtype, value))
+
+        def assert_not_equal(vtype1, value1, vtype2, value2):
+            self.assertNotEqual(GLib.Variant(vtype1, value1), GLib.Variant(vtype2, value2))
+
+        numbers = ['y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd']
+        for num in numbers:
+            assert_equal(num, 42)
+            assert_not_equal(num, 42, num, 41)
+            assert_not_equal(num, 42, 's', '42')
+
+        assert_equal('s', 'something')
+        assert_not_equal('s', 'something', 's', 'somethingelse')
+        assert_not_equal('s', 'something', 'i', 1234)
+
+        assert_equal('g', 'dustybinqhogx')
+        assert_not_equal('g', 'dustybinqhogx', 'g', 'dustybin')
+        assert_not_equal('g', 'dustybinqhogx', 'i', 1234)
+
+        assert_equal('o', '/dev/null')
+        assert_not_equal('o', '/dev/null', 'o', '/dev/zero')
+        assert_not_equal('o', '/dev/null', 'i', 1234)
+
+        assert_equal('(s)', ('strtuple',))
+        assert_not_equal('(s)', ('strtuple',), '(s)', ('strtuple2',))
+
+        assert_equal('a{si}', {'str': 42})
+        assert_not_equal('a{si}', {'str': 42}, 'a{si}', {'str': 43})
+
+        assert_equal('v', GLib.Variant('i', 42))
+        assert_not_equal('v', GLib.Variant('i', 42), 'v', GLib.Variant('i', 43))
+
+    def test_bool(self):
+        # Check if the GVariant bool matches the unpacked Pythonic bool
+
+        def assert_equals_bool(vtype, value):
+            self.assertEqual(bool(GLib.Variant(vtype, value)), bool(value))
+
+        # simple values
+        assert_equals_bool('b', True)
+        assert_equals_bool('b', False)
+
+        numbers = ['y', 'n', 'q', 'i', 'u', 'x', 't', 'h', 'd']
+        for number in numbers:
+            assert_equals_bool(number, 0)
+            assert_equals_bool(number, 1)
+
+        assert_equals_bool('s', '')
+        assert_equals_bool('g', '')
+        assert_equals_bool('s', 'something')
+        assert_equals_bool('o', '/dev/null')
+        assert_equals_bool('g', 'dustybinqhogx')
+
+        # arrays
+        assert_equals_bool('ab', [True])
+        assert_equals_bool('ab', [False])
+        for number in numbers:
+            assert_equals_bool('a' + number, [])
+            assert_equals_bool('a' + number, [0])
+        assert_equals_bool('as', [])
+        assert_equals_bool('as', [''])
+        assert_equals_bool('ao', [])
+        assert_equals_bool('ao', ['/'])
+        assert_equals_bool('ag', [])
+        assert_equals_bool('ag', [''])
+        assert_equals_bool('aai', [[]])
+
+        # tuples
+        assert_equals_bool('()', ())
+        for number in numbers:
+            assert_equals_bool('(' + number + ')', (0,))
+        assert_equals_bool('(s)', ('',))
+        assert_equals_bool('(o)', ('/',))
+        assert_equals_bool('(g)', ('',))
+        assert_equals_bool('(())', ((),))
+
+        # dictionaries
+        assert_equals_bool('a{si}', {})
+        assert_equals_bool('a{si}', {'': 0})
+
+        # complex types, always True
+        assert_equals_bool('(as)', ([],))
+        assert_equals_bool('a{s(i)}', {'': (0,)})
+
+        # variant types, recursive unpacking
+        assert_equals_bool('v', GLib.Variant('i', 0))
+        assert_equals_bool('v', GLib.Variant('i', 1))
+
+    def test_repr(self):
+        # with C constructor
+        v = GLib.Variant.new_uint32(42)
+        self.assertEqual(repr(v), "GLib.Variant('u', 42)")
+
+        # with override constructor
+        v = GLib.Variant('(is)', (1, 'somestring'))
+        self.assertEqual(repr(v), "GLib.Variant('(is)', (1, 'somestring'))")
+
+    def test_str(self):
+        # with C constructor
+        v = GLib.Variant.new_uint32(42)
+        self.assertEqual(str(v), 'uint32 42')
+
+        # with override constructor
+        v = GLib.Variant('(is)', (1, 'somestring'))
+        self.assertEqual(str(v), "(1, 'somestring')")
+
+    def test_parse_error(self):
+        # This test doubles as a test for GLib.Error marshaling.
+        source_str = 'abc'
+        with self.assertRaises(GLib.Error) as context:
+            GLib.Variant.parse(None, source_str, None, None)
+        e = context.exception
+        text = GLib.Variant.parse_error_print_context(e, source_str)
+        self.assertTrue(source_str in text)
+
+    def test_parse_error_exceptions(self):
+        source_str = 'abc'
+        self.assertRaisesRegexp(TypeError, 'Must be GLib.Error, not int',
+                                GLib.Variant.parse_error_print_context,
+                                42, source_str)
+
+        gerror = GLib.Error(message=42)  # not a string
+        self.assertRaisesRegexp(TypeError, ".*Must be string, not int.*",
+                                GLib.Variant.parse_error_print_context,
+                                gerror, source_str)
+
+        gerror = GLib.Error(domain=42)  # not a string
+        self.assertRaisesRegexp(TypeError, ".*Must be string, not int.*",
+                                GLib.Variant.parse_error_print_context,
+                                gerror, source_str)
+
+        gerror = GLib.Error(code='not an int')
+        self.assertRaisesRegexp(TypeError, ".*Must be number, not str.*",
+                                GLib.Variant.parse_error_print_context,
+                                gerror, source_str)
+
+        gerror = GLib.Error(code=GLib.MAXUINT)
+        self.assertRaisesRegexp(OverflowError,
+                                ".*not in range.*",
+                                GLib.Variant.parse_error_print_context,
+                                gerror, source_str)
+
+
+class TestConstants(unittest.TestCase):
+
+    def test_basic_types_limits(self):
+        self.assertTrue(isinstance(GLib.MINFLOAT, float))
+        self.assertTrue(isinstance(GLib.MAXLONG, integer_types))
diff --git a/tests/test_overrides_gtk.py b/tests/test_overrides_gtk.py
new file mode 100644 (file)
index 0000000..1e36552
--- /dev/null
@@ -0,0 +1,2369 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# coding: UTF-8
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import contextlib
+import unittest
+import time
+import sys
+import gc
+import warnings
+
+from .helper import ignore_gi_deprecation_warnings, capture_glib_warnings
+
+import gi.overrides
+import gi.types
+from gi.repository import GLib, GObject
+
+try:
+    from gi.repository import Gtk, GdkPixbuf, Gdk
+    PyGTKDeprecationWarning = Gtk.PyGTKDeprecationWarning
+    Gtk_version = Gtk._version
+except ImportError:
+    Gtk = None
+    Gtk_version = None
+    PyGTKDeprecationWarning = None
+    GdkPixbuf = None
+    Gdk = None
+
+
+def gtkver():
+    if Gtk is None:
+        return (0, 0, 0)
+    return (Gtk.get_major_version(),
+            Gtk.get_minor_version(),
+            Gtk.get_micro_version())
+
+
+@contextlib.contextmanager
+def realized(widget):
+    """Makes sure the widget is realized.
+
+    view = Gtk.TreeView()
+    with realized(view):
+        do_something(view)
+    """
+
+    if isinstance(widget, Gtk.Window):
+        toplevel = widget
+    else:
+        toplevel = widget.get_parent_window()
+
+    if toplevel is None:
+        window = Gtk.Window()
+        window.add(widget)
+
+    widget.realize()
+    while Gtk.events_pending():
+        Gtk.main_iteration()
+    assert widget.get_realized()
+    yield widget
+
+    if toplevel is None:
+        window.remove(widget)
+        window.destroy()
+
+    while Gtk.events_pending():
+        Gtk.main_iteration()
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+def test_freeze_child_notif():
+
+    events = []
+
+    def on_notify(widget, spec):
+        events.append(spec.name)
+
+    b = Gtk.Box()
+    c = Gtk.Button()
+    c.connect("child-notify", on_notify)
+    c.freeze_child_notify()
+    b.pack_start(c, True, True, 0)
+    b.child_set_property(c, "expand", False)
+    b.child_set_property(c, "expand", True)
+    c.thaw_child_notify()
+    assert events.count("expand") == 1
+    del events[:]
+
+    with c.freeze_child_notify():
+        b.child_set_property(c, "expand", True)
+        b.child_set_property(c, "expand", False)
+
+    assert events.count("expand") == 1
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+def test_wrapper_toggle_refs():
+    class MyButton(Gtk.Button):
+        def __init__(self, height):
+            Gtk.Button.__init__(self)
+            self._height = height
+
+        def do_get_preferred_height(self):
+            return (self._height, self._height)
+
+    height = 142
+    w = Gtk.Window()
+    b = MyButton(height)
+    w.add(b)
+    b.show_all()
+    del b
+    gc.collect()
+    gc.collect()
+    assert w.get_preferred_size().minimum_size.height == height
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+@ignore_gi_deprecation_warnings
+class TestGtk(unittest.TestCase):
+    def test_container(self):
+        box = Gtk.Box()
+        self.assertTrue(isinstance(box, Gtk.Box))
+        self.assertTrue(isinstance(box, Gtk.Container))
+        self.assertTrue(isinstance(box, Gtk.Widget))
+        self.assertTrue(box)
+        label = Gtk.Label()
+        label2 = Gtk.Label()
+        box.add(label)
+        box.add(label2)
+        self.assertTrue(label in box)
+        self.assertTrue(label2 in box)
+        self.assertEqual(len(box), 2)
+        self.assertTrue(box)
+        labels = [x for x in box]
+        self.assertEqual(labels, [label, label2])
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_actions(self):
+        self.assertEqual(Gtk.Action, gi.overrides.Gtk.Action)
+        action = Gtk.Action(name="test", label="Test", tooltip="Test Action", stock_id=Gtk.STOCK_COPY)
+        self.assertEqual(action.get_name(), "test")
+        self.assertEqual(action.get_label(), "Test")
+        self.assertEqual(action.get_tooltip(), "Test Action")
+        self.assertEqual(action.get_stock_id(), Gtk.STOCK_COPY)
+
+        self.assertEqual(Gtk.RadioAction, gi.overrides.Gtk.RadioAction)
+        action = Gtk.RadioAction(name="test", label="Test", tooltip="Test Action", stock_id=Gtk.STOCK_COPY, value=1)
+        self.assertEqual(action.get_name(), "test")
+        self.assertEqual(action.get_label(), "Test")
+        self.assertEqual(action.get_tooltip(), "Test Action")
+        self.assertEqual(action.get_stock_id(), Gtk.STOCK_COPY)
+        self.assertEqual(action.get_current_value(), 1)
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_actiongroup(self):
+        self.assertEqual(Gtk.ActionGroup, gi.overrides.Gtk.ActionGroup)
+
+        action_group = Gtk.ActionGroup(name='TestActionGroup')
+        callback_data = "callback data"
+
+        def test_action_callback_data(action, user_data):
+            self.assertEqual(user_data, callback_data)
+
+        def test_radio_action_callback_data(action, current, user_data):
+            self.assertEqual(user_data, callback_data)
+
+        action_group.add_actions([
+            ('test-action1', None, 'Test Action 1',
+             None, None, test_action_callback_data),
+            ('test-action2', Gtk.STOCK_COPY, 'Test Action 2',
+             None, None, test_action_callback_data)], callback_data)
+        action_group.add_toggle_actions([
+            ('test-toggle-action1', None, 'Test Toggle Action 1',
+             None, None, test_action_callback_data, False),
+            ('test-toggle-action2', Gtk.STOCK_COPY, 'Test Toggle Action 2',
+             None, None, test_action_callback_data, True)], callback_data)
+        action_group.add_radio_actions([
+            ('test-radio-action1', None, 'Test Radio Action 1'),
+            ('test-radio-action2', Gtk.STOCK_COPY, 'Test Radio Action 2')], 1,
+            test_radio_action_callback_data,
+            callback_data)
+
+        expected_results = [('test-action1', Gtk.Action),
+                            ('test-action2', Gtk.Action),
+                            ('test-toggle-action1', Gtk.ToggleAction),
+                            ('test-toggle-action2', Gtk.ToggleAction),
+                            ('test-radio-action1', Gtk.RadioAction),
+                            ('test-radio-action2', Gtk.RadioAction)]
+
+        for action in action_group.list_actions():
+            a = (action.get_name(), type(action))
+            self.assertTrue(a in expected_results)
+            expected_results.remove(a)
+            action.activate()
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_uimanager(self):
+        self.assertEqual(Gtk.UIManager, gi.overrides.Gtk.UIManager)
+        ui = Gtk.UIManager()
+        ui.add_ui_from_string("""<ui>
+    <menubar name="menubar1"></menubar>
+</ui>
+"""
+)
+        menubar = ui.get_widget("/menubar1")
+        self.assertEqual(type(menubar), Gtk.MenuBar)
+
+        ag = Gtk.ActionGroup(name="ag1")
+        ui.insert_action_group(ag)
+        ag2 = Gtk.ActionGroup(name="ag2")
+        ui.insert_action_group(ag2)
+        groups = ui.get_action_groups()
+        self.assertEqual(ag, groups[-2])
+        self.assertEqual(ag2, groups[-1])
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_uimanager_nonascii(self):
+        ui = Gtk.UIManager()
+        ui.add_ui_from_string(b'<ui><menubar name="menub\xc3\xa6r1" /></ui>'.decode('UTF-8'))
+        mi = ui.get_widget("/menubær1")
+        self.assertEqual(type(mi), Gtk.MenuBar)
+
+    def test_window(self):
+        # standard Window
+        w = Gtk.Window()
+        self.assertEqual(w.get_property('type'), Gtk.WindowType.TOPLEVEL)
+
+        # type works as keyword argument
+        w = Gtk.Window(type=Gtk.WindowType.POPUP)
+        self.assertEqual(w.get_property('type'), Gtk.WindowType.POPUP)
+
+        class TestWindow(Gtk.Window):
+            __gtype_name__ = "TestWindow"
+
+        # works from builder
+        builder = Gtk.Builder()
+        builder.add_from_string('''
+<interface>
+  <object class="GtkWindow" id="win">
+    <property name="type">popup</property>
+  </object>
+  <object class="TestWindow" id="testwin">
+  </object>
+  <object class="TestWindow" id="testpop">
+    <property name="type">popup</property>
+  </object>
+</interface>''')
+        self.assertEqual(builder.get_object('win').get_property('type'),
+                         Gtk.WindowType.POPUP)
+        self.assertEqual(builder.get_object('testwin').get_property('type'),
+                         Gtk.WindowType.TOPLEVEL)
+        self.assertEqual(builder.get_object('testpop').get_property('type'),
+                         Gtk.WindowType.POPUP)
+
+    def test_dialog_classes(self):
+        self.assertEqual(Gtk.Dialog, gi.overrides.Gtk.Dialog)
+        self.assertEqual(Gtk.FileChooserDialog, gi.overrides.Gtk.FileChooserDialog)
+        self.assertEqual(Gtk.RecentChooserDialog, gi.overrides.Gtk.RecentChooserDialog)
+        if Gtk_version != "4.0":
+            self.assertEqual(Gtk.ColorSelectionDialog, gi.overrides.Gtk.ColorSelectionDialog)
+            self.assertEqual(Gtk.FontSelectionDialog, gi.overrides.Gtk.FontSelectionDialog)
+
+    def test_dialog_base(self):
+        dialog = Gtk.Dialog(title='Foo', modal=True)
+        self.assertTrue(isinstance(dialog, Gtk.Dialog))
+        self.assertTrue(isinstance(dialog, Gtk.Window))
+        self.assertEqual('Foo', dialog.get_title())
+        self.assertTrue(dialog.get_modal())
+
+    def test_dialog_deprecations(self):
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            dialog = Gtk.Dialog(title='Foo', flags=Gtk.DialogFlags.MODAL)
+            self.assertTrue(dialog.get_modal())
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGTKDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*flags.*modal.*')
+
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            dialog = Gtk.Dialog(title='Foo', flags=Gtk.DialogFlags.DESTROY_WITH_PARENT)
+            self.assertTrue(dialog.get_destroy_with_parent())
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGTKDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*flags.*destroy_with_parent.*')
+
+    def test_dialog_deprecation_stacklevels(self):
+        # Test warning levels are setup to give the correct filename for
+        # deprecations in different classes in the inheritance hierarchy.
+
+        # Base class
+        self.assertEqual(Gtk.Dialog, gi.overrides.Gtk.Dialog)
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            Gtk.Dialog(flags=Gtk.DialogFlags.MODAL)
+            self.assertEqual(len(warn), 1)
+            self.assertRegexpMatches(warn[0].filename, '.*test_overrides_gtk.*')
+
+        # Validate overridden base with overridden sub-class.
+        self.assertEqual(Gtk.MessageDialog, gi.overrides.Gtk.MessageDialog)
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            Gtk.MessageDialog(flags=Gtk.DialogFlags.MODAL)
+            self.assertEqual(len(warn), 1)
+            self.assertRegexpMatches(warn[0].filename, '.*test_overrides_gtk.*')
+
+        # Validate overridden base with non-overridden sub-class.
+        self.assertEqual(Gtk.AboutDialog, gi.repository.Gtk.AboutDialog)
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            Gtk.AboutDialog(flags=Gtk.DialogFlags.MODAL)
+            self.assertEqual(len(warn), 1)
+            self.assertRegexpMatches(warn[0].filename, '.*test_overrides_gtk.*')
+
+    def test_dialog_add_buttons(self):
+        # The overloaded "buttons" keyword gives a warning when attempting
+        # to use it for adding buttons as was available in PyGTK.
+        with warnings.catch_warnings(record=True) as warn:
+            warnings.simplefilter('always')
+            dialog = Gtk.Dialog(title='Foo', modal=True,
+                                buttons=('test-button1', 1))
+            self.assertEqual(len(warn), 1)
+            self.assertTrue(issubclass(warn[0].category, PyGTKDeprecationWarning))
+            self.assertRegexpMatches(str(warn[0].message),
+                                     '.*ButtonsType.*add_buttons.*')
+
+        dialog.add_buttons('test-button2', 2, 'gtk-close', Gtk.ResponseType.CLOSE)
+        button = dialog.get_widget_for_response(1)
+        self.assertEqual('test-button1', button.get_label())
+        button = dialog.get_widget_for_response(2)
+        self.assertEqual('test-button2', button.get_label())
+        button = dialog.get_widget_for_response(Gtk.ResponseType.CLOSE)
+        self.assertEqual('gtk-close', button.get_label())
+
+    def test_about_dialog(self):
+        dialog = Gtk.AboutDialog()
+        self.assertTrue(isinstance(dialog, Gtk.Dialog))
+        self.assertTrue(isinstance(dialog, Gtk.Window))
+
+        # AboutDialog is not sub-classed in overrides, make sure
+        # the mro still injects the base class "add_buttons" override.
+        self.assertTrue(hasattr(dialog, 'add_buttons'))
+
+    def test_message_dialog(self):
+        dialog = Gtk.MessageDialog(title='message dialog test',
+                                   modal=True,
+                                   buttons=Gtk.ButtonsType.OK,
+                                   text='dude!')
+        self.assertTrue(isinstance(dialog, Gtk.Dialog))
+        self.assertTrue(isinstance(dialog, Gtk.Window))
+
+        self.assertEqual('message dialog test', dialog.get_title())
+        self.assertTrue(dialog.get_modal())
+        text = dialog.get_property('text')
+        self.assertEqual('dude!', text)
+
+        dialog.format_secondary_text('2nd text')
+        self.assertEqual(dialog.get_property('secondary-text'), '2nd text')
+        self.assertFalse(dialog.get_property('secondary-use-markup'))
+
+        dialog.format_secondary_markup('2nd markup')
+        self.assertEqual(dialog.get_property('secondary-text'), '2nd markup')
+        self.assertTrue(dialog.get_property('secondary-use-markup'))
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_color_selection_dialog(self):
+        dialog = Gtk.ColorSelectionDialog(title="color selection dialog test")
+        self.assertTrue(isinstance(dialog, Gtk.Dialog))
+        self.assertTrue(isinstance(dialog, Gtk.Window))
+        self.assertEqual('color selection dialog test', dialog.get_title())
+
+    def test_file_chooser_dialog(self):
+        # might cause a GVFS warning, do not break on this
+        with capture_glib_warnings(allow_warnings=True):
+            dialog = Gtk.FileChooserDialog(title='file chooser dialog test',
+                                           action=Gtk.FileChooserAction.SAVE)
+
+        self.assertTrue(isinstance(dialog, Gtk.Dialog))
+        self.assertTrue(isinstance(dialog, Gtk.Window))
+        self.assertEqual('file chooser dialog test', dialog.get_title())
+
+        action = dialog.get_property('action')
+        self.assertEqual(Gtk.FileChooserAction.SAVE, action)
+
+    def test_file_chooser_dialog_default_action(self):
+        # might cause a GVFS warning, do not break on this
+        with capture_glib_warnings(allow_warnings=True):
+            dialog = Gtk.FileChooserDialog(title='file chooser dialog test')
+
+        action = dialog.get_property('action')
+        self.assertEqual(Gtk.FileChooserAction.OPEN, action)
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_font_selection_dialog(self):
+        dialog = Gtk.FontSelectionDialog(title="font selection dialog test")
+        self.assertTrue(isinstance(dialog, Gtk.Dialog))
+        self.assertTrue(isinstance(dialog, Gtk.Window))
+        self.assertEqual('font selection dialog test', dialog.get_title())
+
+    def test_recent_chooser_dialog(self):
+        test_manager = Gtk.RecentManager()
+        dialog = Gtk.RecentChooserDialog(title='recent chooser dialog test',
+                                         recent_manager=test_manager)
+        self.assertTrue(isinstance(dialog, Gtk.Dialog))
+        self.assertTrue(isinstance(dialog, Gtk.Window))
+        self.assertEqual('recent chooser dialog test', dialog.get_title())
+
+    class TestClass(GObject.GObject):
+        __gtype_name__ = "GIOverrideTreeAPITest"
+
+        def __init__(self, tester, int_value, string_value):
+            super(TestGtk.TestClass, self).__init__()
+            self.tester = tester
+            self.int_value = int_value
+            self.string_value = string_value
+
+        def check(self, int_value, string_value):
+            self.tester.assertEqual(int_value, self.int_value)
+            self.tester.assertEqual(string_value, self.string_value)
+
+    def test_buttons(self):
+        self.assertEqual(Gtk.Button, gi.overrides.Gtk.Button)
+
+        # test Gtk.Button
+        button = Gtk.Button()
+        self.assertTrue(isinstance(button, Gtk.Button))
+        self.assertTrue(isinstance(button, Gtk.Container))
+        self.assertTrue(isinstance(button, Gtk.Widget))
+
+        if Gtk_version != "4.0":
+            # Using stock items causes hard warning in devel versions of GTK+.
+            with capture_glib_warnings(allow_warnings=True):
+                button = Gtk.Button.new_from_stock(Gtk.STOCK_CLOSE)
+
+            self.assertEqual(Gtk.STOCK_CLOSE, button.get_label())
+            self.assertTrue(button.get_use_stock())
+            self.assertTrue(button.get_use_underline())
+
+            # test Gtk.Button use_stock
+            button = Gtk.Button(label=Gtk.STOCK_CLOSE, use_stock=True,
+                                use_underline=True)
+            self.assertEqual(Gtk.STOCK_CLOSE, button.get_label())
+            self.assertTrue(button.get_use_stock())
+            self.assertTrue(button.get_use_underline())
+
+        # test Gtk.LinkButton
+        button = Gtk.LinkButton(uri='http://www.Gtk.org', label='Gtk')
+        self.assertTrue(isinstance(button, Gtk.Button))
+        self.assertTrue(isinstance(button, Gtk.Container))
+        self.assertTrue(isinstance(button, Gtk.Widget))
+        self.assertEqual('http://www.Gtk.org', button.get_uri())
+        self.assertEqual('Gtk', button.get_label())
+
+    def test_inheritance(self):
+        for name in gi.overrides.Gtk.__all__:
+            over = getattr(gi.overrides.Gtk, name)
+            for element in dir(Gtk):
+                try:
+                    klass = getattr(Gtk, element)
+                    info = klass.__info__
+                except (NotImplementedError, AttributeError):
+                    continue
+
+                # Get all parent classes and interfaces klass inherits from
+                if isinstance(info, gi.types.ObjectInfo):
+                    classes = list(info.get_interfaces())
+                    parent = info.get_parent()
+                    while parent.get_name() != "Object":
+                        classes.append(parent)
+                        parent = parent.get_parent()
+                    classes = [kl for kl in classes if kl.get_namespace() == "Gtk"]
+                else:
+                    continue
+
+                for kl in classes:
+                    if kl.get_name() == name:
+                        self.assertTrue(issubclass(klass, over,),
+                                        "%r does not inherit from override %r" % (klass, over,))
+
+    def test_editable(self):
+        self.assertEqual(Gtk.Editable, gi.overrides.Gtk.Editable)
+
+        # need to use Gtk.Entry because Editable is an interface
+        entry = Gtk.Entry()
+        pos = entry.insert_text('HeWorld', 0)
+        self.assertEqual(pos, 7)
+        pos = entry.insert_text('llo ', 2)
+        self.assertEqual(pos, 6)
+        text = entry.get_chars(0, 11)
+        self.assertEqual('Hello World', text)
+
+    def test_label(self):
+        label = Gtk.Label(label='Hello')
+        self.assertTrue(isinstance(label, Gtk.Widget))
+        self.assertEqual(label.get_text(), 'Hello')
+
+    def adjustment_check(self, adjustment, value=0.0, lower=0.0, upper=0.0,
+                         step_increment=0.0, page_increment=0.0, page_size=0.0):
+        self.assertEqual(adjustment.get_value(), value)
+        self.assertEqual(adjustment.get_lower(), lower)
+        self.assertEqual(adjustment.get_upper(), upper)
+        self.assertEqual(adjustment.get_step_increment(), step_increment)
+        self.assertEqual(adjustment.get_page_increment(), page_increment)
+        self.assertEqual(adjustment.get_page_size(), page_size)
+
+    def test_adjustment(self):
+        adjustment = Gtk.Adjustment(value=1, lower=0, upper=6, step_increment=4, page_increment=5, page_size=3)
+        self.adjustment_check(adjustment, value=1, lower=0, upper=6, step_increment=4, page_increment=5, page_size=3)
+
+        adjustment = Gtk.Adjustment(value=1, lower=0, upper=6, step_increment=4, page_increment=5)
+        self.adjustment_check(adjustment, value=1, lower=0, upper=6, step_increment=4, page_increment=5)
+
+        adjustment = Gtk.Adjustment(value=1, lower=0, upper=6, step_increment=4)
+        self.adjustment_check(adjustment, value=1, lower=0, upper=6, step_increment=4)
+
+        adjustment = Gtk.Adjustment(value=1, lower=0, upper=6)
+        self.adjustment_check(adjustment, value=1, lower=0, upper=6)
+
+        adjustment = Gtk.Adjustment()
+        self.adjustment_check(adjustment)
+
+        adjustment = Gtk.Adjustment(1, -1, 3, 0, 0, 0)
+        self.adjustment_check(adjustment, value=1, lower=-1, upper=3)
+
+        adjustment = Gtk.Adjustment(1, -1, 3, 0, 0, 0, value=2)
+        self.adjustment_check(adjustment, value=2, lower=-1, upper=3)
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_table(self):
+        table = Gtk.Table()
+        self.assertTrue(isinstance(table, Gtk.Table))
+        self.assertTrue(isinstance(table, Gtk.Container))
+        self.assertTrue(isinstance(table, Gtk.Widget))
+        self.assertEqual(table.get_size(), (1, 1))
+        self.assertEqual(table.get_homogeneous(), False)
+
+        table = Gtk.Table(n_rows=2, n_columns=3)
+        self.assertEqual(table.get_size(), (2, 3))
+        self.assertEqual(table.get_homogeneous(), False)
+
+        table = Gtk.Table(n_rows=2, n_columns=3, homogeneous=True)
+        self.assertEqual(table.get_size(), (2, 3))
+        self.assertEqual(table.get_homogeneous(), True)
+
+        label = Gtk.Label(label='Hello')
+        self.assertTrue(isinstance(label, Gtk.Widget))
+        table.attach(label, 0, 1, 0, 1)
+        self.assertEqual(label, table.get_children()[0])
+
+    def test_scrolledwindow(self):
+        sw = Gtk.ScrolledWindow()
+        self.assertTrue(isinstance(sw, Gtk.ScrolledWindow))
+        self.assertTrue(isinstance(sw, Gtk.Container))
+        self.assertTrue(isinstance(sw, Gtk.Widget))
+        sb = sw.get_hscrollbar()
+        self.assertEqual(sw.get_hadjustment(), sb.get_adjustment())
+        sb = sw.get_vscrollbar()
+        self.assertEqual(sw.get_vadjustment(), sb.get_adjustment())
+
+    def test_widget_drag_methods(self):
+        widget = Gtk.Button()
+
+        # here we are not checking functionality, only that the methods exist
+        # and except the right number of arguments
+
+        widget.drag_check_threshold(0, 0, 0, 0)
+
+        # drag_dest_ methods
+        widget.drag_dest_set(Gtk.DestDefaults.DROP, None, Gdk.DragAction.COPY)
+        widget.drag_dest_add_image_targets()
+        widget.drag_dest_add_text_targets()
+        widget.drag_dest_add_uri_targets()
+        widget.drag_dest_get_track_motion()
+        widget.drag_dest_set_track_motion(True)
+        widget.drag_dest_get_target_list()
+        widget.drag_dest_set_target_list(None)
+        widget.drag_dest_set_target_list(Gtk.TargetList.new([Gtk.TargetEntry.new('test', 0, 0)]))
+        widget.drag_dest_unset()
+
+        widget.drag_highlight()
+        widget.drag_unhighlight()
+
+        # drag_source_ methods
+        widget.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, None, Gdk.DragAction.MOVE)
+        widget.drag_source_add_image_targets()
+        widget.drag_source_add_text_targets()
+        widget.drag_source_add_uri_targets()
+        widget.drag_source_set_icon_name("_About")
+        widget.drag_source_set_icon_pixbuf(GdkPixbuf.Pixbuf())
+        if Gtk_version != "4.0":
+            widget.drag_source_set_icon_stock(Gtk.STOCK_ABOUT)
+        widget.drag_source_get_target_list()
+        widget.drag_source_set_target_list(None)
+        widget.drag_source_set_target_list(Gtk.TargetList.new([Gtk.TargetEntry.new('test', 0, 0)]))
+        widget.drag_source_unset()
+
+        # these methods cannot be called because they require a valid drag on
+        # a real GdkWindow. So we only check that they exist and are callable.
+        if Gtk_version != "4.0":
+            self.assertTrue(hasattr(widget, 'drag_dest_set_proxy'))
+        self.assertTrue(hasattr(widget, 'drag_get_data'))
+
+    @unittest.skipIf(sys.platform == "darwin", "crashes")
+    def test_drag_target_list(self):
+        mixed_target_list = [Gtk.TargetEntry.new('test0', 0, 0),
+                             ('test1', 1, 1),
+                             Gtk.TargetEntry.new('test2', 2, 2),
+                             ('test3', 3, 3)]
+
+        def _test_target_list(targets):
+            for i, target in enumerate(targets):
+                self.assertTrue(isinstance(target, Gtk.TargetEntry))
+                self.assertEqual(target.target, 'test' + str(i))
+                self.assertEqual(target.flags, i)
+                self.assertEqual(target.info, i)
+
+        _test_target_list(Gtk._construct_target_list(mixed_target_list))
+
+        widget = Gtk.Button()
+        widget.drag_dest_set(Gtk.DestDefaults.DROP, None, Gdk.DragAction.COPY)
+        widget.drag_dest_set_target_list(mixed_target_list)
+        widget.drag_dest_get_target_list()
+
+        widget.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, None, Gdk.DragAction.MOVE)
+        widget.drag_source_set_target_list(mixed_target_list)
+        widget.drag_source_get_target_list()
+
+        treeview = Gtk.TreeView()
+        treeview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
+                                          mixed_target_list,
+                                          Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
+
+        treeview.enable_model_drag_dest(mixed_target_list,
+                                        Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_scrollbar(self):
+        adjustment = Gtk.Adjustment()
+
+        hscrollbar = Gtk.HScrollbar()
+        vscrollbar = Gtk.VScrollbar()
+        self.assertNotEqual(hscrollbar.props.adjustment, adjustment)
+        self.assertNotEqual(vscrollbar.props.adjustment, adjustment)
+
+        hscrollbar = Gtk.HScrollbar(adjustment=adjustment)
+        vscrollbar = Gtk.VScrollbar(adjustment=adjustment)
+        self.assertEqual(hscrollbar.props.adjustment, adjustment)
+        self.assertEqual(vscrollbar.props.adjustment, adjustment)
+
+    def test_iconview(self):
+        # PyGTK compat
+        iconview = Gtk.IconView()
+        self.assertEqual(iconview.props.model, None)
+
+        model = Gtk.ListStore(str)
+        iconview = Gtk.IconView(model=model)
+        self.assertEqual(iconview.props.model, model)
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_toolbutton(self):
+        # PyGTK compat
+
+        # Using stock items causes hard warning in devel versions of GTK+.
+        with capture_glib_warnings(allow_warnings=True):
+            button = Gtk.ToolButton()
+            self.assertEqual(button.props.stock_id, None)
+
+            button = Gtk.ToolButton(stock_id='gtk-new')
+            self.assertEqual(button.props.stock_id, 'gtk-new')
+
+        icon = Gtk.Image.new_from_stock(Gtk.STOCK_OPEN, Gtk.IconSize.SMALL_TOOLBAR)
+        button = Gtk.ToolButton(label='mylabel', icon_widget=icon)
+        self.assertEqual(button.props.label, 'mylabel')
+        self.assertEqual(button.props.icon_widget, icon)
+
+    def test_toolbutton_gtk4(self):
+        icon = Gtk.Image.new()
+        button = Gtk.ToolButton(label='mylabel', icon_widget=icon)
+        self.assertEqual(button.props.label, 'mylabel')
+        self.assertEqual(button.props.icon_widget, icon)
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_iconset(self):
+        Gtk.IconSet()
+        pixbuf = GdkPixbuf.Pixbuf()
+        Gtk.IconSet.new_from_pixbuf(pixbuf)
+
+    def test_viewport(self):
+        vadjustment = Gtk.Adjustment()
+        hadjustment = Gtk.Adjustment()
+
+        viewport = Gtk.Viewport(hadjustment=hadjustment,
+                                vadjustment=vadjustment)
+
+        self.assertEqual(viewport.props.vadjustment, vadjustment)
+        self.assertEqual(viewport.props.hadjustment, hadjustment)
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_stock_lookup(self):
+        stock_item = Gtk.stock_lookup('gtk-ok')
+        self.assertEqual(type(stock_item), Gtk.StockItem)
+        self.assertEqual(stock_item.stock_id, 'gtk-ok')
+        self.assertEqual(Gtk.stock_lookup('nosuchthing'), None)
+
+    def test_gtk_main(self):
+        # with no arguments
+        GLib.idle_add(Gtk.main_quit)
+        Gtk.main()
+
+        # overridden function ignores its arguments
+        GLib.idle_add(Gtk.main_quit, 'hello')
+        Gtk.main()
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_widget_render_icon(self):
+        button = Gtk.Button(label='OK')
+        pixbuf = button.render_icon(Gtk.STOCK_OK, Gtk.IconSize.BUTTON)
+        self.assertTrue(pixbuf is not None)
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+@unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+class TestWidget(unittest.TestCase):
+    def test_style_get_property_gvalue(self):
+        button = Gtk.Button()
+        value = GObject.Value(int, -42)
+        button.style_get_property('focus-padding', value)
+        # Test only that the style property changed since we can't actuall
+        # set it.
+        self.assertNotEqual(value.get_int(), -42)
+
+    def test_style_get_property_return_with_explicit_gvalue(self):
+        button = Gtk.Button()
+        value = GObject.Value(int, -42)
+        result = button.style_get_property('focus-padding', value)
+        self.assertIsInstance(result, int)
+        self.assertNotEqual(result, -42)
+
+    def test_style_get_property_return_with_implicit_gvalue(self):
+        button = Gtk.Button()
+        result = button.style_get_property('focus-padding')
+        self.assertIsInstance(result, int)
+        self.assertNotEqual(result, -42)
+
+    def test_style_get_property_error(self):
+        button = Gtk.Button()
+        with self.assertRaises(ValueError):
+            button.style_get_property('not-a-valid-style-property')
+
+
+@unittest.skipIf(sys.platform == "darwin", "hangs")
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestSignals(unittest.TestCase):
+    def test_class_closure_override_with_aliased_type(self):
+        class WindowWithSizeAllocOverride(Gtk.ScrolledWindow):
+            __gsignals__ = {'size-allocate': 'override'}
+
+            def __init__(self):
+                Gtk.ScrolledWindow.__init__(self)
+                self._alloc_called = False
+                self._alloc_value = None
+                self._alloc_error = None
+
+            def do_size_allocate(self, alloc):
+                self._alloc_called = True
+                self._alloc_value = alloc
+
+                try:
+                    Gtk.ScrolledWindow.do_size_allocate(self, alloc)
+                except Exception as e:
+                    self._alloc_error = e
+
+        win = WindowWithSizeAllocOverride()
+        rect = Gdk.Rectangle()
+        rect.width = 100
+        rect.height = 100
+
+        with realized(win):
+            win.show()
+            win.get_preferred_size()
+            win.size_allocate(rect)
+            self.assertTrue(win._alloc_called)
+            self.assertIsInstance(win._alloc_value, Gdk.Rectangle)
+            self.assertTrue(win._alloc_error is None, win._alloc_error)
+
+    @unittest.expectedFailure  # https://bugzilla.gnome.org/show_bug.cgi?id=735693
+    def test_overlay_child_position(self):
+        def get_child_position(overlay, widget, rect, user_data=None):
+            rect.x = 1
+            rect.y = 2
+            rect.width = 3
+            rect.height = 4
+            return True
+
+        overlay = Gtk.Overlay()
+        overlay.connect('get-child-position', get_child_position)
+
+        rect = Gdk.Rectangle()
+        rect.x = -1
+        rect.y = -1
+        rect.width = -1
+        rect.height = -1
+
+        overlay.emit('get-child-position', None, rect)
+        self.assertEqual(rect.x, 1)
+        self.assertEqual(rect.y, 2)
+        self.assertEqual(rect.width, 3)
+        self.assertEqual(rect.height, 4)
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestBuilder(unittest.TestCase):
+    class SignalTest(GObject.GObject):
+        __gtype_name__ = "GIOverrideSignalTest"
+        __gsignals__ = {
+            "test-signal": (GObject.SignalFlags.RUN_FIRST,
+                            None,
+                            []),
+        }
+
+    def test_extract_handler_and_args_object(self):
+        class Obj():
+            pass
+
+        obj = Obj()
+        obj.foo = lambda: None
+
+        handler, args = Gtk._extract_handler_and_args(obj, 'foo')
+        self.assertEqual(handler, obj.foo)
+        self.assertEqual(len(args), 0)
+
+    def test_extract_handler_and_args_dict(self):
+        obj = {'foo': lambda: None}
+
+        handler, args = Gtk._extract_handler_and_args(obj, 'foo')
+        self.assertEqual(handler, obj['foo'])
+        self.assertEqual(len(args), 0)
+
+    def test_extract_handler_and_args_with_seq(self):
+        obj = {'foo': (lambda: None, 1, 2)}
+
+        handler, args = Gtk._extract_handler_and_args(obj, 'foo')
+        self.assertEqual(handler, obj['foo'][0])
+        self.assertSequenceEqual(args, [1, 2])
+
+    def test_extract_handler_and_args_no_handler_error(self):
+        obj = dict(foo=lambda: None)
+        self.assertRaises(AttributeError,
+                          Gtk._extract_handler_and_args,
+                          obj, 'not_a_handler')
+
+    def test_builder_with_handler_and_args(self):
+        builder = Gtk.Builder()
+        builder.add_from_string("""
+            <interface>
+              <object class="GIOverrideSignalTest" id="object_sig_test">
+                  <signal name="test-signal" handler="on_signal1" />
+                  <signal name="test-signal" handler="on_signal2" after="yes" />
+              </object>
+            </interface>
+            """)
+
+        args_collector = []
+
+        def on_signal(*args):
+            args_collector.append(args)
+
+        builder.connect_signals({'on_signal1': (on_signal, 1, 2),
+                                 'on_signal2': on_signal})
+
+        objects = builder.get_objects()
+        self.assertEqual(len(objects), 1)
+        obj, = objects
+        obj.emit('test-signal')
+
+        self.assertEqual(len(args_collector), 2)
+        self.assertSequenceEqual(args_collector[0], (obj, 1, 2))
+        self.assertSequenceEqual(args_collector[1], (obj, ))
+
+    def test_builder(self):
+        self.assertEqual(Gtk.Builder, gi.overrides.Gtk.Builder)
+
+        class SignalCheck:
+            def __init__(self):
+                self.sentinel = 0
+                self.after_sentinel = 0
+
+            def on_signal_1(self, *args):
+                self.sentinel += 1
+                self.after_sentinel += 1
+
+            def on_signal_3(self, *args):
+                self.sentinel += 3
+
+            def on_signal_after(self, *args):
+                if self.after_sentinel == 1:
+                    self.after_sentinel += 1
+
+        signal_checker = SignalCheck()
+        builder = Gtk.Builder()
+
+        # add object1 to the builder
+        builder.add_from_string("""
+<interface>
+  <object class="GIOverrideSignalTest" id="object1">
+      <signal name="test-signal" after="yes" handler="on_signal_after" />
+      <signal name="test-signal" handler="on_signal_1" />
+  </object>
+</interface>
+""")
+
+        # only add object3 to the builder
+        builder.add_objects_from_string("""
+<interface>
+  <object class="GIOverrideSignalTest" id="object2">
+      <signal name="test-signal" handler="on_signal_2" />
+  </object>
+  <object class="GIOverrideSignalTest" id="object3">
+      <signal name="test-signal" handler="on_signal_3" />
+  </object>
+  <object class="GIOverrideSignalTest" id="object4">
+      <signal name="test-signal" handler="on_signal_4" />
+  </object>
+</interface>
+""", ['object3'])
+
+        # hook up signals
+        builder.connect_signals(signal_checker)
+
+        # call their notify signals and check sentinel
+        objects = builder.get_objects()
+        self.assertEqual(len(objects), 2)
+        for obj in objects:
+            obj.emit('test-signal')
+
+        self.assertEqual(signal_checker.sentinel, 4)
+        self.assertEqual(signal_checker.after_sentinel, 2)
+
+
+@ignore_gi_deprecation_warnings
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestTreeModel(unittest.TestCase):
+    def test_tree_model_sort(self):
+        self.assertEqual(Gtk.TreeModelSort, gi.overrides.Gtk.TreeModelSort)
+        model = Gtk.TreeStore(int, bool)
+        model_sort = Gtk.TreeModelSort(model=model)
+        self.assertEqual(model_sort.get_model(), model)
+
+    def test_tree_store(self):
+        self.assertEqual(Gtk.TreeStore, gi.overrides.Gtk.TreeStore)
+        self.assertEqual(Gtk.ListStore, gi.overrides.Gtk.ListStore)
+        self.assertEqual(Gtk.TreeModel, gi.overrides.Gtk.TreeModel)
+        self.assertEqual(Gtk.TreeViewColumn, gi.overrides.Gtk.TreeViewColumn)
+
+        class TestPyObject(object):
+            pass
+
+        test_pyobj = TestPyObject()
+        test_pydict = {1: 1, "2": 2, "3": "3"}
+        test_pylist = [1, "2", "3"]
+        tree_store = Gtk.TreeStore(int,
+                                   'gchararray',
+                                   TestGtk.TestClass,
+                                   GObject.TYPE_PYOBJECT,
+                                   object,
+                                   object,
+                                   object,
+                                   bool,
+                                   bool,
+                                   GObject.TYPE_UINT,
+                                   GObject.TYPE_ULONG,
+                                   GObject.TYPE_INT64,
+                                   GObject.TYPE_UINT64,
+                                   GObject.TYPE_UCHAR,
+                                   GObject.TYPE_CHAR)
+
+        parent = None
+        for i in range(97):
+            label = 'this is child #%d' % i
+            testobj = TestGtk.TestClass(self, i, label)
+            parent = tree_store.append(parent, (i,
+                                                label,
+                                                testobj,
+                                                testobj,
+                                                test_pyobj,
+                                                test_pydict,
+                                                test_pylist,
+                                                i % 2,
+                                                bool(i % 2),
+                                                i,
+                                                GLib.MAXULONG,
+                                                GLib.MININT64,
+                                                0xffffffffffffffff,
+                                                254,
+                                                b'a'
+                                                ))
+        # test set
+        parent = tree_store.append(parent)
+        i = 97
+        label = 'this is child #%d' % i
+        testobj = TestGtk.TestClass(self, i, label)
+        tree_store.set(parent,
+                       0, i,
+                       2, testobj,
+                       1, label,
+                       3, testobj,
+                       4, test_pyobj,
+                       5, test_pydict,
+                       6, test_pylist,
+                       7, i % 2,
+                       8, bool(i % 2),
+                       9, i,
+                       10, GLib.MAXULONG,
+                       11, GLib.MININT64,
+                       12, 0xffffffffffffffff,
+                       13, 254,
+                       14, b'a')
+
+        parent = tree_store.append(parent)
+        i = 98
+        label = 'this is child #%d' % i
+        testobj = TestGtk.TestClass(self, i, label)
+        tree_store.set(parent, {0: i,
+                                2: testobj,
+                                1: label,
+                                3: testobj,
+                                4: test_pyobj,
+                                5: test_pydict,
+                                6: test_pylist,
+                                7: i % 2,
+                                8: bool(i % 2),
+                                9: i,
+                                10: GLib.MAXULONG,
+                                11: GLib.MININT64,
+                                12: 0xffffffffffffffff,
+                                13: 254,
+                                14: b'a'})
+
+        parent = tree_store.append(parent)
+        i = 99
+        label = 'this is child #%d' % i
+        testobj = TestGtk.TestClass(self, i, label)
+        tree_store.set(parent, (0, 2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14),
+                               (i,
+                                testobj,
+                                label,
+                                testobj,
+                                test_pyobj,
+                                test_pydict,
+                                test_pylist,
+                                i % 2,
+                                bool(i % 2),
+                                i,
+                                GLib.MAXULONG,
+                                GLib.MININT64,
+                                0xffffffffffffffff,
+                                254,
+                                b'a'))
+
+        # len gets the number of children in the root node
+        # since we kept appending to the previous node
+        # there should only be one child of the root
+        self.assertEqual(len(tree_store), 1)
+
+        # walk the tree to see if the values were stored correctly
+        parent = None
+        i = 0
+
+        treeiter = tree_store.iter_children(parent)
+        while treeiter:
+            i = tree_store.get_value(treeiter, 0)
+            s = tree_store.get_value(treeiter, 1)
+            obj = tree_store.get_value(treeiter, 2)
+            obj.check(i, s)
+            obj2 = tree_store.get_value(treeiter, 3)
+            self.assertEqual(obj, obj2)
+
+            pyobj = tree_store.get_value(treeiter, 4)
+            self.assertEqual(pyobj, test_pyobj)
+            pydict = tree_store.get_value(treeiter, 5)
+            self.assertEqual(pydict, test_pydict)
+            pylist = tree_store.get_value(treeiter, 6)
+            self.assertEqual(pylist, test_pylist)
+
+            bool_1 = tree_store.get_value(treeiter, 7)
+            bool_2 = tree_store.get_value(treeiter, 8)
+            self.assertEqual(bool_1, bool_2)
+            self.assertTrue(isinstance(bool_1, bool))
+            self.assertTrue(isinstance(bool_2, bool))
+
+            uint_ = tree_store.get_value(treeiter, 9)
+            self.assertEqual(uint_, i)
+            ulong_ = tree_store.get_value(treeiter, 10)
+            self.assertEqual(ulong_, GLib.MAXULONG)
+            int64_ = tree_store.get_value(treeiter, 11)
+            self.assertEqual(int64_, GLib.MININT64)
+            uint64_ = tree_store.get_value(treeiter, 12)
+            self.assertEqual(uint64_, 0xffffffffffffffff)
+            uchar_ = tree_store.get_value(treeiter, 13)
+            self.assertEqual(ord(uchar_), 254)
+            char_ = tree_store.get_value(treeiter, 14)
+            self.assertEqual(char_, 'a')
+
+            parent = treeiter
+            treeiter = tree_store.iter_children(parent)
+
+        self.assertEqual(i, 99)
+
+    def test_tree_store_signals(self):
+        tree_store = Gtk.TreeStore(int, bool)
+
+        def on_row_inserted(tree_store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-inserted')
+
+        def on_row_changed(tree_store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-changed')
+
+        signals = []
+        tree_store.connect('row-inserted', on_row_inserted, signals)
+        tree_store.connect('row-changed', on_row_changed, signals)
+
+        # adding rows with and without data should only call one signal
+        tree_store.append(None, (0, False))
+        self.assertEqual(signals, ['row-inserted'])
+
+        signals.pop()
+        tree_store.append(None)
+        self.assertEqual(signals, ['row-inserted'])
+
+        signals.pop()
+        tree_store.prepend(None, (0, False))
+        self.assertEqual(signals, ['row-inserted'])
+
+        signals.pop()
+        tree_store.prepend(None)
+        self.assertEqual(signals, ['row-inserted'])
+
+        signals.pop()
+        tree_store.insert(None, 1, (0, False))
+        self.assertEqual(signals, ['row-inserted'])
+
+        signals.pop()
+        tree_store.insert(None, 1)
+        self.assertEqual(signals, ['row-inserted'])
+
+        # One set one signal
+        signals.pop()
+        tree_iter = tree_store.append(None, (10, False))
+        tree_store.set(tree_iter, (0, 1), (20, True))
+        self.assertEqual(signals, ['row-inserted', 'row-changed'])
+
+    def test_list_store(self):
+        class TestPyObject(object):
+            pass
+
+        test_pyobj = TestPyObject()
+        test_pydict = {1: 1, "2": 2, "3": "3"}
+        test_pylist = [1, "2", "3"]
+
+        list_store = Gtk.ListStore(int, str, 'GIOverrideTreeAPITest', object, object, object, bool, bool)
+        for i in range(1, 93):
+            label = 'this is row #%d' % i
+            testobj = TestGtk.TestClass(self, i, label)
+            list_store.append((i,
+                               label,
+                               testobj,
+                               test_pyobj,
+                               test_pydict,
+                               test_pylist,
+                               i % 2,
+                               bool(i % 2)))
+
+        i = 93
+        label = u'this is row #93'
+        treeiter = list_store.append()
+        list_store.set_value(treeiter, 0, i)
+        list_store.set_value(treeiter, 1, label)
+        list_store.set_value(treeiter, 2, TestGtk.TestClass(self, i, label))
+        list_store.set_value(treeiter, 3, test_pyobj)
+        list_store.set_value(treeiter, 4, test_pydict)
+        list_store.set_value(treeiter, 5, test_pylist)
+        list_store.set_value(treeiter, 6, 1)
+        list_store.set_value(treeiter, 7, True)
+
+        # test prepend
+        label = 'this is row #0'
+        list_store.prepend((0,
+                            label,
+                            TestGtk.TestClass(self, 0, label),
+                            test_pyobj,
+                            test_pydict,
+                            test_pylist,
+                            0,
+                            False))
+
+        # test automatic unicode->str conversion
+        i = 94
+        label = u'this is row #94'
+        treeiter = list_store.append((i,
+                                      label,
+                                      TestGtk.TestClass(self, i, label),
+                                      test_pyobj,
+                                      test_pydict,
+                                      test_pylist,
+                                      0,
+                                      False))
+
+        # add sorted items out of order to test insert* apis
+        # also test sending in None to not set a column
+        i = 97
+        label = 'this is row #97'
+        treeiter = list_store.append((None,
+                                      None,
+                                      None,
+                                      test_pyobj,
+                                      None,
+                                      test_pylist,
+                                      1,
+                                      None))
+
+        list_store.set_value(treeiter, 0, i)
+        list_store.set_value(treeiter, 1, label)
+        list_store.set_value(treeiter, 2, TestGtk.TestClass(self, i, label))
+        list_store.set_value(treeiter, 4, test_pydict)
+        list_store.set_value(treeiter, 7, True)
+
+        # this should append
+        i = 99
+        label = 'this is row #99'
+        list_store.insert(9999, (i,
+                                 label,
+                                 TestGtk.TestClass(self, i, label),
+                                 test_pyobj,
+                                 test_pydict,
+                                 test_pylist,
+                                 1,
+                                 True))
+
+        i = 96
+        label = 'this is row #96'
+        list_store.insert_before(treeiter, (i,
+                                            label,
+                                            TestGtk.TestClass(self, i, label),
+                                            test_pyobj,
+                                            test_pydict,
+                                            test_pylist,
+                                            0,
+                                            False))
+
+        i = 98
+        label = 'this is row #98'
+        list_store.insert_after(treeiter, (i,
+                                           label,
+                                           TestGtk.TestClass(self, i, label),
+                                           test_pyobj,
+                                           test_pydict,
+                                           test_pylist,
+                                           0,
+                                           False))
+
+        i = 95
+        label = 'this is row #95'
+        list_store.insert(95, (i,
+                               label,
+                               TestGtk.TestClass(self, i, label),
+                               test_pyobj,
+                               test_pydict,
+                               test_pylist,
+                               1,
+                               True))
+
+        i = 100
+        label = 'this is row #100'
+        treeiter = list_store.append()
+        list_store.set(treeiter,
+                       1, label,
+                       0, i,
+                       2, TestGtk.TestClass(self, i, label),
+                       3, test_pyobj,
+                       4, test_pydict,
+                       5, test_pylist,
+                       6, 0,
+                       7, False)
+        i = 101
+        label = 'this is row #101'
+        treeiter = list_store.append()
+        list_store.set(treeiter, {1: label,
+                                  0: i,
+                                  2: TestGtk.TestClass(self, i, label),
+                                  3: test_pyobj,
+                                  4: test_pydict,
+                                  5: test_pylist,
+                                  6: 1,
+                                  7: True})
+        i = 102
+        label = 'this is row #102'
+        treeiter = list_store.append()
+        list_store.set(treeiter, (1, 0, 2, 3, 4, 5, 6, 7),
+                                 (label,
+                                  i,
+                                  TestGtk.TestClass(self, i, label),
+                                  test_pyobj,
+                                  test_pydict,
+                                  test_pylist,
+                                  0,
+                                  False))
+
+        self.assertEqual(len(list_store), 103)
+
+        # walk the list to see if the values were stored correctly
+        i = 0
+        treeiter = list_store.get_iter_first()
+
+        counter = 0
+        while treeiter:
+            i = list_store.get_value(treeiter, 0)
+            self.assertEqual(i, counter)
+            s = list_store.get_value(treeiter, 1)
+            obj = list_store.get_value(treeiter, 2)
+            obj.check(i, s)
+
+            pyobj = list_store.get_value(treeiter, 3)
+            self.assertEqual(pyobj, test_pyobj)
+            pydict = list_store.get_value(treeiter, 4)
+            self.assertEqual(pydict, test_pydict)
+            pylist = list_store.get_value(treeiter, 5)
+            self.assertEqual(pylist, test_pylist)
+
+            bool_1 = list_store.get_value(treeiter, 6)
+            bool_2 = list_store.get_value(treeiter, 7)
+            self.assertEqual(bool_1, bool_2)
+            self.assertTrue(isinstance(bool_1, bool))
+            self.assertTrue(isinstance(bool_2, bool))
+
+            treeiter = list_store.iter_next(treeiter)
+
+            counter += 1
+
+        self.assertEqual(i, 102)
+
+    def test_list_store_sort(self):
+        def comp1(model, row1, row2, user_data):
+            v1 = model[row1][1]
+            v2 = model[row2][1]
+
+            # make "m" smaller than anything else
+            if v1.startswith('m') and not v2.startswith('m'):
+                return -1
+            if v2.startswith('m') and not v1.startswith('m'):
+                return 1
+            return (v1 > v2) - (v1 < v2)
+
+        list_store = Gtk.ListStore(int, str)
+        list_store.set_sort_func(2, comp1, None)
+        list_store.append((1, 'apples'))
+        list_store.append((3, 'oranges'))
+        list_store.append((2, 'mango'))
+
+        # not sorted yet, should be original order
+        self.assertEqual([list(i) for i in list_store],
+                         [[1, 'apples'], [3, 'oranges'], [2, 'mango']])
+
+        # sort with our custom function
+        list_store.set_sort_column_id(2, Gtk.SortType.ASCENDING)
+        self.assertEqual([list(i) for i in list_store],
+                         [[2, 'mango'], [1, 'apples'], [3, 'oranges']])
+
+        list_store.set_sort_column_id(2, Gtk.SortType.DESCENDING)
+        self.assertEqual([list(i) for i in list_store],
+                         [[3, 'oranges'], [1, 'apples'], [2, 'mango']])
+
+    def test_list_store_signals(self):
+        list_store = Gtk.ListStore(int, bool)
+
+        def on_row_inserted(list_store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-inserted')
+
+        def on_row_changed(list_store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-changed')
+
+        signals = []
+        list_store.connect('row-inserted', on_row_inserted, signals)
+        list_store.connect('row-changed', on_row_changed, signals)
+
+        # adding rows with and without data should only call one signal
+        list_store.append((0, False))
+        self.assertEqual(signals, ['row-inserted'])
+
+        signals.pop()
+        list_store.append()
+        self.assertEqual(signals, ['row-inserted'])
+
+        signals.pop()
+        list_store.prepend((0, False))
+        self.assertEqual(signals, ['row-inserted'])
+
+        signals.pop()
+        list_store.prepend()
+        self.assertEqual(signals, ['row-inserted'])
+
+        signals.pop()
+        list_store.insert(1, (0, False))
+        self.assertEqual(signals, ['row-inserted'])
+
+        signals.pop()
+        list_store.insert(1)
+        self.assertEqual(signals, ['row-inserted'])
+
+        # One set one signal
+        signals.pop()
+        tree_iter = list_store.append((10, False))
+        list_store.set(tree_iter, (0, 1), (20, True))
+        self.assertEqual(signals, ['row-inserted', 'row-changed'])
+
+    def test_list_store_insert_before(self):
+        store = Gtk.ListStore(object)
+        signals = []
+
+        def on_row_inserted(store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-inserted')
+
+        def on_row_changed(store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-changed')
+
+        store.connect('row-inserted', on_row_inserted, signals)
+        store.connect('row-changed', on_row_changed, signals)
+
+        iter_ = store.append([0])
+        assert store.get_value(iter_, 0) == 0
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # append empty
+        iter_ = store.insert_before(None)
+        assert store.get_path(iter_).get_indices() == [1]
+        assert store.get_value(iter_, 0) is None
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # insert empty
+        iter_ = store.insert_before(iter_)
+        assert store.get_path(iter_).get_indices() == [1]
+        assert store.get_value(iter_, 0) is None
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # append non-empty
+        iter_ = store.insert_before(None, [1234])
+        assert store.get_path(iter_).get_indices() == [3]
+        assert store.get_value(iter_, 0) == 1234
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # insert non-empty
+        iter_ = store.insert_before(iter_, [4321])
+        assert store.get_path(iter_).get_indices() == [3]
+        assert store.get_value(iter_, 0) == 4321
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        assert [r[0] for r in store] == [0, None, None, 4321, 1234]
+
+    def test_list_store_insert_after(self):
+        store = Gtk.ListStore(object)
+        signals = []
+
+        def on_row_inserted(store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-inserted')
+
+        def on_row_changed(store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-changed')
+
+        store.connect('row-inserted', on_row_inserted, signals)
+        store.connect('row-changed', on_row_changed, signals)
+
+        iter_ = store.append([0])
+        assert store.get_value(iter_, 0) == 0
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # prepend empty
+        iter_ = store.insert_after(None)
+        assert store.get_path(iter_).get_indices() == [0]
+        assert store.get_value(iter_, 0) is None
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # insert empty
+        iter_ = store.insert_after(iter_)
+        assert store.get_path(iter_).get_indices() == [1]
+        assert store.get_value(iter_, 0) is None
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # prepend non-empty
+        iter_ = store.insert_after(None, [1234])
+        assert store.get_path(iter_).get_indices() == [0]
+        assert store.get_value(iter_, 0) == 1234
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # insert non-empty
+        iter_ = store.insert_after(iter_, [4321])
+        assert store.get_path(iter_).get_indices() == [1]
+        assert store.get_value(iter_, 0) == 4321
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        assert [r[0] for r in store] == [1234, 4321, None, None, 0]
+
+    def test_tree_store_insert_before(self):
+        store = Gtk.TreeStore(object)
+        signals = []
+
+        def on_row_inserted(store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-inserted')
+
+        def on_row_changed(store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-changed')
+
+        store.connect('row-inserted', on_row_inserted, signals)
+        store.connect('row-changed', on_row_changed, signals)
+
+        parent = store.append(None, [-1])
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        iter_ = store.append(parent, [0])
+        assert store.get_path(iter_).get_indices() == [0, 0]
+        assert store.get_value(iter_, 0) == 0
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # append empty
+        iter_ = store.insert_before(parent, None)
+        assert store.get_path(iter_).get_indices() == [0, 1]
+        assert store.get_value(iter_, 0) is None
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # insert empty
+        iter_ = store.insert_before(parent, iter_)
+        assert store.get_path(iter_).get_indices() == [0, 1]
+        assert store.get_value(iter_, 0) is None
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # append non-empty
+        iter_ = store.insert_before(parent, None, [1234])
+        assert store.get_path(iter_).get_indices() == [0, 3]
+        assert store.get_value(iter_, 0) == 1234
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # insert non-empty
+        iter_ = store.insert_before(parent, iter_, [4321])
+        assert store.get_path(iter_).get_indices() == [0, 3]
+        assert store.get_value(iter_, 0) == 4321
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        def func(model, path, iter_, rows):
+            rows.append((path.get_indices(), model[iter_][:]))
+
+        rows = []
+        store.foreach(func, rows)
+        assert rows == [
+            ([0], [-1]), ([0, 0], [0]), ([0, 1], [None]), ([0, 2], [None]),
+            ([0, 3], [4321]), ([0, 4], [1234])]
+
+    def test_tree_store_insert_after(self):
+        store = Gtk.TreeStore(object)
+        signals = []
+
+        def on_row_inserted(store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-inserted')
+
+        def on_row_changed(store, tree_path, tree_iter, signal_list):
+            signal_list.append('row-changed')
+
+        store.connect('row-inserted', on_row_inserted, signals)
+        store.connect('row-changed', on_row_changed, signals)
+
+        parent = store.append(None, [-1])
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        iter_ = store.append(parent, [0])
+        assert store.get_path(iter_).get_indices() == [0, 0]
+        assert store.get_value(iter_, 0) == 0
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # append empty
+        iter_ = store.insert_after(parent, None)
+        assert store.get_path(iter_).get_indices() == [0, 0]
+        assert store.get_value(iter_, 0) is None
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # insert empty
+        iter_ = store.insert_after(parent, iter_)
+        assert store.get_path(iter_).get_indices() == [0, 1]
+        assert store.get_value(iter_, 0) is None
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # append non-empty
+        iter_ = store.insert_after(parent, None, [1234])
+        assert store.get_path(iter_).get_indices() == [0, 0]
+        assert store.get_value(iter_, 0) == 1234
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        # insert non-empty
+        iter_ = store.insert_after(parent, iter_, [4321])
+        assert store.get_path(iter_).get_indices() == [0, 1]
+        assert store.get_value(iter_, 0) == 4321
+        assert signals == ['row-inserted']
+        del signals[:]
+
+        def func(model, path, iter_, rows):
+            rows.append((path.get_indices(), model[iter_][:]))
+
+        rows = []
+        store.foreach(func, rows)
+
+        assert rows == [
+            ([0], [-1]), ([0, 0], [1234]), ([0, 1], [4321]),
+            ([0, 2], [None]), ([0, 3], [None]), ([0, 4], [0])]
+
+    def test_tree_path(self):
+        p1 = Gtk.TreePath()
+        p2 = Gtk.TreePath.new_first()
+        self.assertEqual(p1, p2)
+        self.assertEqual(str(p1), '0')
+        self.assertEqual(len(p1), 1)
+        p1 = Gtk.TreePath(2)
+        p2 = Gtk.TreePath.new_from_string('2')
+        self.assertEqual(p1, p2)
+        self.assertEqual(str(p1), '2')
+        self.assertEqual(len(p1), 1)
+        p1 = Gtk.TreePath('1:2:3')
+        p2 = Gtk.TreePath.new_from_string('1:2:3')
+        self.assertEqual(p1, p2)
+        self.assertEqual(str(p1), '1:2:3')
+        self.assertEqual(len(p1), 3)
+        p1 = Gtk.TreePath((1, 2, 3))
+        p2 = Gtk.TreePath.new_from_string('1:2:3')
+        self.assertEqual(p1, p2)
+        self.assertEqual(str(p1), '1:2:3')
+        self.assertEqual(len(p1), 3)
+        self.assertNotEqual(p1, None)
+        self.assertTrue(p1 > None)
+        self.assertTrue(p1 >= None)
+        self.assertFalse(p1 < None)
+        self.assertFalse(p1 <= None)
+
+        self.assertEqual(tuple(p1), (1, 2, 3))
+        self.assertEqual(p1[0], 1)
+        self.assertEqual(p1[1], 2)
+        self.assertEqual(p1[2], 3)
+        self.assertRaises(IndexError, p1.__getitem__, 3)
+
+    def test_tree_path_empty(self):
+        p1 = Gtk.TreePath.new()
+        assert str(p1) == ""
+
+    def test_tree_model(self):
+        tree_store = Gtk.TreeStore(int, str)
+
+        self.assertTrue(tree_store)
+        self.assertEqual(len(tree_store), 0)
+        self.assertEqual(tree_store.get_iter_first(), None)
+
+        def get_by_index(row, col=None):
+            if col:
+                return tree_store[row][col]
+            else:
+                return tree_store[row]
+
+        self.assertRaises(TypeError, get_by_index, None)
+        self.assertRaises(TypeError, get_by_index, "")
+        self.assertRaises(TypeError, get_by_index, ())
+
+        self.assertRaises(IndexError, get_by_index, "0")
+        self.assertRaises(IndexError, get_by_index, 0)
+        self.assertRaises(IndexError, get_by_index, (0,))
+
+        self.assertRaises(ValueError, tree_store.get_iter, "0")
+        self.assertRaises(ValueError, tree_store.get_iter, 0)
+        self.assertRaises(ValueError, tree_store.get_iter, (0,))
+
+        self.assertRaises(ValueError, tree_store.get_iter_from_string, "0")
+
+        for row in tree_store:
+            self.fail("Should not be reached")
+
+        class DerivedIntType(int):
+            pass
+
+        class DerivedStrType(str):
+            pass
+
+        for i in range(100):
+            label = 'this is row #%d' % i
+            parent = tree_store.append(None, (DerivedIntType(i), DerivedStrType(label),))
+            self.assertNotEqual(parent, None)
+            for j in range(20):
+                label = 'this is child #%d of node #%d' % (j, i)
+                child = tree_store.append(parent, (j, label,))
+                self.assertNotEqual(child, None)
+
+        self.assertTrue(tree_store)
+        self.assertEqual(len(tree_store), 100)
+
+        self.assertEqual(tree_store.iter_previous(tree_store.get_iter(0)), None)
+
+        for i, row in enumerate(tree_store):
+            self.assertEqual(row.model, tree_store)
+            self.assertEqual(row.parent, None)
+
+            self.assertEqual(tree_store[i].path, row.path)
+            self.assertEqual(tree_store[str(i)].path, row.path)
+            self.assertEqual(tree_store[(i,)].path, row.path)
+
+            self.assertEqual(tree_store[i][0], i)
+            self.assertEqual(tree_store[i][1], "this is row #%d" % i)
+
+            aiter = tree_store.get_iter(i)
+            self.assertEqual(tree_store.get_path(aiter), row.path)
+
+            aiter = tree_store.get_iter(str(i))
+            self.assertEqual(tree_store.get_path(aiter), row.path)
+
+            aiter = tree_store.get_iter((i,))
+            self.assertEqual(tree_store.get_path(aiter), row.path)
+
+            self.assertEqual(tree_store.iter_parent(aiter), row.parent)
+
+            next = tree_store.iter_next(aiter)
+            if i < len(tree_store) - 1:
+                self.assertEqual(tree_store.get_path(next), row.next.path)
+                self.assertEqual(tree_store.get_path(tree_store.iter_previous(next)),
+                                 tree_store.get_path(aiter))
+            else:
+                self.assertEqual(next, None)
+
+            self.assertEqual(tree_store.iter_n_children(row.iter), 20)
+
+            child = tree_store.iter_children(row.iter)
+            for j, childrow in enumerate(row.iterchildren()):
+                child_path = tree_store.get_path(child)
+                self.assertEqual(childrow.path, child_path)
+                self.assertEqual(childrow.parent.path, row.path)
+                self.assertEqual(childrow.path, tree_store[child].path)
+                self.assertEqual(childrow.path, tree_store[child_path].path)
+
+                self.assertEqual(childrow[0], tree_store[child][0])
+                self.assertEqual(childrow[0], j)
+                self.assertEqual(childrow[1], tree_store[child][1])
+                self.assertEqual(childrow[1], 'this is child #%d of node #%d' % (j, i))
+
+                self.assertRaises(IndexError, get_by_index, child, 2)
+
+                tree_store[child][1] = 'this was child #%d of node #%d' % (j, i)
+                self.assertEqual(childrow[1], 'this was child #%d of node #%d' % (j, i))
+
+                nth_child = tree_store.iter_nth_child(row.iter, j)
+                self.assertEqual(childrow.path, tree_store.get_path(nth_child))
+
+                childrow2 = tree_store["%d:%d" % (i, j)]
+                self.assertEqual(childrow.path, childrow2.path)
+
+                childrow2 = tree_store[(i, j,)]
+                self.assertEqual(childrow.path, childrow2.path)
+
+                child = tree_store.iter_next(child)
+                if j < 19:
+                    self.assertEqual(childrow.next.path, tree_store.get_path(child))
+                else:
+                    self.assertEqual(child, childrow.next)
+                    self.assertEqual(child, None)
+
+            self.assertEqual(j, 19)
+
+        self.assertEqual(i, 99)
+
+        # negative indices
+        for i in range(-1, -100, -1):
+            i_real = i + 100
+            self.assertEqual(tree_store[i][0], i_real)
+
+            row = tree_store[i]
+            for j in range(-1, -20, -1):
+                j_real = j + 20
+                path = (i_real, j_real,)
+
+                self.assertEqual(tree_store[path][-2], j_real)
+
+                label = 'this was child #%d of node #%d' % (j_real, i_real)
+                self.assertEqual(tree_store[path][-1], label)
+
+                new_label = 'this still is child #%d of node #%d' % (j_real, i_real)
+                tree_store[path][-1] = new_label
+                self.assertEqual(tree_store[path][-1], new_label)
+
+                self.assertRaises(IndexError, get_by_index, path, -3)
+
+        self.assertRaises(IndexError, get_by_index, -101)
+
+        last_row = tree_store[99]
+        self.assertNotEqual(last_row, None)
+
+        for i, childrow in enumerate(last_row.iterchildren()):
+            if i < 19:
+                self.assertTrue(tree_store.remove(childrow.iter))
+            else:
+                self.assertFalse(tree_store.remove(childrow.iter))
+
+        self.assertEqual(i, 19)
+
+        self.assertEqual(tree_store.iter_n_children(last_row.iter), 0)
+        for childrow in last_row.iterchildren():
+            self.fail("Should not be reached")
+
+        aiter = tree_store.get_iter(10)
+        self.assertRaises(TypeError, tree_store.get, aiter, 1, 'a')
+        self.assertRaises(ValueError, tree_store.get, aiter, 1, -1)
+        self.assertRaises(ValueError, tree_store.get, aiter, 1, 100)
+        self.assertEqual(tree_store.get(aiter, 0, 1), (10, 'this is row #10'))
+
+        # check __delitem__
+        self.assertEqual(len(tree_store), 100)
+        aiter = tree_store.get_iter(10)
+        del tree_store[aiter]
+        self.assertEqual(len(tree_store), 99)
+        self.assertRaises(TypeError, tree_store.__delitem__, None)
+        self.assertRaises(IndexError, tree_store.__delitem__, -101)
+        self.assertRaises(IndexError, tree_store.__delitem__, 101)
+
+    def test_tree_model_get_iter_fail(self):
+        # TreeModel class with a failing get_iter()
+        class MyTreeModel(GObject.GObject, Gtk.TreeModel):
+            def do_get_iter(self, iter):
+                return (False, None)
+
+        tm = MyTreeModel()
+        self.assertEqual(tm.get_iter_first(), None)
+
+    def test_tree_model_edit(self):
+        model = Gtk.ListStore(int, str, float)
+        model.append([1, "one", -0.1])
+        model.append([2, "two", -0.2])
+
+        def set_row(value):
+            model[1] = value
+
+        self.assertRaises(TypeError, set_row, 3)
+        self.assertRaises(TypeError, set_row, "three")
+        self.assertRaises(ValueError, set_row, [])
+        self.assertRaises(ValueError, set_row, [3, "three"])
+
+        model[0] = (3, "three", -0.3)
+
+    def test_tree_row_slice(self):
+        model = Gtk.ListStore(int, str, float)
+        model.append([1, "one", -0.1])
+
+        self.assertEqual([1, "one", -0.1], model[0][:])
+        self.assertEqual([1, "one"], model[0][:2])
+        self.assertEqual(["one", -0.1], model[0][1:])
+        self.assertEqual(["one"], model[0][1:-1])
+        self.assertEqual([1], model[0][:-2])
+        self.assertEqual([], model[0][5:])
+        self.assertEqual([1, -0.1], model[0][0:3:2])
+
+        model[0][:] = (2, "two", -0.2)
+        self.assertEqual([2, "two", -0.2], model[0][:])
+
+        model[0][:2] = (3, "three")
+        self.assertEqual([3, "three", -0.2], model[0][:])
+
+        model[0][1:] = ("four", -0.4)
+        self.assertEqual([3, "four", -0.4], model[0][:])
+
+        model[0][1:-1] = ("five",)
+        self.assertEqual([3, "five", -0.4], model[0][:])
+
+        model[0][0:3:2] = (6, -0.6)
+        self.assertEqual([6, "five", -0.6], model[0][:])
+
+        def set_row1():
+            model[0][5:] = ("doesn't", "matter",)
+
+        self.assertRaises(ValueError, set_row1)
+
+        def set_row2():
+            model[0][:1] = (0, "zero", 0)
+
+        self.assertRaises(ValueError, set_row2)
+
+        def set_row3():
+            model[0][:2] = ("0", 0)
+
+        self.assertRaises(TypeError, set_row3)
+
+    def test_tree_row_sequence(self):
+        model = Gtk.ListStore(int, str, float)
+        model.append([1, "one", -0.1])
+
+        self.assertEqual([1, "one", -0.1], model[0][0, 1, 2])
+        self.assertEqual([1, "one"], model[0][0, 1])
+        self.assertEqual(["one", -0.1], model[0][1, 2])
+        self.assertEqual("one", model[0][1])
+        self.assertEqual([1, -0.1], model[0][0, 2])
+        self.assertEqual([-0.1, 1], model[0][2, 0])
+
+        model[0][0, 1, 2] = (2, "two", -0.2)
+        self.assertEqual([2, "two", -0.2], model[0][0, 1, 2])
+
+        model[0][0, 1] = (3, "three")
+        self.assertEqual([3, "three"], model[0][0, 1])
+
+        model[0][1, 2] = ("four", -0.4)
+        self.assertEqual(["four", -0.4], model[0][1, 2])
+
+        model[0][0, 2] = (5, -0.5)
+        self.assertEqual([5, -0.5], model[0][0, 2])
+
+        model[0][0, 1, 2] = (6, "six", -0.6)
+        self.assertEqual([-0.6, 6, "six"], model[0][2, 0, 1])
+
+        def set_row1():
+            model[0][4, 5] = ("shouldn't", "work",)
+
+        self.assertRaises(IndexError, set_row1)
+
+        def set_row2():
+            model[0][0, 1] = (0, "zero", 0)
+
+        self.assertRaises(ValueError, set_row2)
+
+        def set_row3():
+            model[0][0, 1] = ("shouldn't", 0)
+
+        self.assertRaises(TypeError, set_row3)
+
+        def set_row4():
+            model[0][0, "two"] = (0, "zero")
+
+        self.assertRaises(TypeError, set_row4)
+
+    def test_tree_model_set_value_to_none(self):
+        # Tests allowing the usage of None to set an empty value on a model.
+        store = Gtk.ListStore(str)
+        row = store.append(['test'])
+        self.assertSequenceEqual(store[0][:], ['test'])
+        store.set_value(row, 0, None)
+        self.assertSequenceEqual(store[0][:], [None])
+
+    def test_signal_emission_tree_path_coerce(self):
+        class Model(GObject.Object, Gtk.TreeModel):
+            pass
+
+        model = Model()
+        tree_paths = []
+
+        def on_any_signal(model, path, *args):
+            tree_paths.append(path.to_string())
+
+        model.connect('row-changed', on_any_signal)
+        model.connect('row-deleted', on_any_signal)
+        model.connect('row-has-child-toggled', on_any_signal)
+        model.connect('row-inserted', on_any_signal)
+
+        model.row_changed('0', Gtk.TreeIter())
+        self.assertEqual(tree_paths[-1], '0')
+
+        model.row_deleted('1')
+        self.assertEqual(tree_paths[-1], '1')
+
+        model.row_has_child_toggled('2', Gtk.TreeIter())
+        self.assertEqual(tree_paths[-1], '2')
+
+        model.row_inserted('3', Gtk.TreeIter())
+        self.assertEqual(tree_paths[-1], '3')
+
+    def test_tree_model_filter(self):
+        model = Gtk.ListStore(int, str, float)
+        model.append([1, "one", -0.1])
+        model.append([2, "two", -0.2])
+
+        filtered = Gtk.TreeModelFilter(child_model=model)
+
+        self.assertEqual(filtered[0][1], 'one')
+        filtered[0][1] = 'ONE'
+        self.assertEqual(filtered[0][1], 'ONE')
+
+    def test_list_store_performance(self):
+        model = Gtk.ListStore(int, str)
+
+        iterations = 2000
+        start = time.clock()
+        i = iterations
+        while i > 0:
+            model.append([1, 'hello'])
+            i -= 1
+        end = time.clock()
+        sys.stderr.write('[%.0f Âµs/append] ' % ((end - start) * 1000000 / iterations))
+
+    def test_filter_new_default(self):
+        # Test filter_new accepts implicit default of None
+        model = Gtk.ListStore(int)
+        filt = model.filter_new()
+        self.assertTrue(filt is not None)
+
+
+@unittest.skipIf(sys.platform == "darwin", "hangs")
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestTreeView(unittest.TestCase):
+    def test_tree_view(self):
+        store = Gtk.ListStore(int, str)
+        store.append((0, "foo"))
+        store.append((1, "bar"))
+        view = Gtk.TreeView()
+
+        with realized(view):
+            view.set_cursor(store[1].path)
+            view.set_cursor(str(store[1].path))
+
+            view.get_cell_area(store[1].path)
+            view.get_cell_area(str(store[1].path))
+
+    def test_tree_view_column(self):
+        cell = Gtk.CellRendererText()
+        col = Gtk.TreeViewColumn(title='This is just a test',
+                                 cell_renderer=cell,
+                                 text=0,
+                                 style=2)
+
+        # Regression test for: https://bugzilla.gnome.org/show_bug.cgi?id=711173
+        col.set_cell_data_func(cell, None, None)
+
+    def test_tree_view_add_column_with_attributes(self):
+        model = Gtk.ListStore(str, str, str)
+        # deliberately use out-of-order sorting here; we assign column 0 to
+        # model index 2, etc.
+        model.append(['cell13', 'cell11', 'cell12'])
+        model.append(['cell23', 'cell21', 'cell22'])
+
+        tree = Gtk.TreeView(model=model)
+        cell1 = Gtk.CellRendererText()
+        cell2 = Gtk.CellRendererText()
+        cell3 = Gtk.CellRendererText()
+        cell4 = Gtk.CellRendererText()
+
+        tree.insert_column_with_attributes(0, 'Head2', cell2, text=2)
+        tree.insert_column_with_attributes(0, 'Head1', cell1, text=1)
+        tree.insert_column_with_attributes(-1, 'Head3', cell3, text=0)
+        # unconnected
+        tree.insert_column_with_attributes(-1, 'Head4', cell4)
+
+        with realized(tree):
+            tree.set_cursor(model[0].path)
+            while Gtk.events_pending():
+                Gtk.main_iteration()
+
+            self.assertEqual(tree.get_column(0).get_title(), 'Head1')
+            self.assertEqual(tree.get_column(1).get_title(), 'Head2')
+            self.assertEqual(tree.get_column(2).get_title(), 'Head3')
+            self.assertEqual(tree.get_column(3).get_title(), 'Head4')
+
+            # cursor should be at the first row
+            self.assertEqual(cell1.props.text, 'cell11')
+            self.assertEqual(cell2.props.text, 'cell12')
+            self.assertEqual(cell3.props.text, 'cell13')
+            self.assertEqual(cell4.props.text, None)
+
+    def test_tree_view_column_set_attributes(self):
+        store = Gtk.ListStore(int, str)
+        directors = ['Fellini', 'Tarantino', 'Tarkovskiy']
+        for i, director in enumerate(directors):
+            store.append([i, director])
+
+        treeview = Gtk.TreeView()
+        treeview.set_model(store)
+
+        column = Gtk.TreeViewColumn()
+        treeview.append_column(column)
+
+        cell = Gtk.CellRendererText()
+        column.pack_start(cell, expand=True)
+        column.set_attributes(cell, text=1)
+
+        with realized(treeview):
+            self.assertTrue(cell.props.text in directors)
+
+    def test_tree_selection(self):
+        store = Gtk.ListStore(int, str)
+        for i in range(10):
+            store.append((i, "foo"))
+        view = Gtk.TreeView()
+        view.set_model(store)
+        firstpath = store.get_path(store.get_iter_first())
+        sel = view.get_selection()
+
+        sel.select_path(firstpath)
+        (m, s) = sel.get_selected()
+        self.assertEqual(m, store)
+        self.assertEqual(store.get_path(s), firstpath)
+
+        sel.select_path(0)
+        (m, s) = sel.get_selected()
+        self.assertEqual(m, store)
+        self.assertEqual(store.get_path(s), firstpath)
+
+        sel.select_path("0:0")
+        (m, s) = sel.get_selected()
+        self.assertEqual(m, store)
+        self.assertEqual(store.get_path(s), firstpath)
+
+        sel.select_path((0, 0))
+        (m, s) = sel.get_selected()
+        self.assertEqual(m, store)
+        self.assertEqual(store.get_path(s), firstpath)
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestTextBuffer(unittest.TestCase):
+    def test_text_buffer(self):
+        self.assertEqual(Gtk.TextBuffer, gi.overrides.Gtk.TextBuffer)
+        buffer = Gtk.TextBuffer()
+        tag = buffer.create_tag('title', font='Sans 18')
+
+        self.assertEqual(tag.props.name, 'title')
+        self.assertEqual(tag.props.font, 'Sans 18')
+
+        (start, end) = buffer.get_bounds()
+
+        mark = buffer.create_mark(None, start)
+        self.assertFalse(mark.get_left_gravity())
+
+        buffer.set_text('Hello Jane Hello Bob')
+        (start, end) = buffer.get_bounds()
+        text = buffer.get_text(start, end, False)
+        self.assertEqual(text, 'Hello Jane Hello Bob')
+
+        buffer.set_text('')
+        (start, end) = buffer.get_bounds()
+        text = buffer.get_text(start, end, False)
+        self.assertEqual(text, '')
+
+        buffer.insert(end, 'HelloHello')
+        buffer.insert(end, ' Bob')
+
+        cursor_iter = end.copy()
+        cursor_iter.backward_chars(9)
+        buffer.place_cursor(cursor_iter)
+        buffer.insert_at_cursor(' Jane ')
+
+        (start, end) = buffer.get_bounds()
+        text = buffer.get_text(start, end, False)
+        self.assertEqual(text, 'Hello Jane Hello Bob')
+
+        sel = buffer.get_selection_bounds()
+        self.assertEqual(sel, ())
+        buffer.select_range(start, end)
+        sel = buffer.get_selection_bounds()
+        self.assertTrue(sel[0].equal(start))
+        self.assertTrue(sel[1].equal(end))
+
+        buffer.set_text('')
+        buffer.insert_with_tags(buffer.get_start_iter(), 'HelloHello')
+        start, end = buffer.get_bounds()
+        text = buffer.get_text(start, end, False)
+        self.assertEqual(text, 'HelloHello')
+
+        buffer.set_text('')
+        buffer.insert_with_tags_by_name(buffer.get_start_iter(), 'HelloHello')
+        start, end = buffer.get_bounds()
+        text = buffer.get_text(start, end, False)
+        self.assertEqual(text, 'HelloHello')
+
+        try:
+            starts_tag = Gtk.TextIter.starts_tag
+        except AttributeError:
+            starts_tag = Gtk.TextIter.begins_tag
+
+        buffer.set_text('')
+        buffer.insert_with_tags(buffer.get_start_iter(), 'HelloHello', tag)
+        (start, end) = buffer.get_bounds()
+        self.assertTrue(starts_tag(start, tag))
+        self.assertTrue(start.has_tag(tag))
+
+        buffer.set_text('')
+        buffer.insert_with_tags_by_name(buffer.get_start_iter(), 'HelloHello', 'title')
+        (start, end) = buffer.get_bounds()
+        self.assertTrue(starts_tag(start, tag))
+        self.assertTrue(start.has_tag(tag))
+
+        self.assertRaises(ValueError, buffer.insert_with_tags_by_name,
+                          buffer.get_start_iter(), 'HelloHello', 'unknowntag')
+
+    def test_text_iter(self):
+        try:
+            starts_tag = Gtk.TextIter.starts_tag
+        except AttributeError:
+            starts_tag = Gtk.TextIter.begins_tag
+
+        self.assertEqual(Gtk.TextIter, gi.overrides.Gtk.TextIter)
+        buffer = Gtk.TextBuffer()
+        buffer.set_text('Hello Jane Hello Bob')
+        tag = buffer.create_tag('title', font='Sans 18')
+        (start, end) = buffer.get_bounds()
+        start.forward_chars(10)
+        buffer.apply_tag(tag, start, end)
+        self.assertTrue(starts_tag(start))
+        self.assertTrue(end.ends_tag())
+        self.assertTrue(start.toggles_tag())
+        self.assertTrue(end.toggles_tag())
+        start.backward_chars(1)
+        self.assertFalse(starts_tag(start))
+        self.assertFalse(start.ends_tag())
+        self.assertFalse(start.toggles_tag())
+
+    def test_text_buffer_search(self):
+        buffer = Gtk.TextBuffer()
+        buffer.set_text('Hello World Hello GNOME')
+
+        i = buffer.get_iter_at_offset(0)
+        self.assertTrue(isinstance(i, Gtk.TextIter))
+
+        self.assertEqual(i.forward_search('world', 0, None), None)
+
+        (start, end) = i.forward_search('World', 0, None)
+        self.assertEqual(start.get_offset(), 6)
+        self.assertEqual(end.get_offset(), 11)
+
+        (start, end) = i.forward_search('world',
+                                        Gtk.TextSearchFlags.CASE_INSENSITIVE,
+                                        None)
+        self.assertEqual(start.get_offset(), 6)
+        self.assertEqual(end.get_offset(), 11)
+
+    def test_insert_text_signal_location_modification(self):
+        # Regression test for: https://bugzilla.gnome.org/show_bug.cgi?id=736175
+
+        def callback(buffer, location, text, length):
+            location.assign(buffer.get_end_iter())
+
+        buffer = Gtk.TextBuffer()
+        buffer.set_text('first line\n')
+        buffer.connect('insert-text', callback)
+
+        # attempt insertion at the beginning of the buffer, the callback will
+        # modify the insert location to the end.
+        buffer.place_cursor(buffer.get_start_iter())
+        buffer.insert_at_cursor('second line\n')
+
+        self.assertEqual(buffer.get_property('text'),
+                         'first line\nsecond line\n')
+
+    @unittest.skipIf(gtkver() < (3, 20, 0), "broken with older gtk")
+    def test_backward_find_char(self):
+        buffer = Gtk.TextBuffer()
+        buffer.set_text('abc')
+        end = buffer.get_iter_at_line(99)
+
+        values = []
+
+        def pred_func(ch, user_data):
+            values.append(ch)
+            return ch == u"a"
+
+        self.assertTrue(end.backward_find_char(pred_func))
+        self.assertEqual(values, [u"c", u"b", u"a"])
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestContainer(unittest.TestCase):
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_child_set_property(self):
+        box = Gtk.Box()
+        child = Gtk.Button()
+        box.pack_start(child, expand=False, fill=True, padding=0)
+
+        box.child_set_property(child, 'padding', 42)
+
+        value = GObject.Value(int)
+        box.child_get_property(child, 'padding', value)
+        self.assertEqual(value.get_int(), 42)
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_child_get_property_gvalue(self):
+        box = Gtk.Box()
+        child = Gtk.Button()
+        box.pack_start(child, expand=False, fill=True, padding=42)
+
+        value = GObject.Value(int)
+        box.child_get_property(child, 'padding', value)
+        self.assertEqual(value.get_int(), 42)
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_child_get_property_return_with_explicit_gvalue(self):
+        box = Gtk.Box()
+        child = Gtk.Button()
+        box.pack_start(child, expand=False, fill=True, padding=42)
+
+        value = GObject.Value(int)
+        result = box.child_get_property(child, 'padding', value)
+        self.assertEqual(result, 42)
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_child_get_property_return_with_implicit_gvalue(self):
+        box = Gtk.Box()
+        child = Gtk.Button()
+        box.pack_start(child, expand=False, fill=True, padding=42)
+
+        result = box.child_get_property(child, 'padding')
+        self.assertEqual(result, 42)
+
+    def test_child_get_property_error(self):
+        box = Gtk.Box()
+        child = Gtk.Button()
+        if Gtk_version == "4.0":
+            box.pack_start(child, expand=False, fill=True)
+        else:
+            box.pack_start(child, expand=False, fill=True, padding=42)
+        with self.assertRaises(ValueError):
+            box.child_get_property(child, 'not-a-valid-child-property')
+
+    @unittest.skipIf(Gtk_version == "4.0", "not in gtk4")
+    def test_child_get_and_set(self):
+        box = Gtk.Box()
+        child = Gtk.Button()
+        box.pack_start(child, expand=True, fill=True, padding=42)
+
+        expand, fill, padding = box.child_get(child, 'expand', 'fill', 'padding')
+        self.assertEqual(expand, True)
+        self.assertEqual(fill, True)
+        self.assertEqual(padding, 42)
+
+        box.child_set(child, expand=False, fill=False, padding=21, pack_type=1)
+        expand, fill, padding, pack_type = box.child_get(child, 'expand', 'fill', 'padding', 'pack-type')
+        self.assertEqual(expand, False)
+        self.assertEqual(fill, False)
+        self.assertEqual(padding, 21)
+
+    @unittest.skipIf(Gtk_version != "4.0", "only in gtk4")
+    def test_child_get_and_set_gtk4(self):
+        # padding got removed in gtk4
+        box = Gtk.Box()
+        child = Gtk.Button()
+        box.pack_start(child, expand=True, fill=True)
+
+        expand, fill = box.child_get(child, 'expand', 'fill')
+        self.assertEqual(expand, True)
+        self.assertEqual(fill, True)
+
+        box.child_set(child, expand=False, fill=False, pack_type=1)
+        expand, fill, pack_type = box.child_get(child, 'expand', 'fill', 'pack-type')
+        self.assertEqual(expand, False)
+        self.assertEqual(fill, False)
diff --git a/tests/test_overrides_pango.py b/tests/test_overrides_pango.py
new file mode 100644 (file)
index 0000000..a789715
--- /dev/null
@@ -0,0 +1,49 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import unittest
+
+try:
+    from gi.repository import Pango
+    from gi.repository import PangoCairo
+except ImportError:
+    Pango = None
+    PangoCairo = None
+
+
+@unittest.skipUnless(Pango, 'Pango not available')
+class TestPango(unittest.TestCase):
+
+    def test_default_font_description(self):
+        desc = Pango.FontDescription()
+        self.assertEqual(desc.get_variant(), Pango.Variant.NORMAL)
+
+    def test_font_description(self):
+        desc = Pango.FontDescription('monospace')
+        self.assertEqual(desc.get_family(), 'monospace')
+        self.assertEqual(desc.get_variant(), Pango.Variant.NORMAL)
+
+    def test_layout(self):
+        self.assertRaises(TypeError, Pango.Layout)
+        context = Pango.Context()
+        layout = Pango.Layout(context)
+        self.assertEqual(layout.get_context(), context)
+
+        layout.set_markup("Foobar")
+        self.assertEqual(layout.get_text(), "Foobar")
+
+    def test_break_keyword_escape(self):
+        # https://bugzilla.gnome.org/show_bug.cgi?id=697363
+        self.assertTrue(hasattr(Pango, 'break_'))
+        self.assertTrue(Pango.break_ is not None)
+
+    def test_context_get_metrics(self):
+        # Test default "language" argument
+        font_map = PangoCairo.font_map_get_default()
+        context = font_map.create_context()
+        desc = Pango.FontDescription('monospace')
+        metrics1 = context.get_metrics(desc)
+        metrics2 = context.get_metrics(desc, context.get_language())
+        self.assertEqual(metrics1.get_ascent(), metrics2.get_ascent())
diff --git a/tests/test_properties.py b/tests/test_properties.py
new file mode 100644 (file)
index 0000000..35b03b7
--- /dev/null
@@ -0,0 +1,1372 @@
+# coding=utf-8
+
+from __future__ import absolute_import
+
+import os
+import gc
+import sys
+import struct
+import types
+import unittest
+import tempfile
+
+import pytest
+
+from gi.repository import GObject
+from gi.repository.GObject import ParamFlags, GType, new
+from gi.repository.GObject import \
+    TYPE_INT, TYPE_UINT, TYPE_LONG, TYPE_ULONG, TYPE_INT64, \
+    TYPE_UINT64, TYPE_GTYPE, TYPE_INVALID, TYPE_NONE, TYPE_STRV, \
+    TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR, TYPE_BOOLEAN, TYPE_FLOAT, \
+    TYPE_DOUBLE, TYPE_POINTER, TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, \
+    TYPE_STRING, TYPE_PYOBJECT, TYPE_VARIANT
+
+from gi.repository.GLib import \
+    MININT, MAXINT, MAXUINT, MINLONG, MAXLONG, MAXULONG, \
+    MAXUINT64, MAXINT64, MININT64
+
+from gi.repository import Gio
+from gi.repository import GLib
+from gi.repository import GIMarshallingTests
+from gi.repository import Regress
+from gi import _propertyhelper as propertyhelper
+
+from gi._compat import long_, PY3, PY2
+from .helper import capture_glib_warnings
+
+
+class PropertyObject(GObject.GObject):
+    normal = GObject.Property(type=str)
+    construct = GObject.Property(
+        type=str,
+        flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT,
+        default='default')
+
+    construct_only = GObject.Property(
+        type=str,
+        flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT_ONLY)
+
+    uint64 = GObject.Property(
+        type=TYPE_UINT64,
+        flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT)
+
+    enum = GObject.Property(
+        type=Gio.SocketType, default=Gio.SocketType.STREAM)
+
+    boxed = GObject.Property(
+        type=GLib.Regex,
+        flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT)
+
+    flags = GObject.Property(
+        type=GIMarshallingTests.Flags,
+        flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT,
+        default=GIMarshallingTests.Flags.VALUE1)
+
+    gtype = GObject.Property(
+        type=TYPE_GTYPE,
+        flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT)
+
+    strings = GObject.Property(
+        type=TYPE_STRV,
+        flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT)
+
+    variant = GObject.Property(
+        type=TYPE_VARIANT,
+        flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT)
+
+    variant_def = GObject.Property(
+        type=TYPE_VARIANT,
+        flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT,
+        default=GLib.Variant('i', 42))
+
+    interface = GObject.Property(
+        type=Gio.File,
+        flags=ParamFlags.READABLE | ParamFlags.WRITABLE | ParamFlags.CONSTRUCT)
+
+
+class PropertyInheritanceObject(Regress.TestObj):
+    # override property from the base class, with a different type
+    string = GObject.Property(type=int)
+
+    # a property entirely defined at the Python level
+    python_prop = GObject.Property(type=str)
+
+
+class PropertySubClassObject(PropertyInheritanceObject):
+    # override property from the base class, with a different type
+    python_prop = GObject.Property(type=int)
+
+
+class TestPropertyInheritanceObject(unittest.TestCase):
+    def test_override_gi_property(self):
+        self.assertNotEqual(Regress.TestObj.props.string.value_type,
+                            PropertyInheritanceObject.props.string.value_type)
+        obj = PropertyInheritanceObject()
+        self.assertEqual(type(obj.props.string), int)
+        obj.props.string = 4
+        self.assertEqual(obj.props.string, 4)
+
+    def test_override_python_property(self):
+        obj = PropertySubClassObject()
+        self.assertEqual(type(obj.props.python_prop), int)
+        obj.props.python_prop = 5
+        self.assertEqual(obj.props.python_prop, 5)
+
+
+class TestPropertyObject(unittest.TestCase):
+    def test_get_set(self):
+        obj = PropertyObject()
+        obj.props.normal = "value"
+        self.assertEqual(obj.props.normal, "value")
+
+    def test_hasattr_on_object(self):
+        obj = PropertyObject()
+        self.assertTrue(hasattr(obj.props, "normal"))
+
+    def test_hasattr_on_class(self):
+        self.assertTrue(hasattr(PropertyObject.props, "normal"))
+
+    def test_set_on_class(self):
+        def set(obj):
+            obj.props.normal = "foobar"
+
+        self.assertRaises(TypeError, set, PropertyObject)
+
+    def test_iteration(self):
+        for obj in (PropertyObject.props, PropertyObject().props):
+            names = []
+            for pspec in obj:
+                gtype = GType(pspec)
+                self.assertEqual(gtype.parent.name, 'GParam')
+                names.append(pspec.name)
+
+            names.sort()
+            self.assertEqual(names, ['boxed',
+                                     'construct',
+                                     'construct-only',
+                                     'enum',
+                                     'flags',
+                                     'gtype',
+                                     'interface',
+                                     'normal',
+                                     'strings',
+                                     'uint64',
+                                     'variant',
+                                     'variant-def'])
+
+    def test_normal(self):
+        obj = new(PropertyObject, normal="123")
+        self.assertEqual(obj.props.normal, "123")
+        obj.set_property('normal', '456')
+        self.assertEqual(obj.props.normal, "456")
+        obj.props.normal = '789'
+        self.assertEqual(obj.props.normal, "789")
+
+    def test_construct(self):
+        obj = new(PropertyObject, construct="123")
+        self.assertEqual(obj.props.construct, "123")
+        obj.set_property('construct', '456')
+        self.assertEqual(obj.props.construct, "456")
+        obj.props.construct = '789'
+        self.assertEqual(obj.props.construct, "789")
+
+    def test_utf8(self):
+        test_utf8 = "♥"
+        unicode_utf8 = u"♥"
+        obj = new(PropertyObject, construct_only=unicode_utf8)
+        self.assertEqual(obj.props.construct_only, test_utf8)
+        obj.set_property('construct', unicode_utf8)
+        self.assertEqual(obj.props.construct, test_utf8)
+        obj.props.normal = unicode_utf8
+        self.assertEqual(obj.props.normal, test_utf8)
+
+    def test_utf8_lone_surrogate(self):
+        obj = PropertyObject()
+        if PY3:
+            with pytest.raises(TypeError):
+                obj.set_property('construct', '\ud83d')
+        else:
+            obj.set_property('construct', '\ud83d')
+
+    def test_int_to_str(self):
+        obj = new(PropertyObject, construct_only=1)
+        self.assertEqual(obj.props.construct_only, '1')
+        obj.set_property('construct', '2')
+        self.assertEqual(obj.props.construct, '2')
+        obj.props.normal = 3
+        self.assertEqual(obj.props.normal, '3')
+
+    def test_construct_only(self):
+        obj = new(PropertyObject, construct_only="123")
+        self.assertEqual(obj.props.construct_only, "123")
+        self.assertRaises(TypeError,
+                          setattr, obj.props, 'construct_only', '456')
+        self.assertRaises(TypeError,
+                          obj.set_property, 'construct-only', '456')
+
+    def test_uint64(self):
+        obj = new(PropertyObject)
+        self.assertEqual(obj.props.uint64, 0)
+        obj.props.uint64 = long_(1)
+        self.assertEqual(obj.props.uint64, long_(1))
+        obj.props.uint64 = 1
+        self.assertEqual(obj.props.uint64, long_(1))
+
+        self.assertRaises((TypeError, OverflowError), obj.set_property, "uint64", long_(-1))
+        self.assertRaises((TypeError, OverflowError), obj.set_property, "uint64", -1)
+
+    def test_uint64_default_value(self):
+        try:
+            class TimeControl(GObject.GObject):
+                __gproperties__ = {
+                    'time': (TYPE_UINT64, 'Time', 'Time',
+                             long_(0), (1 << 64) - 1, long_(0),
+                             ParamFlags.READABLE)
+                    }
+        except OverflowError:
+            (etype, ex) = sys.exc_info()[2:]
+            self.fail(str(ex))
+
+    def test_enum(self):
+        obj = new(PropertyObject)
+        self.assertEqual(obj.props.enum, Gio.SocketType.STREAM)
+        self.assertEqual(obj.enum, Gio.SocketType.STREAM)
+        obj.enum = Gio.SocketType.DATAGRAM
+        self.assertEqual(obj.props.enum, Gio.SocketType.DATAGRAM)
+        self.assertEqual(obj.enum, Gio.SocketType.DATAGRAM)
+        obj.props.enum = Gio.SocketType.STREAM
+        self.assertEqual(obj.props.enum, Gio.SocketType.STREAM)
+        self.assertEqual(obj.enum, Gio.SocketType.STREAM)
+        obj.props.enum = 2
+        self.assertEqual(obj.props.enum, Gio.SocketType.DATAGRAM)
+        self.assertEqual(obj.enum, Gio.SocketType.DATAGRAM)
+        obj.enum = 1
+        self.assertEqual(obj.props.enum, Gio.SocketType.STREAM)
+        self.assertEqual(obj.enum, Gio.SocketType.STREAM)
+
+        self.assertRaises(TypeError, setattr, obj, 'enum', 'foo')
+        self.assertRaises(TypeError, setattr, obj, 'enum', object())
+
+        self.assertRaises(TypeError, GObject.Property, type=Gio.SocketType)
+        self.assertRaises(TypeError, GObject.Property, type=Gio.SocketType,
+                          default=Gio.SocketProtocol.TCP)
+        self.assertRaises(TypeError, GObject.Property, type=Gio.SocketType,
+                          default=object())
+        self.assertRaises(TypeError, GObject.Property, type=Gio.SocketType,
+                          default=1)
+
+    def test_repr(self):
+        prop = GObject.Property(type=int)
+        assert repr(prop) == "<GObject Property (uninitialized) (gint)>"
+
+    def test_flags(self):
+        obj = new(PropertyObject)
+        self.assertEqual(obj.props.flags, GIMarshallingTests.Flags.VALUE1)
+        self.assertEqual(obj.flags, GIMarshallingTests.Flags.VALUE1)
+
+        obj.flags = GIMarshallingTests.Flags.VALUE2 | GIMarshallingTests.Flags.VALUE3
+        self.assertEqual(obj.props.flags, GIMarshallingTests.Flags.VALUE2 | GIMarshallingTests.Flags.VALUE3)
+        self.assertEqual(obj.flags, GIMarshallingTests.Flags.VALUE2 | GIMarshallingTests.Flags.VALUE3)
+
+        self.assertRaises(TypeError, setattr, obj, 'flags', 'foo')
+        self.assertRaises(TypeError, setattr, obj, 'flags', object())
+        self.assertRaises(TypeError, setattr, obj, 'flags', None)
+
+        self.assertRaises(TypeError, GObject.Property,
+                          type=GIMarshallingTests.Flags, default='foo')
+        self.assertRaises(TypeError, GObject.Property,
+                          type=GIMarshallingTests.Flags, default=object())
+        self.assertRaises(TypeError, GObject.Property,
+                          type=GIMarshallingTests.Flags, default=None)
+
+    def test_gtype(self):
+        obj = new(PropertyObject)
+
+        self.assertEqual(obj.props.gtype, TYPE_NONE)
+        self.assertEqual(obj.gtype, TYPE_NONE)
+
+        obj.gtype = TYPE_UINT64
+        self.assertEqual(obj.props.gtype, TYPE_UINT64)
+        self.assertEqual(obj.gtype, TYPE_UINT64)
+
+        obj.gtype = TYPE_INVALID
+        self.assertEqual(obj.props.gtype, TYPE_INVALID)
+        self.assertEqual(obj.gtype, TYPE_INVALID)
+
+        # GType parameters do not support defaults in GLib
+        self.assertRaises(TypeError, GObject.Property, type=TYPE_GTYPE,
+                          default=TYPE_INT)
+
+        # incompatible type
+        self.assertRaises(TypeError, setattr, obj, 'gtype', 'foo')
+        self.assertRaises(TypeError, setattr, obj, 'gtype', object())
+
+        self.assertRaises(TypeError, GObject.Property, type=TYPE_GTYPE,
+                          default='foo')
+        self.assertRaises(TypeError, GObject.Property, type=TYPE_GTYPE,
+                          default=object())
+
+        # set in constructor
+        obj = new(PropertyObject, gtype=TYPE_UINT)
+        self.assertEqual(obj.props.gtype, TYPE_UINT)
+        self.assertEqual(obj.gtype, TYPE_UINT)
+
+    def test_boxed(self):
+        obj = new(PropertyObject)
+
+        regex = GLib.Regex.new('[a-z]*', 0, 0)
+        obj.props.boxed = regex
+        self.assertEqual(obj.props.boxed.get_pattern(), '[a-z]*')
+        self.assertEqual(obj.boxed.get_pattern(), '[a-z]*')
+
+        self.assertRaises(TypeError, setattr, obj, 'boxed', 'foo')
+        self.assertRaises(TypeError, setattr, obj, 'boxed', object())
+
+    def test_strings(self):
+        obj = new(PropertyObject)
+
+        # Should work with actual GStrv objects as well as
+        # Python string lists
+        class GStrv(list):
+            __gtype__ = GObject.TYPE_STRV
+
+        self.assertEqual(obj.props.strings, GStrv([]))
+        self.assertEqual(obj.strings, GStrv([]))
+        self.assertEqual(obj.props.strings, [])
+        self.assertEqual(obj.strings, [])
+
+        obj.strings = ['hello', 'world']
+        self.assertEqual(obj.props.strings, ['hello', 'world'])
+        self.assertEqual(obj.strings, ['hello', 'world'])
+
+        obj.strings = GStrv(['hello', 'world'])
+        self.assertEqual(obj.props.strings, GStrv(['hello', 'world']))
+        self.assertEqual(obj.strings, GStrv(['hello', 'world']))
+
+        obj.strings = []
+        self.assertEqual(obj.strings, [])
+        obj.strings = GStrv([])
+        self.assertEqual(obj.strings, GStrv([]))
+
+        p = GObject.Property(type=TYPE_STRV, default=['hello', '1'])
+        self.assertEqual(p.default, ['hello', '1'])
+        self.assertEqual(p.type, TYPE_STRV)
+        p = GObject.Property(type=TYPE_STRV, default=GStrv(['hello', '1']))
+        self.assertEqual(p.default, ['hello', '1'])
+        self.assertEqual(p.type, TYPE_STRV)
+
+        # set in constructor
+        obj = new(PropertyObject, strings=['hello', 'world'])
+        self.assertEqual(obj.props.strings, ['hello', 'world'])
+        self.assertEqual(obj.strings, ['hello', 'world'])
+
+        # wrong types
+        self.assertRaises(TypeError, setattr, obj, 'strings', 1)
+        self.assertRaises(TypeError, setattr, obj, 'strings', 'foo')
+        self.assertRaises(TypeError, setattr, obj, 'strings', ['foo', 1])
+
+        self.assertRaises(TypeError, GObject.Property, type=TYPE_STRV,
+                          default=1)
+        self.assertRaises(TypeError, GObject.Property, type=TYPE_STRV,
+                          default='foo')
+        self.assertRaises(TypeError, GObject.Property, type=TYPE_STRV,
+                          default=['hello', 1])
+
+    def test_variant(self):
+        obj = new(PropertyObject)
+
+        self.assertEqual(obj.props.variant, None)
+        self.assertEqual(obj.variant, None)
+
+        obj.variant = GLib.Variant('s', 'hello')
+        self.assertEqual(obj.variant.print_(True), "'hello'")
+
+        obj.variant = GLib.Variant('b', True)
+        self.assertEqual(obj.variant.print_(True), "true")
+
+        obj.props.variant = GLib.Variant('y', 2)
+        self.assertEqual(obj.variant.print_(True), "byte 0x02")
+
+        obj.variant = None
+        self.assertEqual(obj.variant, None)
+
+        # set in constructor
+        obj = new(PropertyObject, variant=GLib.Variant('u', 5))
+        self.assertEqual(obj.props.variant.print_(True), 'uint32 5')
+
+        GObject.Property(type=TYPE_VARIANT, default=GLib.Variant('i', 1))
+
+        # incompatible types
+        self.assertRaises(TypeError, setattr, obj, 'variant', 'foo')
+        self.assertRaises(TypeError, setattr, obj, 'variant', 42)
+
+        self.assertRaises(TypeError, GObject.Property, type=TYPE_VARIANT,
+                          default='foo')
+        self.assertRaises(TypeError, GObject.Property, type=TYPE_VARIANT,
+                          default=object())
+
+    def test_variant_default(self):
+        obj = new(PropertyObject)
+
+        self.assertEqual(obj.props.variant_def.print_(True), '42')
+        self.assertEqual(obj.variant_def.print_(True), '42')
+
+        obj.props.variant_def = GLib.Variant('y', 2)
+        self.assertEqual(obj.variant_def.print_(True), "byte 0x02")
+
+        # set in constructor
+        obj = new(PropertyObject, variant_def=GLib.Variant('u', 5))
+        self.assertEqual(obj.props.variant_def.print_(True), 'uint32 5')
+
+    def test_interface(self):
+        obj = new(PropertyObject)
+
+        path = os.path.join(tempfile.gettempdir(), "some", "path")
+        file = Gio.File.new_for_path(path)
+        obj.props.interface = file
+        self.assertEqual(obj.props.interface.get_path(), path)
+        self.assertEqual(obj.interface.get_path(), path)
+
+        self.assertRaises(TypeError, setattr, obj, 'interface', 'foo')
+        self.assertRaises(TypeError, setattr, obj, 'interface', object())
+
+    def test_range(self):
+        # kiwi code
+        def max(c):
+            return 2 ** ((8 * struct.calcsize(c)) - 1) - 1
+
+        def umax(c):
+            return 2 ** (8 * struct.calcsize(c)) - 1
+
+        maxint = max('i')
+        minint = -maxint - 1
+        maxuint = umax('I')
+        maxlong = max('l')
+        minlong = -maxlong - 1
+        maxulong = umax('L')
+        maxint64 = max('q')
+        minint64 = -maxint64 - 1
+        maxuint64 = umax('Q')
+
+        types_ = dict(int=(TYPE_INT, minint, maxint),
+                      uint=(TYPE_UINT, 0, maxuint),
+                      long=(TYPE_LONG, minlong, maxlong),
+                      ulong=(TYPE_ULONG, 0, maxulong),
+                      int64=(TYPE_INT64, minint64, maxint64),
+                      uint64=(TYPE_UINT64, 0, maxuint64))
+
+        def build_gproperties(types_):
+            d = {}
+            for key, (gtype, min, max) in types_.items():
+                d[key] = (gtype, 'blurb', 'desc', min, max, 0,
+                          ParamFlags.READABLE | ParamFlags.WRITABLE)
+            return d
+
+        class RangeCheck(GObject.GObject):
+            __gproperties__ = build_gproperties(types_)
+
+            def __init__(self):
+                self.values = {}
+                GObject.GObject.__init__(self)
+
+            def do_set_property(self, pspec, value):
+                self.values[pspec.name] = value
+
+            def do_get_property(self, pspec):
+                return self.values.get(pspec.name, pspec.default_value)
+
+        self.assertEqual(RangeCheck.props.int.minimum, minint)
+        self.assertEqual(RangeCheck.props.int.maximum, maxint)
+        self.assertEqual(RangeCheck.props.uint.minimum, 0)
+        self.assertEqual(RangeCheck.props.uint.maximum, maxuint)
+        self.assertEqual(RangeCheck.props.long.minimum, minlong)
+        self.assertEqual(RangeCheck.props.long.maximum, maxlong)
+        self.assertEqual(RangeCheck.props.ulong.minimum, 0)
+        self.assertEqual(RangeCheck.props.ulong.maximum, maxulong)
+        self.assertEqual(RangeCheck.props.int64.minimum, minint64)
+        self.assertEqual(RangeCheck.props.int64.maximum, maxint64)
+        self.assertEqual(RangeCheck.props.uint64.minimum, 0)
+        self.assertEqual(RangeCheck.props.uint64.maximum, maxuint64)
+
+        obj = RangeCheck()
+        for key, (gtype, min, max) in types_.items():
+            self.assertEqual(obj.get_property(key),
+                             getattr(RangeCheck.props, key).default_value)
+
+            obj.set_property(key, min)
+            self.assertEqual(obj.get_property(key), min)
+
+            obj.set_property(key, max)
+            self.assertEqual(obj.get_property(key), max)
+
+    def test_multi(self):
+        obj = PropertyObject()
+        obj.set_properties(normal="foo",
+                           uint64=7)
+        normal, uint64 = obj.get_properties("normal", "uint64")
+        self.assertEqual(normal, "foo")
+        self.assertEqual(uint64, 7)
+
+
+class TestProperty(unittest.TestCase):
+    def test_simple(self):
+        class C(GObject.GObject):
+            str = GObject.Property(type=str)
+            int = GObject.Property(type=int)
+            float = GObject.Property(type=float)
+            long = GObject.Property(type=long_)
+
+        self.assertTrue(hasattr(C.props, 'str'))
+        self.assertTrue(hasattr(C.props, 'int'))
+        self.assertTrue(hasattr(C.props, 'float'))
+        self.assertTrue(hasattr(C.props, 'long'))
+
+        o = C()
+        self.assertEqual(o.str, '')
+        o.str = 'str'
+        self.assertEqual(o.str, 'str')
+
+        self.assertEqual(o.int, 0)
+        o.int = 1138
+        self.assertEqual(o.int, 1138)
+
+        self.assertEqual(o.float, 0.0)
+        o.float = 3.14
+        self.assertEqual(o.float, 3.14)
+
+        self.assertEqual(o.long, long_(0))
+        o.long = long_(100)
+        self.assertEqual(o.long, long_(100))
+
+    def test_custom_getter(self):
+        class C(GObject.GObject):
+            def get_prop(self):
+                return 'value'
+            prop = GObject.Property(getter=get_prop)
+
+        o = C()
+        self.assertEqual(o.prop, 'value')
+        self.assertRaises(TypeError, setattr, o, 'prop', 'xxx')
+
+    def test_getter_exception(self):
+        class C(GObject.Object):
+            @GObject.Property(type=int)
+            def prop(self):
+                raise ValueError('something bad happend')
+
+        o = C()
+
+        with self.assertRaisesRegex(ValueError, 'something bad happend'):
+            o.prop
+
+        with self.assertRaisesRegex(ValueError, 'something bad happend'):
+            o.get_property('prop')
+
+        with self.assertRaisesRegex(ValueError, 'something bad happend'):
+            o.props.prop
+
+    def test_custom_setter(self):
+        class C(GObject.GObject):
+            def set_prop(self, value):
+                self._value = value
+            prop = GObject.Property(setter=set_prop)
+
+            def __init__(self):
+                self._value = None
+                GObject.GObject.__init__(self)
+
+        o = C()
+        self.assertEqual(o._value, None)
+        o.prop = 'bar'
+        self.assertEqual(o._value, 'bar')
+        self.assertRaises(TypeError, getattr, o, 'prop')
+
+    def test_decorator_default(self):
+        class C(GObject.GObject):
+            _value = 'value'
+
+            @GObject.Property
+            def value(self):
+                return self._value
+
+            @value.setter
+            def value_setter(self, value):
+                self._value = value
+
+        o = C()
+        self.assertEqual(o.value, 'value')
+        o.value = 'blah'
+        self.assertEqual(o.value, 'blah')
+        self.assertEqual(o.props.value, 'blah')
+
+    def test_decorator_private_setter(self):
+        class C(GObject.GObject):
+            _value = 'value'
+
+            @GObject.Property
+            def value(self):
+                return self._value
+
+            @value.setter
+            def _set_value(self, value):
+                self._value = value
+
+        o = C()
+        self.assertEqual(o.value, 'value')
+        o.value = 'blah'
+        self.assertEqual(o.value, 'blah')
+        self.assertEqual(o.props.value, 'blah')
+
+    def test_decorator_with_call(self):
+        class C(GObject.GObject):
+            _value = 1
+
+            @GObject.Property(type=int, default=1, minimum=1, maximum=10)
+            def typedValue(self):
+                return self._value
+
+            @typedValue.setter
+            def typedValue_setter(self, value):
+                self._value = value
+
+        o = C()
+        self.assertEqual(o.typedValue, 1)
+        o.typedValue = 5
+        self.assertEqual(o.typedValue, 5)
+        self.assertEqual(o.props.typedValue, 5)
+
+    def test_errors(self):
+        self.assertRaises(TypeError, GObject.Property, type='str')
+        self.assertRaises(TypeError, GObject.Property, nick=False)
+        self.assertRaises(TypeError, GObject.Property, blurb=False)
+        # this never fail while bool is a subclass of int
+        # >>> bool.__bases__
+        # (<type 'int'>,)
+        # self.assertRaises(TypeError, GObject.Property, type=bool, default=0)
+        self.assertRaises(TypeError, GObject.Property, type=bool, default='ciao mamma')
+        self.assertRaises(TypeError, GObject.Property, type=bool)
+        self.assertRaises(TypeError, GObject.Property, type=object, default=0)
+        self.assertRaises(TypeError, GObject.Property, type=complex)
+
+    def test_defaults(self):
+        GObject.Property(type=bool, default=True)
+        GObject.Property(type=bool, default=False)
+
+    def test_name_with_underscore(self):
+        class C(GObject.GObject):
+            prop_name = GObject.Property(type=int)
+        o = C()
+        o.prop_name = 10
+        self.assertEqual(o.prop_name, 10)
+
+    def test_range(self):
+        types_ = [
+            (TYPE_INT, MININT, MAXINT),
+            (TYPE_UINT, 0, MAXUINT),
+            (TYPE_LONG, MINLONG, MAXLONG),
+            (TYPE_ULONG, 0, MAXULONG),
+            (TYPE_INT64, MININT64, MAXINT64),
+            (TYPE_UINT64, 0, MAXUINT64),
+            ]
+
+        for gtype, min, max in types_:
+            # Normal, everything is alright
+            prop = GObject.Property(type=gtype, minimum=min, maximum=max)
+            subtype = type('', (GObject.GObject,), dict(prop=prop))
+            self.assertEqual(subtype.props.prop.minimum, min)
+            self.assertEqual(subtype.props.prop.maximum, max)
+
+            # Lower than minimum
+            self.assertRaises(TypeError,
+                              GObject.Property, type=gtype, minimum=min - 1,
+                              maximum=max)
+
+            # Higher than maximum
+            self.assertRaises(TypeError,
+                              GObject.Property, type=gtype, minimum=min,
+                              maximum=max + 1)
+
+    def test_min_max(self):
+        class C(GObject.GObject):
+            prop_int = GObject.Property(type=int, minimum=1, maximum=100, default=1)
+            prop_float = GObject.Property(type=float, minimum=0.1, maximum=10.5, default=1.1)
+
+            def __init__(self):
+                GObject.GObject.__init__(self)
+
+        # we test known-bad values here which cause Gtk-WARNING logs.
+        # Explicitly allow these for this test.
+        with capture_glib_warnings(allow_warnings=True):
+            o = C()
+            self.assertEqual(o.prop_int, 1)
+
+            o.prop_int = 5
+            self.assertEqual(o.prop_int, 5)
+
+            o.prop_int = 0
+            self.assertEqual(o.prop_int, 5)
+
+            o.prop_int = 101
+            self.assertEqual(o.prop_int, 5)
+
+            self.assertEqual(o.prop_float, 1.1)
+
+            o.prop_float = 7.75
+            self.assertEqual(o.prop_float, 7.75)
+
+            o.prop_float = 0.09
+            self.assertEqual(o.prop_float, 7.75)
+
+            o.prop_float = 10.51
+            self.assertEqual(o.prop_float, 7.75)
+
+    def test_multiple_instances(self):
+        class C(GObject.GObject):
+            prop = GObject.Property(type=str, default='default')
+
+        o1 = C()
+        o2 = C()
+        self.assertEqual(o1.prop, 'default')
+        self.assertEqual(o2.prop, 'default')
+        o1.prop = 'value'
+        self.assertEqual(o1.prop, 'value')
+        self.assertEqual(o2.prop, 'default')
+
+    def test_object_property(self):
+        class PropertyObject(GObject.GObject):
+            obj = GObject.Property(type=GObject.GObject)
+
+        pobj1 = PropertyObject()
+        obj1_hash = hash(pobj1)
+        pobj2 = PropertyObject()
+
+        pobj2.obj = pobj1
+        del pobj1
+        pobj1 = pobj2.obj
+        self.assertEqual(hash(pobj1), obj1_hash)
+
+    def test_object_subclass_property(self):
+        class ObjectSubclass(GObject.GObject):
+            __gtype_name__ = 'ObjectSubclass'
+
+        class PropertyObjectSubclass(GObject.GObject):
+            obj = GObject.Property(type=ObjectSubclass)
+
+        PropertyObjectSubclass(obj=ObjectSubclass())
+
+    def test_property_subclass(self):
+        # test for #470718
+        class A(GObject.GObject):
+            prop1 = GObject.Property(type=int)
+
+        class B(A):
+            prop2 = GObject.Property(type=int)
+
+        b = B()
+        b.prop2 = 10
+        self.assertEqual(b.prop2, 10)
+        b.prop1 = 20
+        self.assertEqual(b.prop1, 20)
+
+    def test_property_subclass_c(self):
+        class A(Regress.TestSubObj):
+            prop1 = GObject.Property(type=int)
+
+        a = A()
+        a.prop1 = 10
+        self.assertEqual(a.prop1, 10)
+
+        # also has parent properties
+        a.props.int = 20
+        self.assertEqual(a.props.int, 20)
+
+        # Some of which are unusable without introspection
+        a.props.list = ("str1", "str2")
+        self.assertEqual(a.props.list, ["str1", "str2"])
+
+        a.set_property("list", ("str3", "str4"))
+        self.assertEqual(a.props.list, ["str3", "str4"])
+
+    def test_property_subclass_custom_setter(self):
+        # test for #523352
+        class A(GObject.GObject):
+            def get_first(self):
+                return 'first'
+            first = GObject.Property(type=str, getter=get_first)
+
+        class B(A):
+            def get_second(self):
+                return 'second'
+            second = GObject.Property(type=str, getter=get_second)
+
+        a = A()
+        self.assertEqual(a.first, 'first')
+        self.assertRaises(TypeError, setattr, a, 'first', 'foo')
+
+        b = B()
+        self.assertEqual(b.first, 'first')
+        self.assertRaises(TypeError, setattr, b, 'first', 'foo')
+        self.assertEqual(b.second, 'second')
+        self.assertRaises(TypeError, setattr, b, 'second', 'foo')
+
+    def test_property_subclass_custom_setter_error(self):
+        try:
+            class A(GObject.GObject):
+                def get_first(self):
+                    return 'first'
+                first = GObject.Property(type=str, getter=get_first)
+
+                def do_get_property(self, pspec):
+                    pass
+        except TypeError:
+            pass
+        else:
+            raise AssertionError
+
+    # Bug 587637.
+
+    def test_float_min(self):
+        GObject.Property(type=float, minimum=-1)
+        GObject.Property(type=GObject.TYPE_FLOAT, minimum=-1)
+        GObject.Property(type=GObject.TYPE_DOUBLE, minimum=-1)
+
+    # Bug 644039
+    @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount")
+    def test_reference_count(self):
+        # We can check directly if an object gets finalized, so we will
+        # observe it indirectly through the refcount of a member object.
+
+        # We create our dummy object and store its current refcount
+        o = object()
+        rc = sys.getrefcount(o)
+
+        # We add our object as a member to our newly created object we
+        # want to observe. Its refcount is increased by one.
+        t = PropertyObject(normal="test")
+        t.o = o
+        self.assertEqual(sys.getrefcount(o), rc + 1)
+
+        # Now we want to ensure we do not leak any references to our
+        # object with properties. If no ref is leaked, then when deleting
+        # the local reference to this object, its reference count shoud
+        # drop to zero, and our dummy object should loose one reference.
+        del t
+        self.assertEqual(sys.getrefcount(o), rc)
+
+    def test_doc_strings(self):
+        class C(GObject.GObject):
+            foo_blurbed = GObject.Property(type=int, blurb='foo_blurbed doc string')
+
+            @GObject.Property
+            def foo_getter(self):
+                """foo_getter doc string"""
+                return 0
+
+        self.assertEqual(C.foo_blurbed.blurb, 'foo_blurbed doc string')
+        self.assertEqual(C.foo_blurbed.__doc__, 'foo_blurbed doc string')
+
+        self.assertEqual(C.foo_getter.blurb, 'foo_getter doc string')
+        self.assertEqual(C.foo_getter.__doc__, 'foo_getter doc string')
+
+    def test_python_to_glib_type_mapping(self):
+        tester = GObject.Property()
+        self.assertEqual(tester._type_from_python(int), GObject.TYPE_INT)
+        if PY2:
+            self.assertEqual(tester._type_from_python(long_), GObject.TYPE_LONG)
+        self.assertEqual(tester._type_from_python(bool), GObject.TYPE_BOOLEAN)
+        self.assertEqual(tester._type_from_python(float), GObject.TYPE_DOUBLE)
+        self.assertEqual(tester._type_from_python(str), GObject.TYPE_STRING)
+        self.assertEqual(tester._type_from_python(object), GObject.TYPE_PYOBJECT)
+
+        self.assertEqual(tester._type_from_python(GObject.GObject), GObject.GObject.__gtype__)
+        self.assertEqual(tester._type_from_python(GObject.GEnum), GObject.GEnum.__gtype__)
+        self.assertEqual(tester._type_from_python(GObject.GFlags), GObject.GFlags.__gtype__)
+        self.assertEqual(tester._type_from_python(GObject.GBoxed), GObject.GBoxed.__gtype__)
+        self.assertEqual(tester._type_from_python(GObject.GInterface), GObject.GInterface.__gtype__)
+
+        for type_ in [TYPE_NONE, TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR,
+                      TYPE_INT, TYPE_UINT, TYPE_BOOLEAN, TYPE_LONG,
+                      TYPE_ULONG, TYPE_INT64, TYPE_UINT64,
+                      TYPE_FLOAT, TYPE_DOUBLE, TYPE_POINTER,
+                      TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, TYPE_STRING,
+                      TYPE_PYOBJECT, TYPE_GTYPE, TYPE_STRV]:
+            self.assertEqual(tester._type_from_python(type_), type_)
+
+        self.assertRaises(TypeError, tester._type_from_python, types.CodeType)
+
+
+class TestInstallProperties(unittest.TestCase):
+    # These tests only test how signalhelper.install_signals works
+    # with the __gsignals__ dict and therefore does not need to use
+    # GObject as a base class because that would automatically call
+    # install_signals within the meta-class.
+    class Base(object):
+        __gproperties__ = {'test': (0, '', '', 0, 0, 0, 0)}
+
+    class Sub1(Base):
+        pass
+
+    class Sub2(Base):
+        @GObject.Property(type=int)
+        def sub2test(self):
+            return 123
+
+    class ClassWithPropertyAndGetterVFunc(object):
+        @GObject.Property(type=int)
+        def sub2test(self):
+            return 123
+
+        def do_get_property(self, name):
+            return 321
+
+    class ClassWithPropertyRedefined(object):
+        __gproperties__ = {'test': (0, '', '', 0, 0, 0, 0)}
+        test = GObject.Property(type=int)
+
+    def setUp(self):
+        self.assertEqual(len(self.Base.__gproperties__), 1)
+        propertyhelper.install_properties(self.Base)
+        self.assertEqual(len(self.Base.__gproperties__), 1)
+
+    def test_subclass_without_properties_is_not_modified(self):
+        self.assertFalse('__gproperties__' in self.Sub1.__dict__)
+        propertyhelper.install_properties(self.Sub1)
+        self.assertFalse('__gproperties__' in self.Sub1.__dict__)
+
+    def test_subclass_with_decorator_gets_gproperties_dict(self):
+        # Sub2 has Property instances but will not have a __gproperties__
+        # until install_properties is called
+        self.assertFalse('__gproperties__' in self.Sub2.__dict__)
+        self.assertFalse('do_get_property' in self.Sub2.__dict__)
+        self.assertFalse('do_set_property' in self.Sub2.__dict__)
+
+        propertyhelper.install_properties(self.Sub2)
+        self.assertTrue('__gproperties__' in self.Sub2.__dict__)
+        self.assertEqual(len(self.Base.__gproperties__), 1)
+        self.assertEqual(len(self.Sub2.__gproperties__), 1)
+        self.assertTrue('sub2test' in self.Sub2.__gproperties__)
+
+        # get/set vfuncs should have been added
+        self.assertTrue('do_get_property' in self.Sub2.__dict__)
+        self.assertTrue('do_set_property' in self.Sub2.__dict__)
+
+    def test_object_with_property_and_do_get_property_vfunc_raises(self):
+        self.assertRaises(TypeError, propertyhelper.install_properties,
+                          self.ClassWithPropertyAndGetterVFunc)
+
+    def test_same_name_property_definitions_raises(self):
+        self.assertRaises(ValueError, propertyhelper.install_properties,
+                          self.ClassWithPropertyRedefined)
+
+
+class CPropertiesTestBase(object):
+    # Tests for properties implemented in C not Python.
+
+    def setUp(self):
+        self.obj = GIMarshallingTests.PropertiesObject()
+
+    def get_prop(self, obj, name):
+        raise NotImplementedError
+
+    def set_prop(self, obj, name, value):
+        raise NotImplementedError
+
+    # https://bugzilla.gnome.org/show_bug.cgi?id=780652
+    @unittest.skipUnless(
+        "some_flags" in dir(GIMarshallingTests.PropertiesObject.props),
+        "tool old gi")
+    def test_flags(self):
+        self.assertEqual(
+            self.get_prop(self.obj, 'some-flags'),
+            GIMarshallingTests.Flags.VALUE1)
+        self.set_prop(self.obj, 'some-flags', GIMarshallingTests.Flags.VALUE2)
+        self.assertEqual(self.get_prop(self.obj, 'some-flags'),
+                         GIMarshallingTests.Flags.VALUE2)
+
+        obj = GIMarshallingTests.PropertiesObject(
+            some_flags=GIMarshallingTests.Flags.VALUE3)
+        self.assertEqual(self.get_prop(obj, 'some-flags'),
+                         GIMarshallingTests.Flags.VALUE3)
+
+    # https://bugzilla.gnome.org/show_bug.cgi?id=780652
+    @unittest.skipUnless(
+        "some_enum" in dir(GIMarshallingTests.PropertiesObject.props),
+        "tool old gi")
+    def test_enum(self):
+        self.assertEqual(
+            self.get_prop(self.obj, 'some-enum'),
+            GIMarshallingTests.GEnum.VALUE1)
+        self.set_prop(self.obj, 'some-enum', GIMarshallingTests.GEnum.VALUE2)
+        self.assertEqual(self.get_prop(self.obj, 'some-enum'),
+                         GIMarshallingTests.GEnum.VALUE2)
+
+        obj = GIMarshallingTests.PropertiesObject(
+            some_enum=GIMarshallingTests.GEnum.VALUE3)
+        self.assertEqual(self.get_prop(obj, 'some-enum'),
+                         GIMarshallingTests.GEnum.VALUE3)
+
+    def test_boolean(self):
+        self.assertEqual(self.get_prop(self.obj, 'some-boolean'), False)
+        self.set_prop(self.obj, 'some-boolean', True)
+        self.assertEqual(self.get_prop(self.obj, 'some-boolean'), True)
+
+        obj = GIMarshallingTests.PropertiesObject(some_boolean=True)
+        self.assertEqual(self.get_prop(obj, 'some-boolean'), True)
+
+    def test_char(self):
+        self.assertEqual(self.get_prop(self.obj, 'some-char'), 0)
+        self.set_prop(self.obj, 'some-char', GLib.MAXINT8)
+        self.assertEqual(self.get_prop(self.obj, 'some-char'), GLib.MAXINT8)
+
+        obj = GIMarshallingTests.PropertiesObject(some_char=-42)
+        self.assertEqual(self.get_prop(obj, 'some-char'), -42)
+
+        with pytest.raises(OverflowError):
+            self.set_prop(obj, 'some-char', GLib.MAXINT8 + 1)
+        with pytest.raises(OverflowError):
+            self.set_prop(obj, 'some-char', GLib.MININT8 - 1)
+
+        self.set_prop(obj, 'some-char', b"\x44")
+        assert self.get_prop(obj, 'some-char') == 0x44
+
+        self.set_prop(obj, 'some-char', b"\xff")
+        assert self.get_prop(obj, 'some-char') == -1
+
+        obj = GIMarshallingTests.PropertiesObject(some_char=u"\x7f")
+        assert self.get_prop(obj, 'some-char') == 0x7f
+
+        with pytest.raises(TypeError):
+            GIMarshallingTests.PropertiesObject(some_char=u"€")
+
+        with pytest.raises(TypeError):
+            GIMarshallingTests.PropertiesObject(some_char=u"\ud83d")
+
+    def test_uchar(self):
+        self.assertEqual(self.get_prop(self.obj, 'some-uchar'), 0)
+        self.set_prop(self.obj, 'some-uchar', GLib.MAXUINT8)
+        self.assertEqual(self.get_prop(self.obj, 'some-uchar'), GLib.MAXUINT8)
+
+        obj = GIMarshallingTests.PropertiesObject(some_uchar=42)
+        self.assertEqual(self.get_prop(obj, 'some-uchar'), 42)
+
+        with pytest.raises(OverflowError):
+            self.set_prop(obj, 'some-uchar', GLib.MAXUINT8 + 1)
+        with pytest.raises(OverflowError):
+            self.set_prop(obj, 'some-uchar', -1)
+
+        self.set_prop(obj, 'some-uchar', b"\x57")
+        assert self.get_prop(obj, 'some-uchar') == 0x57
+
+        self.set_prop(obj, 'some-uchar', b"\xff")
+        assert self.get_prop(obj, 'some-uchar') == 255
+
+        obj = GIMarshallingTests.PropertiesObject(some_uchar=u"\x7f")
+        assert self.get_prop(obj, 'some-uchar') == 127
+
+        with pytest.raises(TypeError):
+            GIMarshallingTests.PropertiesObject(some_uchar=u"\x80")
+
+        with pytest.raises(TypeError):
+            GIMarshallingTests.PropertiesObject(some_uchar=u"\ud83d")
+
+    def test_int(self):
+        self.assertEqual(self.get_prop(self.obj, 'some_int'), 0)
+        self.set_prop(self.obj, 'some-int', GLib.MAXINT)
+        self.assertEqual(self.get_prop(self.obj, 'some_int'), GLib.MAXINT)
+
+        obj = GIMarshallingTests.PropertiesObject(some_int=-42)
+        self.assertEqual(self.get_prop(obj, 'some-int'), -42)
+
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-int', 'foo')
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-int', None)
+
+        self.assertEqual(self.get_prop(obj, 'some-int'), -42)
+
+    def test_uint(self):
+        self.assertEqual(self.get_prop(self.obj, 'some_uint'), 0)
+        self.set_prop(self.obj, 'some-uint', GLib.MAXUINT)
+        self.assertEqual(self.get_prop(self.obj, 'some_uint'), GLib.MAXUINT)
+
+        obj = GIMarshallingTests.PropertiesObject(some_uint=42)
+        self.assertEqual(self.get_prop(obj, 'some-uint'), 42)
+
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-uint', 'foo')
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-uint', None)
+
+        self.assertEqual(self.get_prop(obj, 'some-uint'), 42)
+
+    def test_long(self):
+        self.assertEqual(self.get_prop(self.obj, 'some_long'), 0)
+        self.set_prop(self.obj, 'some-long', GLib.MAXLONG)
+        self.assertEqual(self.get_prop(self.obj, 'some_long'), GLib.MAXLONG)
+
+        obj = GIMarshallingTests.PropertiesObject(some_long=-42)
+        self.assertEqual(self.get_prop(obj, 'some-long'), -42)
+
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-long', 'foo')
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-long', None)
+
+        self.assertEqual(self.get_prop(obj, 'some-long'), -42)
+
+    def test_ulong(self):
+        self.assertEqual(self.get_prop(self.obj, 'some_ulong'), 0)
+        self.set_prop(self.obj, 'some-ulong', GLib.MAXULONG)
+        self.assertEqual(self.get_prop(self.obj, 'some_ulong'), GLib.MAXULONG)
+
+        obj = GIMarshallingTests.PropertiesObject(some_ulong=42)
+        self.assertEqual(self.get_prop(obj, 'some-ulong'), 42)
+
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-ulong', 'foo')
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-ulong', None)
+
+        self.assertEqual(self.get_prop(obj, 'some-ulong'), 42)
+
+    def test_int64(self):
+        self.assertEqual(self.get_prop(self.obj, 'some-int64'), 0)
+        self.set_prop(self.obj, 'some-int64', GLib.MAXINT64)
+        self.assertEqual(self.get_prop(self.obj, 'some-int64'), GLib.MAXINT64)
+
+        obj = GIMarshallingTests.PropertiesObject(some_int64=-4200000000000000)
+        self.assertEqual(self.get_prop(obj, 'some-int64'), -4200000000000000)
+
+    def test_uint64(self):
+        self.assertEqual(self.get_prop(self.obj, 'some-uint64'), 0)
+        self.set_prop(self.obj, 'some-uint64', GLib.MAXUINT64)
+        self.assertEqual(self.get_prop(self.obj, 'some-uint64'), GLib.MAXUINT64)
+
+        obj = GIMarshallingTests.PropertiesObject(some_uint64=4200000000000000)
+        self.assertEqual(self.get_prop(obj, 'some-uint64'), 4200000000000000)
+
+    def test_float(self):
+        self.assertEqual(self.get_prop(self.obj, 'some-float'), 0)
+        self.set_prop(self.obj, 'some-float', GLib.MAXFLOAT)
+        self.assertEqual(self.get_prop(self.obj, 'some-float'), GLib.MAXFLOAT)
+
+        obj = GIMarshallingTests.PropertiesObject(some_float=42.42)
+        self.assertAlmostEqual(self.get_prop(obj, 'some-float'), 42.42, places=4)
+
+        obj = GIMarshallingTests.PropertiesObject(some_float=42)
+        self.assertAlmostEqual(self.get_prop(obj, 'some-float'), 42.0, places=4)
+
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-float', 'foo')
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-float', None)
+
+        self.assertAlmostEqual(self.get_prop(obj, 'some-float'), 42.0, places=4)
+
+    def test_double(self):
+        self.assertEqual(self.get_prop(self.obj, 'some-double'), 0)
+        self.set_prop(self.obj, 'some-double', GLib.MAXDOUBLE)
+        self.assertEqual(self.get_prop(self.obj, 'some-double'), GLib.MAXDOUBLE)
+
+        obj = GIMarshallingTests.PropertiesObject(some_double=42.42)
+        self.assertAlmostEqual(self.get_prop(obj, 'some-double'), 42.42)
+
+        obj = GIMarshallingTests.PropertiesObject(some_double=42)
+        self.assertAlmostEqual(self.get_prop(obj, 'some-double'), 42.0)
+
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-double', 'foo')
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-double', None)
+
+        self.assertAlmostEqual(self.get_prop(obj, 'some-double'), 42.0)
+
+    def test_strv(self):
+        self.assertEqual(self.get_prop(self.obj, 'some-strv'), [])
+        self.set_prop(self.obj, 'some-strv', ['hello', 'world'])
+        self.assertEqual(self.get_prop(self.obj, 'some-strv'), ['hello', 'world'])
+
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-strv', 1)
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-strv', 'foo')
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-strv', [1, 2])
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-strv', ['foo', 1])
+
+        self.assertEqual(self.get_prop(self.obj, 'some-strv'), ['hello', 'world'])
+
+        obj = GIMarshallingTests.PropertiesObject(some_strv=['hello', 'world'])
+        self.assertEqual(self.get_prop(obj, 'some-strv'), ['hello', 'world'])
+
+        # unicode on py2
+        obj = GIMarshallingTests.PropertiesObject(some_strv=[u'foo'])
+        self.assertEqual(self.get_prop(obj, 'some-strv'), [u'foo'])
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-strv',
+                          [u'foo', 1])
+
+    def test_boxed_struct(self):
+        self.assertEqual(self.get_prop(self.obj, 'some-boxed-struct'), None)
+
+        class GStrv(list):
+            __gtype__ = GObject.TYPE_STRV
+
+        struct1 = GIMarshallingTests.BoxedStruct()
+        struct1.long_ = 1
+
+        self.set_prop(self.obj, 'some-boxed-struct', struct1)
+        self.assertEqual(self.get_prop(self.obj, 'some-boxed-struct').long_, 1)
+        self.assertEqual(self.obj.some_boxed_struct.long_, 1)
+
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-boxed-struct', 1)
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-boxed-struct', 'foo')
+
+        obj = GIMarshallingTests.PropertiesObject(some_boxed_struct=struct1)
+        self.assertEqual(self.get_prop(obj, 'some-boxed-struct').long_, 1)
+
+    def test_boxed_glist(self):
+        self.assertEqual(self.get_prop(self.obj, 'some-boxed-glist'), [])
+
+        list_ = [GLib.MININT, 42, GLib.MAXINT]
+        self.set_prop(self.obj, 'some-boxed-glist', list_)
+        self.assertEqual(self.get_prop(self.obj, 'some-boxed-glist'), list_)
+        self.set_prop(self.obj, 'some-boxed-glist', [])
+        self.assertEqual(self.get_prop(self.obj, 'some-boxed-glist'), [])
+
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-boxed-glist', 1)
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-boxed-glist', 'foo')
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-boxed-glist', ['a'])
+
+    def test_annotated_glist(self):
+        obj = Regress.TestObj()
+        self.assertEqual(self.get_prop(obj, 'list'), [])
+
+        self.set_prop(obj, 'list', ['1', '2', '3'])
+        self.assertTrue(isinstance(self.get_prop(obj, 'list'), list))
+        self.assertEqual(self.get_prop(obj, 'list'), ['1', '2', '3'])
+
+    @unittest.expectedFailure
+    def test_boxed_glist_ctor(self):
+        list_ = [GLib.MININT, 42, GLib.MAXINT]
+        obj = GIMarshallingTests.PropertiesObject(some_boxed_glist=list_)
+        self.assertEqual(self.get_prop(obj, 'some-boxed-glist'), list_)
+
+    def test_variant(self):
+        self.assertEqual(self.get_prop(self.obj, 'some-variant'), None)
+
+        self.set_prop(self.obj, 'some-variant', GLib.Variant('o', '/myobj'))
+        self.assertEqual(self.get_prop(self.obj, 'some-variant').get_type_string(), 'o')
+        self.assertEqual(self.get_prop(self.obj, 'some-variant').print_(False), "'/myobj'")
+
+        self.set_prop(self.obj, 'some-variant', None)
+        self.assertEqual(self.get_prop(self.obj, 'some-variant'), None)
+
+        obj = GIMarshallingTests.PropertiesObject(some_variant=GLib.Variant('b', True))
+        self.assertEqual(self.get_prop(obj, 'some-variant').get_type_string(), 'b')
+        self.assertEqual(self.get_prop(obj, 'some-variant').get_boolean(), True)
+
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-variant', 'foo')
+        self.assertRaises(TypeError, self.set_prop, self.obj, 'some-variant', 23)
+
+        self.assertEqual(self.get_prop(obj, 'some-variant').get_type_string(), 'b')
+        self.assertEqual(self.get_prop(obj, 'some-variant').get_boolean(), True)
+
+    def test_setting_several_properties(self):
+        obj = GIMarshallingTests.PropertiesObject()
+        obj.set_properties(some_uchar=54, some_int=42)
+        self.assertEqual(42, self.get_prop(obj, 'some-int'))
+        self.assertEqual(54, self.get_prop(obj, 'some-uchar'))
+
+    def test_gtype(self):
+        obj = Regress.TestObj()
+        self.assertEqual(self.get_prop(obj, 'gtype'), GObject.TYPE_INVALID)
+        self.set_prop(obj, 'gtype', int)
+        self.assertEqual(self.get_prop(obj, 'gtype'), GObject.TYPE_INT)
+
+        obj = Regress.TestObj(gtype=int)
+        self.assertEqual(self.get_prop(obj, 'gtype'), GObject.TYPE_INT)
+        self.set_prop(obj, 'gtype', str)
+        self.assertEqual(self.get_prop(obj, 'gtype'), GObject.TYPE_STRING)
+
+    def test_hash_table(self):
+        obj = Regress.TestObj()
+        self.assertEqual(self.get_prop(obj, 'hash-table'), None)
+
+        self.set_prop(obj, 'hash-table', {'mec': 56})
+        self.assertTrue(isinstance(self.get_prop(obj, 'hash-table'), dict))
+        self.assertEqual(list(self.get_prop(obj, 'hash-table').items())[0],
+                         ('mec', 56))
+
+    def test_parent_class(self):
+        class A(Regress.TestObj):
+            prop1 = GObject.Property(type=int)
+
+        a = A()
+        self.set_prop(a, 'int', 20)
+        self.assertEqual(self.get_prop(a, 'int'), 20)
+
+        # test parent property which needs introspection
+        self.set_prop(a, 'list', ("str1", "str2"))
+        self.assertEqual(self.get_prop(a, 'list'), ["str1", "str2"])
+
+    def test_held_object_ref_count_getter(self):
+        holder = GIMarshallingTests.PropertiesObject()
+        held = GObject.Object()
+
+        self.assertEqual(holder.__grefcount__, 1)
+        self.assertEqual(held.__grefcount__, 1)
+
+        self.set_prop(holder, 'some-object', held)
+        self.assertEqual(holder.__grefcount__, 1)
+
+        initial_ref_count = held.__grefcount__
+        self.get_prop(holder, 'some-object')
+        gc.collect()
+        self.assertEqual(held.__grefcount__, initial_ref_count)
+
+    def test_held_object_ref_count_setter(self):
+        holder = GIMarshallingTests.PropertiesObject()
+        held = GObject.Object()
+
+        self.assertEqual(holder.__grefcount__, 1)
+        self.assertEqual(held.__grefcount__, 1)
+
+        # Setting property should only increase ref count by 1
+        self.set_prop(holder, 'some-object', held)
+        self.assertEqual(holder.__grefcount__, 1)
+        self.assertEqual(held.__grefcount__, 2)
+
+        # Clearing should pull it back down
+        self.set_prop(holder, 'some-object', None)
+        self.assertEqual(held.__grefcount__, 1)
+
+    def test_set_object_property_to_invalid_type(self):
+        obj = GIMarshallingTests.PropertiesObject()
+        self.assertRaises(TypeError, self.set_prop, obj, 'some-object', 'not_an_object')
+
+
+class TestCPropsAccessor(CPropertiesTestBase, unittest.TestCase):
+    # C property tests using the "props" accessor.
+    def get_prop(self, obj, name):
+        return getattr(obj.props, name.replace('-', '_'))
+
+    def set_prop(self, obj, name, value):
+        setattr(obj.props, name.replace('-', '_'), value)
+
+    def test_props_accessor_dir(self):
+        # Test class
+        props = dir(GIMarshallingTests.PropertiesObject.props)
+        self.assertTrue('some_float' in props)
+        self.assertTrue('some_double' in props)
+        self.assertTrue('some_variant' in props)
+
+        # Test instance
+        obj = GIMarshallingTests.PropertiesObject()
+        props = dir(obj.props)
+        self.assertTrue('some_float' in props)
+        self.assertTrue('some_double' in props)
+        self.assertTrue('some_variant' in props)
+
+    def test_param_spec_dir(self):
+        attrs = dir(GIMarshallingTests.PropertiesObject.props.some_float)
+        self.assertTrue('name' in attrs)
+        self.assertTrue('nick' in attrs)
+        self.assertTrue('blurb' in attrs)
+        self.assertTrue('flags' in attrs)
+        self.assertTrue('default_value' in attrs)
+        self.assertTrue('minimum' in attrs)
+        self.assertTrue('maximum' in attrs)
+
+
+class TestCGetPropertyMethod(CPropertiesTestBase, unittest.TestCase):
+    # C property tests using the "props" accessor.
+    def get_prop(self, obj, name):
+        return obj.get_property(name)
+
+    def set_prop(self, obj, name, value):
+        obj.set_property(name, value)
diff --git a/tests/test_pycapi.py b/tests/test_pycapi.py
new file mode 100644 (file)
index 0000000..969b78e
--- /dev/null
@@ -0,0 +1,54 @@
+from __future__ import absolute_import
+
+import sys
+import unittest
+import ctypes
+from ctypes import c_void_p, py_object, c_char_p
+
+import gi
+from gi.repository import Gio
+
+
+def get_capi():
+
+    if not hasattr(ctypes, "pythonapi"):
+        return
+
+    class CAPI(ctypes.Structure):
+        _fields_ = [
+            ("", c_void_p),
+            ("", c_void_p),
+            ("", c_void_p),
+            ("newgobj", ctypes.PYFUNCTYPE(py_object, c_void_p)),
+        ]
+
+    api_obj = gi._gobject._PyGObject_API
+    if sys.version_info[0] == 2:
+        func_type = ctypes.PYFUNCTYPE(c_void_p, py_object)
+        PyCObject_AsVoidPtr = func_type(
+            ('PyCObject_AsVoidPtr', ctypes.pythonapi))
+        ptr = PyCObject_AsVoidPtr(api_obj)
+    else:
+        func_type = ctypes.PYFUNCTYPE(c_void_p, py_object, c_char_p)
+        PyCapsule_GetPointer = func_type(
+            ('PyCapsule_GetPointer', ctypes.pythonapi))
+        ptr = PyCapsule_GetPointer(api_obj, b"gobject._PyGObject_API")
+
+    ptr = ctypes.cast(ptr, ctypes.POINTER(CAPI))
+    return ptr.contents
+
+
+API = get_capi()
+
+
+@unittest.skipUnless(API, "no pythonapi support")
+class TestPythonCAPI(unittest.TestCase):
+
+    def test_newgobj(self):
+        w = Gio.FileInfo()
+        # XXX: ugh :/
+        ptr = int(repr(w).split()[-1].split(")")[0], 16)
+
+        capi = get_capi()
+        new_w = capi.newgobj(ptr)
+        assert w == new_w
diff --git a/tests/test_pygtkcompat.py b/tests/test_pygtkcompat.py
new file mode 100644 (file)
index 0000000..da220b5
--- /dev/null
@@ -0,0 +1,239 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+from __future__ import absolute_import
+
+import unittest
+import base64
+
+import pygtkcompat
+from pygtkcompat.pygtkcompat import _disable_all as disable_all
+
+from .helper import capture_gi_deprecation_warnings, capture_glib_warnings
+
+try:
+    from gi.repository import Gtk, Gdk
+except ImportError:
+    Gtk = None
+else:
+    if Gtk._version != "3.0":
+        Gtk = None
+
+
+class TestGlibCompat(unittest.TestCase):
+
+    def setUp(self):
+        pygtkcompat.enable()
+
+    def tearDown(self):
+        disable_all()
+
+    def test_import(self):
+        import glib
+        import gio
+        glib, gio
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestMultipleEnable(unittest.TestCase):
+
+    def tearDown(self):
+        disable_all()
+
+    def test_main(self):
+        pygtkcompat.enable()
+        pygtkcompat.enable()
+
+    def test_gtk(self):
+        pygtkcompat.enable_gtk("3.0")
+        pygtkcompat.enable_gtk("3.0")
+        import gtk
+
+        # https://bugzilla.gnome.org/show_bug.cgi?id=759009
+        w = gtk.Window()
+        w.realize()
+        self.assertEqual(len(w.window.get_origin()), 2)
+        w.destroy()
+
+    def test_gtk_no_4(self):
+        self.assertRaises(ValueError, pygtkcompat.enable_gtk, version='4.0')
+
+    def test_gtk_version_conflict(self):
+        pygtkcompat.enable_gtk("3.0")
+        self.assertRaises(ValueError, pygtkcompat.enable_gtk, version='2.0')
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestATKCompat(unittest.TestCase):
+
+    def setUp(self):
+        pygtkcompat.enable_gtk("3.0")
+
+    def tearDown(self):
+        disable_all()
+
+    def test_object(self):
+        import atk
+        self.assertTrue(hasattr(atk, 'Object'))
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestPangoCompat(unittest.TestCase):
+
+    def setUp(self):
+        pygtkcompat.enable_gtk("3.0")
+
+    def tearDown(self):
+        disable_all()
+
+    def test_layout(self):
+        import pango
+        self.assertTrue(hasattr(pango, 'Layout'))
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestPangoCairoCompat(unittest.TestCase):
+
+    def setUp(self):
+        pygtkcompat.enable_gtk("3.0")
+
+    def tearDown(self):
+        disable_all()
+
+    def test_error_underline_path(self):
+        import pangocairo
+        self.assertTrue(hasattr(pangocairo, 'error_underline_path'))
+
+
+@unittest.skipUnless(Gtk, 'Gtk not available')
+class TestGTKCompat(unittest.TestCase):
+
+    def setUp(self):
+        pygtkcompat.enable_gtk("3.0")
+
+    def tearDown(self):
+        disable_all()
+
+    def test_buttons(self):
+        import gtk.gdk
+        self.assertEqual(gtk.gdk._2BUTTON_PRESS, 5)
+        self.assertEqual(gtk.gdk.BUTTON_PRESS, 4)
+
+    def test_enums(self):
+        import gtk
+        self.assertEqual(gtk.WINDOW_TOPLEVEL, Gtk.WindowType.TOPLEVEL)
+        self.assertEqual(gtk.PACK_START, Gtk.PackType.START)
+
+    def test_flags(self):
+        import gtk
+        self.assertEqual(gtk.EXPAND, Gtk.AttachOptions.EXPAND)
+        self.assertEqual(gtk.gdk.SHIFT_MASK, Gdk.ModifierType.SHIFT_MASK)
+
+    def test_keysyms(self):
+        import gtk.keysyms
+        self.assertEqual(gtk.keysyms.Escape, Gdk.KEY_Escape)
+        self.assertTrue(gtk.keysyms._0, Gdk.KEY_0)
+
+    def test_style(self):
+        import gtk
+        widget = gtk.Button()
+        with capture_gi_deprecation_warnings():
+            widget.get_style_context().set_state(gtk.STATE_NORMAL)
+            self.assertTrue(isinstance(widget.style.base[gtk.STATE_NORMAL],
+                                       gtk.gdk.Color))
+
+    def test_alignment(self):
+        import gtk
+        # Creation of pygtk.Alignment causes hard warnings, ignore this in testing.
+        with capture_glib_warnings(allow_warnings=True):
+            a = gtk.Alignment()
+
+        self.assertEqual(a.props.xalign, 0.0)
+        self.assertEqual(a.props.yalign, 0.0)
+        self.assertEqual(a.props.xscale, 0.0)
+        self.assertEqual(a.props.yscale, 0.0)
+
+    def test_box(self):
+        import gtk
+        box = gtk.Box()
+        child = gtk.Button()
+
+        box.pack_start(child)
+        expand, fill, padding, pack_type = box.query_child_packing(child)
+        self.assertTrue(expand)
+        self.assertTrue(fill)
+        self.assertEqual(padding, 0)
+        self.assertEqual(pack_type, gtk.PACK_START)
+
+        child = gtk.Button()
+        box.pack_end(child)
+        expand, fill, padding, pack_type = box.query_child_packing(child)
+        self.assertTrue(expand)
+        self.assertTrue(fill)
+        self.assertEqual(padding, 0)
+        self.assertEqual(pack_type, gtk.PACK_END)
+
+    def test_combobox_entry(self):
+        import gtk
+        liststore = gtk.ListStore(int, str)
+        liststore.append((1, 'One'))
+        liststore.append((2, 'Two'))
+        liststore.append((3, 'Three'))
+        # might cause a Pango warning, do not break on this
+        with capture_glib_warnings(allow_warnings=True):
+            combo = gtk.ComboBoxEntry(model=liststore)
+        combo.set_text_column(1)
+        combo.set_active(0)
+        self.assertEqual(combo.get_text_column(), 1)
+        self.assertEqual(combo.get_child().get_text(), 'One')
+        combo = gtk.combo_box_entry_new()
+        combo.set_model(liststore)
+        combo.set_text_column(1)
+        combo.set_active(0)
+        self.assertEqual(combo.get_text_column(), 1)
+        self.assertEqual(combo.get_child().get_text(), 'One')
+        combo = gtk.combo_box_entry_new_with_model(liststore)
+        combo.set_text_column(1)
+        combo.set_active(0)
+        self.assertEqual(combo.get_text_column(), 1)
+        self.assertEqual(combo.get_child().get_text(), 'One')
+
+    def test_size_request(self):
+        import gtk
+        box = gtk.Box()
+        with capture_gi_deprecation_warnings():
+            self.assertEqual(box.size_request(), [0, 0])
+
+    def test_pixbuf(self):
+        import gtk.gdk
+        gtk.gdk.Pixbuf()
+
+    def test_pixbuf_loader(self):
+        import gtk.gdk
+        # load a 1x1 pixel PNG from memory
+        data = base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP4n8Dw'
+                                'HwAGIAJf85Z3XgAAAABJRU5ErkJggg==')
+        loader = gtk.gdk.PixbufLoader('png')
+        loader.write(data)
+        loader.close()
+
+        pixbuf = loader.get_pixbuf()
+        self.assertEqual(pixbuf.get_width(), 1)
+        self.assertEqual(pixbuf.get_height(), 1)
+
+    def test_pixbuf_formats(self):
+        import gtk.gdk
+        formats = gtk.gdk.pixbuf_get_formats()
+        self.assertEqual(type(formats[0]), dict)
+        self.assertTrue('name' in formats[0])
+        self.assertTrue('description' in formats[0])
+        self.assertTrue('mime_types' in formats[0])
+        self.assertEqual(type(formats[0]['extensions']), list)
+
+    def test_gdk_window(self):
+        import gtk
+        w = gtk.Window()
+        w.realize()
+        origin = w.get_window().get_origin()
+        self.assertTrue(isinstance(origin, tuple))
+        self.assertEqual(len(origin), 2)
diff --git a/tests/test_repository.py b/tests/test_repository.py
new file mode 100644 (file)
index 0000000..d324dfc
--- /dev/null
@@ -0,0 +1,396 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2013 Simon Feltman <sfeltman@gnome.org>
+#
+#   test_repository.py: Test for the GIRepository module
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from __future__ import absolute_import
+
+import unittest
+import collections
+
+import gi._gi as GIRepository
+from gi.module import repository as repo
+from gi.repository import GObject
+from gi.repository import GIMarshallingTests
+from gi.repository import GIRepository as IntrospectedRepository
+
+from .helper import capture_glib_warnings
+
+
+def find_child_info(info, getter_name, name):
+    getter = getattr(info, getter_name)
+    for child in getter():
+        if child.get_name() == name:
+            return child
+    else:
+        raise ValueError('child info %s not found' % name)
+
+
+class Test(unittest.TestCase):
+    def setUp(self):
+        repo.require('GLib')
+        repo.require('GObject')
+        repo.require('GIMarshallingTests')
+
+    def test_repo_get_dependencies(self):
+        self.assertRaises(TypeError, repo.get_dependencies)
+        self.assertEqual(repo.get_dependencies("GLib"), [])
+        self.assertEqual(repo.get_dependencies("GObject"), ["GLib-2.0"])
+
+    def test_repo_is_registered(self):
+        self.assertRaises(TypeError, repo.is_registered)
+        self.assertRaises(TypeError, repo.is_registered, None)
+        self.assertTrue(repo.is_registered("GIRepository"))
+        self.assertTrue(repo.is_registered("GIRepository", None))
+        self.assertTrue(isinstance(repo.is_registered("GIRepository"), bool))
+        self.assertTrue(repo.is_registered("GIRepository", "2.0"))
+        self.assertFalse(repo.is_registered("GIRepository", ""))
+        self.assertFalse(repo.is_registered("GIRepository", "99.0"))
+        self.assertFalse(repo.is_registered("GIRepository", "1.0"))
+
+    def test_repo_get_immediate_dependencies(self):
+        self.assertRaises(TypeError, repo.get_immediate_dependencies)
+        self.assertEqual(repo.get_immediate_dependencies("GLib"), [])
+        self.assertEqual(
+            repo.get_immediate_dependencies("GObject"), ["GLib-2.0"])
+        self.assertEqual(
+            repo.get_immediate_dependencies(namespace="GObject"), ["GLib-2.0"])
+        self.assertEqual(
+            repo.get_immediate_dependencies("GIMarshallingTests"), ["Gio-2.0"])
+
+    def test_arg_info(self):
+        func_info = repo.find_by_name('GIMarshallingTests', 'array_fixed_out_struct')
+        args = func_info.get_arguments()
+        self.assertTrue(len(args), 1)
+
+        arg = args[0]
+        self.assertEqual(arg.get_container(), func_info)
+        self.assertEqual(arg.get_direction(), GIRepository.Direction.OUT)
+        self.assertEqual(arg.get_name(), 'structs')
+        self.assertEqual(arg.get_namespace(), 'GIMarshallingTests')
+        self.assertFalse(arg.is_caller_allocates())
+        self.assertFalse(arg.is_optional())
+        self.assertFalse(arg.is_return_value())
+        self.assertFalse(arg.may_be_null())
+        self.assertEqual(arg.get_destroy(), -1)
+        self.assertEqual(arg.get_ownership_transfer(), GIRepository.Transfer.NOTHING)
+        self.assertEqual(arg.get_scope(), GIRepository.ScopeType.INVALID)
+        self.assertEqual(arg.get_type().get_tag(), GIRepository.TypeTag.ARRAY)
+
+    def test_base_info(self):
+        info = repo.find_by_name('GIMarshallingTests', 'Object')
+        self.assertEqual(info.__name__, 'Object')
+        self.assertEqual(info.get_name(), 'Object')
+        self.assertEqual(info.__module__, 'gi.repository.GIMarshallingTests')
+        self.assertEqual(info.get_name_unescaped(), 'Object')
+        self.assertEqual(info.get_namespace(), 'GIMarshallingTests')
+        self.assertEqual(info.get_container(), None)
+        info2 = repo.find_by_name('GIMarshallingTests', 'Object')
+        self.assertFalse(info is info2)
+        self.assertEqual(info, info2)
+        self.assertTrue(info.equal(info2))
+        assert isinstance(info.is_deprecated(), bool)
+        assert isinstance(info.get_type(), int)
+        assert info.get_attribute("nopenope") is None
+
+    def test_object_info(self):
+        info = repo.find_by_name('GIMarshallingTests', 'Object')
+        self.assertEqual(info.get_parent(), repo.find_by_name('GObject', 'Object'))
+        self.assertTrue(isinstance(info.get_methods(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_fields(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_interfaces(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_constants(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_vfuncs(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_properties(), collections.Iterable))
+        self.assertFalse(info.get_abstract())
+        self.assertEqual(info.get_class_struct(), repo.find_by_name('GIMarshallingTests', 'ObjectClass'))
+        self.assertEqual(info.get_type_name(), 'GIMarshallingTestsObject')
+        self.assertEqual(info.get_type_init(), 'gi_marshalling_tests_object_get_type')
+        self.assertFalse(info.get_fundamental())
+        self.assertEqual(info.get_parent(), repo.find_by_name('GObject', 'Object'))
+        assert info.find_vfunc("nopenope") is None
+        vfunc = info.find_vfunc("method_int8_out")
+        assert isinstance(vfunc, GIRepository.VFuncInfo)
+
+    def test_callable_inheritance(self):
+        self.assertTrue(issubclass(GIRepository.CallableInfo, GIRepository.BaseInfo))
+        self.assertTrue(issubclass(GIRepository.CallbackInfo, GIRepository.CallableInfo))
+        self.assertTrue(issubclass(GIRepository.FunctionInfo, GIRepository.CallableInfo))
+        self.assertTrue(issubclass(GIRepository.VFuncInfo, GIRepository.CallableInfo))
+        self.assertTrue(issubclass(GIRepository.SignalInfo, GIRepository.CallableInfo))
+
+    def test_registered_type_info(self):
+        info = repo.find_by_name('GIMarshallingTests', 'Object')
+        # Call these from the class because GIObjectInfo overrides them
+        self.assertEqual(GIRepository.RegisteredTypeInfo.get_g_type(info),
+                         GObject.type_from_name('GIMarshallingTestsObject'))
+        self.assertEqual(GIRepository.RegisteredTypeInfo.get_type_name(info),
+                         'GIMarshallingTestsObject')
+        self.assertEqual(GIRepository.RegisteredTypeInfo.get_type_init(info),
+                         'gi_marshalling_tests_object_get_type')
+
+    def test_fundamental_object_info(self):
+        repo.require('Regress')
+        info = repo.find_by_name('Regress', 'TestFundamentalObject')
+        self.assertTrue(info.get_abstract())
+        self.assertTrue(info.get_fundamental())
+        self.assertEqual(info.get_ref_function(), 'regress_test_fundamental_object_ref')
+        self.assertEqual(info.get_unref_function(), 'regress_test_fundamental_object_unref')
+        self.assertEqual(info.get_get_value_function(), 'regress_test_value_get_fundamental_object')
+        self.assertEqual(info.get_set_value_function(), 'regress_test_value_set_fundamental_object')
+
+    def test_interface_info(self):
+        info = repo.find_by_name('GIMarshallingTests', 'Interface')
+        self.assertTrue(isinstance(info.get_methods(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_vfuncs(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_constants(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_prerequisites(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_properties(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_signals(), collections.Iterable))
+
+        method = info.find_method('test_int8_in')
+        vfunc = info.find_vfunc('test_int8_in')
+        self.assertEqual(method.get_name(), 'test_int8_in')
+        self.assertEqual(vfunc.get_invoker(), method)
+        self.assertEqual(method.get_vfunc(), vfunc)
+
+        iface = info.get_iface_struct()
+        self.assertEqual(iface, repo.find_by_name('GIMarshallingTests', 'InterfaceIface'))
+
+        assert info.find_signal("nopenope") is None
+
+    def test_struct_info(self):
+        info = repo.find_by_name('GIMarshallingTests', 'InterfaceIface')
+        self.assertTrue(isinstance(info, GIRepository.StructInfo))
+        self.assertTrue(isinstance(info.get_fields(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_methods(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_size(), int))
+        self.assertTrue(isinstance(info.get_alignment(), int))
+        self.assertTrue(info.is_gtype_struct())
+        self.assertFalse(info.is_foreign())
+
+    def test_enum_info(self):
+        info = repo.find_by_name('GIMarshallingTests', 'Enum')
+        self.assertTrue(isinstance(info, GIRepository.EnumInfo))
+        self.assertTrue(isinstance(info.get_values(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_methods(), collections.Iterable))
+        self.assertFalse(info.is_flags())
+        self.assertTrue(info.get_storage_type() > 0)  # might be platform dependent
+
+    def test_union_info(self):
+        info = repo.find_by_name('GIMarshallingTests', 'Union')
+        self.assertTrue(isinstance(info, GIRepository.UnionInfo))
+        self.assertTrue(isinstance(info.get_fields(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_methods(), collections.Iterable))
+        self.assertTrue(isinstance(info.get_size(), int))
+
+    def test_type_info(self):
+        func_info = repo.find_by_name('GIMarshallingTests', 'array_fixed_out_struct')
+        arg_info, = func_info.get_arguments()
+        type_info = arg_info.get_type()
+
+        self.assertTrue(type_info.is_pointer())
+        self.assertEqual(type_info.get_tag(), GIRepository.TypeTag.ARRAY)
+        self.assertEqual(type_info.get_tag_as_string(), 'array')
+        self.assertEqual(type_info.get_param_type(0).get_tag(),
+                         GIRepository.TypeTag.INTERFACE)
+        self.assertEqual(type_info.get_param_type(0).get_interface(),
+                         repo.find_by_name('GIMarshallingTests', 'SimpleStruct'))
+        self.assertEqual(type_info.get_interface(), None)
+        self.assertEqual(type_info.get_array_length(), -1)
+        self.assertEqual(type_info.get_array_fixed_size(), 2)
+        self.assertFalse(type_info.is_zero_terminated())
+        self.assertEqual(type_info.get_array_type(), GIRepository.ArrayType.C)
+
+    def test_field_info(self):
+        info = repo.find_by_name('GIMarshallingTests', 'InterfaceIface')
+        field = find_child_info(info, 'get_fields', 'test_int8_in')
+        self.assertEqual(field.get_name(), 'test_int8_in')
+        self.assertTrue(field.get_flags() & GIRepository.FieldInfoFlags.IS_READABLE)
+        self.assertFalse(field.get_flags() & GIRepository.FieldInfoFlags.IS_WRITABLE)
+        self.assertEqual(field.get_type().get_tag(), GIRepository.TypeTag.INTERFACE)
+
+        # don't test actual values because that might fail with architecture differences
+        self.assertTrue(isinstance(field.get_size(), int))
+        self.assertTrue(isinstance(field.get_offset(), int))
+
+    def test_property_info(self):
+        info = repo.find_by_name('GIMarshallingTests', 'PropertiesObject')
+        prop = find_child_info(info, 'get_properties', 'some-object')
+
+        flags = GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT
+        self.assertEqual(prop.get_flags(), flags)
+        self.assertEqual(prop.get_type().get_tag(), GIRepository.TypeTag.INTERFACE)
+        self.assertEqual(prop.get_type().get_interface(),
+                         repo.find_by_name('GObject', 'Object'))
+        self.assertEqual(prop.get_ownership_transfer(), GIRepository.Transfer.NOTHING)
+
+    def test_callable_info(self):
+        func_info = repo.find_by_name('GIMarshallingTests', 'array_fixed_out_struct')
+        self.assertTrue(hasattr(func_info, 'invoke'))
+        self.assertTrue(isinstance(func_info.get_arguments(), collections.Iterable))
+        self.assertEqual(func_info.get_caller_owns(), GIRepository.Transfer.NOTHING)
+        self.assertFalse(func_info.may_return_null())
+        self.assertEqual(func_info.get_return_type().get_tag(), GIRepository.TypeTag.VOID)
+        self.assertRaises(AttributeError, func_info.get_return_attribute, '_not_an_attr')
+
+    def test_signal_info(self):
+        repo.require('Regress')
+        info = repo.find_by_name('Regress', 'TestObj')
+        sig_info = find_child_info(info, 'get_signals', 'test')
+
+        sig_flags = GObject.SignalFlags.RUN_LAST | \
+            GObject.SignalFlags.NO_RECURSE | GObject.SignalFlags.NO_HOOKS
+
+        self.assertTrue(sig_info is not None)
+        self.assertTrue(isinstance(sig_info, GIRepository.CallableInfo))
+        self.assertTrue(isinstance(sig_info, GIRepository.SignalInfo))
+        self.assertEqual(sig_info.get_name(), 'test')
+        self.assertEqual(sig_info.get_class_closure(), None)
+        self.assertFalse(sig_info.true_stops_emit())
+        self.assertEqual(sig_info.get_flags(), sig_flags)
+
+    def test_notify_signal_info_with_obj(self):
+        repo.require('Regress')
+        info = repo.find_by_name('Regress', 'TestObj')
+        sig_info = find_child_info(info, 'get_signals', 'sig-with-array-prop')
+
+        sig_flags = GObject.SignalFlags.RUN_LAST
+
+        self.assertTrue(sig_info is not None)
+        self.assertTrue(isinstance(sig_info, GIRepository.CallableInfo))
+        self.assertTrue(isinstance(sig_info, GIRepository.SignalInfo))
+        self.assertEqual(sig_info.get_name(), 'sig-with-array-prop')
+        self.assertEqual(sig_info.get_class_closure(), None)
+        self.assertFalse(sig_info.true_stops_emit())
+        self.assertEqual(sig_info.get_flags(), sig_flags)
+
+    def test_object_constructor(self):
+        info = repo.find_by_name('GIMarshallingTests', 'Object')
+        method = find_child_info(info, 'get_methods', 'new')
+
+        self.assertTrue(isinstance(method, GIRepository.CallableInfo))
+        self.assertTrue(isinstance(method, GIRepository.FunctionInfo))
+        self.assertTrue(method in info.get_methods())
+        self.assertEqual(method.get_name(), 'new')
+        self.assertFalse(method.is_method())
+        self.assertTrue(method.is_constructor())
+        self.assertEqual(method.get_symbol(), 'gi_marshalling_tests_object_new')
+
+        flags = method.get_flags()
+        self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_METHOD)
+        self.assertTrue(flags & GIRepository.FunctionInfoFlags.IS_CONSTRUCTOR)
+        self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_GETTER)
+        self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_SETTER)
+        self.assertFalse(flags & GIRepository.FunctionInfoFlags.WRAPS_VFUNC)
+        self.assertFalse(flags & GIRepository.FunctionInfoFlags.THROWS)
+
+    def test_method_info(self):
+        info = repo.find_by_name('GIMarshallingTests', 'Object')
+        method = find_child_info(info, 'get_methods', 'vfunc_return_value_only')
+
+        self.assertTrue(isinstance(method, GIRepository.CallableInfo))
+        self.assertTrue(isinstance(method, GIRepository.FunctionInfo))
+        self.assertTrue(method in info.get_methods())
+        self.assertEqual(method.get_name(), 'vfunc_return_value_only')
+        self.assertFalse(method.is_constructor())
+        self.assertEqual(method.get_symbol(), 'gi_marshalling_tests_object_vfunc_return_value_only')
+        self.assertTrue(method.is_method())
+
+        flags = method.get_flags()
+        self.assertTrue(flags & GIRepository.FunctionInfoFlags.IS_METHOD)
+        self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_CONSTRUCTOR)
+        self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_GETTER)
+        self.assertFalse(flags & GIRepository.FunctionInfoFlags.IS_SETTER)
+        self.assertFalse(flags & GIRepository.FunctionInfoFlags.WRAPS_VFUNC)
+        self.assertFalse(flags & GIRepository.FunctionInfoFlags.THROWS)
+
+    def test_vfunc_info(self):
+        info = repo.find_by_name('GIMarshallingTests', 'Object')
+        invoker = find_child_info(info, 'get_methods', 'vfunc_return_value_only')
+        vfunc = find_child_info(info, 'get_vfuncs', 'vfunc_return_value_only')
+
+        self.assertTrue(isinstance(vfunc, GIRepository.CallableInfo))
+        self.assertTrue(isinstance(vfunc, GIRepository.VFuncInfo))
+        self.assertEqual(vfunc.get_name(), 'vfunc_return_value_only')
+        self.assertEqual(vfunc.get_invoker(), invoker)
+        self.assertEqual(invoker, info.find_method('vfunc_return_value_only'))
+        self.assertEqual(vfunc.get_flags(), 0)
+        self.assertEqual(vfunc.get_offset(), 0xFFFF)  # unknown offset
+        self.assertEqual(vfunc.get_signal(), None)
+
+    def test_callable_can_throw_gerror(self):
+        info = repo.find_by_name('GIMarshallingTests', 'Object')
+        invoker = find_child_info(info, 'get_methods', 'vfunc_meth_with_error')
+        vfunc = find_child_info(info, 'get_vfuncs', 'vfunc_meth_with_err')
+
+        self.assertTrue(invoker.can_throw_gerror())
+        self.assertTrue(vfunc.can_throw_gerror())
+
+        # Test that args do not include the GError**
+        self.assertEqual(len(invoker.get_arguments()), 1)
+        self.assertEqual(len(vfunc.get_arguments()), 1)
+
+        # Sanity check method that does not throw.
+        invoker_no_throws = find_child_info(info, 'get_methods', 'method_int8_in')
+        self.assertFalse(invoker_no_throws.can_throw_gerror())
+
+    def test_flags_double_registration_error(self):
+        # a warning is printed for double registration and pygobject will
+        # also raise a RuntimeError.
+        GIMarshallingTests.NoTypeFlags  # cause flags registration
+        info = repo.find_by_name('GIMarshallingTests', 'NoTypeFlags')
+        with capture_glib_warnings(allow_warnings=True):
+            self.assertRaises(RuntimeError,
+                              GIRepository.flags_register_new_gtype_and_add,
+                              info)
+
+    def test_enum_double_registration_error(self):
+        # a warning is printed for double registration and pygobject will
+        # also raise a RuntimeError.
+        GIMarshallingTests.Enum  # cause enum registration
+        info = repo.find_by_name('GIMarshallingTests', 'Enum')
+        with capture_glib_warnings(allow_warnings=True):
+            self.assertRaises(RuntimeError,
+                              GIRepository.enum_register_new_gtype_and_add,
+                              info)
+
+    def test_enums(self):
+        self.assertTrue(hasattr(GIRepository, 'Direction'))
+        self.assertTrue(hasattr(GIRepository, 'Transfer'))
+        self.assertTrue(hasattr(GIRepository, 'ArrayType'))
+        self.assertTrue(hasattr(GIRepository, 'ScopeType'))
+        self.assertTrue(hasattr(GIRepository, 'VFuncInfoFlags'))
+        self.assertTrue(hasattr(GIRepository, 'FieldInfoFlags'))
+        self.assertTrue(hasattr(GIRepository, 'FunctionInfoFlags'))
+        self.assertTrue(hasattr(GIRepository, 'TypeTag'))
+        self.assertTrue(hasattr(GIRepository, 'InfoType'))
+
+    def test_introspected_argument_info(self):
+        self.assertTrue(isinstance(IntrospectedRepository.Argument.__info__,
+                                   GIRepository.UnionInfo))
+
+        arg = IntrospectedRepository.Argument()
+        self.assertTrue(isinstance(arg.__info__, GIRepository.UnionInfo))
+
+        old_info = IntrospectedRepository.Argument.__info__
+        IntrospectedRepository.Argument.__info__ = 'not an info'
+        self.assertRaises(TypeError, IntrospectedRepository.Argument)
+        IntrospectedRepository.Argument.__info__ = old_info
diff --git a/tests/test_resulttuple.py b/tests/test_resulttuple.py
new file mode 100644 (file)
index 0000000..4576495
--- /dev/null
@@ -0,0 +1,91 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2015 Christoph Reiter <reiter.christoph@gmail.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from __future__ import absolute_import
+
+import unittest
+import pickle
+
+import gi
+from gi.repository import GIMarshallingTests
+from gi.repository import Regress
+
+
+ResultTuple = gi._gi.ResultTuple
+
+
+class TestResultTuple(unittest.TestCase):
+
+    def test_base(self):
+        self.assertTrue(issubclass(ResultTuple, tuple))
+
+    def test_create(self):
+        names = [None, "foo", None, "bar"]
+        for i in range(10):
+            new = ResultTuple._new_type(names)
+            self.assertTrue(issubclass(new, ResultTuple))
+
+    def test_repr_dir(self):
+        new = ResultTuple._new_type([None, "foo", None, "bar"])
+        inst = new([1, 2, 3, "a"])
+
+        self.assertEqual(repr(inst), "(1, foo=2, 3, bar='a')")
+        self.assertTrue("foo" in dir(inst))
+
+    def test_repr_dir_empty(self):
+        new = ResultTuple._new_type([])
+        inst = new()
+        self.assertEqual(repr(inst), "()")
+        dir(inst)
+
+    def test_getatttr(self):
+        new = ResultTuple._new_type([None, "foo", None, "bar"])
+        inst = new([1, 2, 3, "a"])
+
+        self.assertTrue(hasattr(inst, "foo"))
+        self.assertEqual(inst.foo, inst[1])
+        self.assertRaises(AttributeError, getattr, inst, "nope")
+
+    def test_pickle(self):
+        new = ResultTuple._new_type([None, "foo", None, "bar"])
+        inst = new([1, 2, 3, "a"])
+
+        inst2 = pickle.loads(pickle.dumps(inst))
+        self.assertEqual(inst2, inst)
+        self.assertTrue(isinstance(inst2, tuple))
+        self.assertFalse(isinstance(inst2, new))
+
+    def test_gi(self):
+        res = GIMarshallingTests.init_function([])
+        self.assertEqual(repr(res), "(True, argv=[])")
+
+        res = GIMarshallingTests.array_return_etc(5, 9)
+        self.assertEqual(repr(res), "([5, 0, 1, 9], sum=14)")
+
+        res = GIMarshallingTests.array_out_etc(-5, 9)
+        self.assertEqual(repr(res), "(ints=[-5, 0, 1, 9], sum=4)")
+
+        cb = lambda: (1, 2)
+        res = GIMarshallingTests.callback_multiple_out_parameters(cb)
+        self.assertEqual(repr(res), "(a=1.0, b=2.0)")
+
+    def test_regress(self):
+        res = Regress.TestObj().skip_return_val(50, 42.0, 60, 2, 3)
+        self.assertEqual(repr(res), "(out_b=51, inout_d=61, out_sum=32)")
diff --git a/tests/test_signal.py b/tests/test_signal.py
new file mode 100644 (file)
index 0000000..16d95c2
--- /dev/null
@@ -0,0 +1,1570 @@
+# -*- Mode: Python -*-
+
+from __future__ import absolute_import
+
+import gc
+import unittest
+import sys
+import weakref
+import threading
+import time
+
+from gi.repository import GObject, GLib, Regress, Gio
+from gi import _signalhelper as signalhelper
+from gi.module import repository as repo
+from gi._compat import PY3, long_
+
+import testhelper
+from .helper import capture_glib_warnings, capture_gi_deprecation_warnings
+
+
+class C(GObject.GObject):
+    __gsignals__ = {'my_signal': (GObject.SignalFlags.RUN_FIRST, None,
+                                  (GObject.TYPE_INT,))}
+
+    def do_my_signal(self, arg):
+        self.arg = arg
+
+
+class D(C):
+    def do_my_signal(self, arg2):
+        self.arg2 = arg2
+        C.do_my_signal(self, arg2)
+
+
+class TestSignalCreation(unittest.TestCase):
+    # Bug 540376.
+    def test_illegals(self):
+        self.assertRaises(TypeError, lambda: GObject.signal_new('test',
+                                                                None,
+                                                                0,
+                                                                None,
+                                                                (GObject.TYPE_LONG,)))
+
+
+class TestChaining(unittest.TestCase):
+    def setUp(self):
+        self.inst = C()
+        self.inst.connect("my_signal", self.my_signal_handler_cb, 1, 2, 3)
+
+    def my_signal_handler_cb(self, *args):
+        assert len(args) == 5
+        assert isinstance(args[0], C)
+        assert args[0] == self.inst
+
+        assert isinstance(args[1], int)
+        assert args[1] == 42
+
+        assert args[2:] == (1, 2, 3)
+
+    def test_chaining(self):
+        self.inst.emit("my_signal", 42)
+        assert self.inst.arg == 42
+
+    def test_chaining2(self):
+        inst2 = D()
+        inst2.emit("my_signal", 44)
+        assert inst2.arg == 44
+        assert inst2.arg2 == 44
+
+# This is for bug 153718
+
+
+class TestGSignalsError(unittest.TestCase):
+    def test_invalid_type(self, *args):
+        def foo():
+            class Foo(GObject.GObject):
+                __gsignals__ = None
+        self.assertRaises(TypeError, foo)
+        gc.collect()
+
+    def test_invalid_name(self, *args):
+        def foo():
+            class Foo(GObject.GObject):
+                __gsignals__ = {'not-exists': 'override'}
+
+        with capture_glib_warnings(allow_warnings=True):
+            self.assertRaises(TypeError, foo)
+        gc.collect()
+
+
+class TestGPropertyError(unittest.TestCase):
+    def test_invalid_type(self, *args):
+        def foo():
+            class Foo(GObject.GObject):
+                __gproperties__ = None
+        self.assertRaises(TypeError, foo)
+        gc.collect()
+
+    def test_invalid_name(self, *args):
+        def foo():
+            class Foo(GObject.GObject):
+                __gproperties__ = {None: None}
+
+        self.assertRaises(TypeError, foo)
+        gc.collect()
+
+
+class TestList(unittest.TestCase):
+    def test_list_names(self):
+        self.assertEqual(GObject.signal_list_names(C), ('my-signal',))
+
+
+def my_accumulator(ihint, return_accu, handler_return, user_data):
+    """An accumulator that stops emission when the sum of handler
+    returned values reaches 3"""
+    assert user_data == "accum data"
+    if return_accu >= 3:
+        return False, return_accu
+    return True, return_accu + handler_return
+
+
+class Foo(GObject.GObject):
+    my_acc_signal = GObject.Signal(return_type=GObject.TYPE_INT,
+                                   flags=GObject.SignalFlags.RUN_LAST,
+                                   accumulator=my_accumulator,
+                                   accu_data="accum data")
+
+    my_other_acc_signal = GObject.Signal(return_type=GObject.TYPE_BOOLEAN,
+                                         flags=GObject.SignalFlags.RUN_LAST,
+                                         accumulator=GObject.signal_accumulator_true_handled)
+
+    my_acc_first_wins = GObject.Signal(return_type=GObject.TYPE_BOOLEAN,
+                                       flags=GObject.SignalFlags.RUN_LAST,
+                                       accumulator=GObject.signal_accumulator_first_wins)
+
+
+class TestAccumulator(unittest.TestCase):
+
+    def test_accumulator(self):
+        inst = Foo()
+        inst.my_acc_signal.connect(lambda obj: 1)
+        inst.my_acc_signal.connect(lambda obj: 2)
+        # the value returned in the following handler will not be
+        # considered, because at this point the accumulator already
+        # reached its limit.
+        inst.my_acc_signal.connect(lambda obj: 3)
+        retval = inst.my_acc_signal.emit()
+        self.assertEqual(retval, 3)
+
+    def test_accumulator_true_handled(self):
+        inst = Foo()
+        inst.my_other_acc_signal.connect(self._true_handler1)
+        inst.my_other_acc_signal.connect(self._true_handler2)
+        # the following handler will not be called because handler2
+        # returns True, so it should stop the emission.
+        inst.my_other_acc_signal.connect(self._true_handler3)
+        self.__true_val = None
+        inst.my_other_acc_signal.emit()
+        self.assertEqual(self.__true_val, 2)
+
+    def test_accumulator_first_wins(self):
+        # First signal hit will always win
+        inst = Foo()
+        inst.my_acc_first_wins.connect(self._true_handler3)
+        inst.my_acc_first_wins.connect(self._true_handler1)
+        inst.my_acc_first_wins.connect(self._true_handler2)
+        self.__true_val = None
+        inst.my_acc_first_wins.emit()
+        self.assertEqual(self.__true_val, 3)
+
+    def _true_handler1(self, obj):
+        self.__true_val = 1
+        return False
+
+    def _true_handler2(self, obj):
+        self.__true_val = 2
+        return True
+
+    def _true_handler3(self, obj):
+        self.__true_val = 3
+        return False
+
+
+class E(GObject.GObject):
+    __gsignals__ = {'signal': (GObject.SignalFlags.RUN_FIRST, None,
+                               ())}
+
+    # Property used to test detailed signal
+    prop = GObject.Property(type=int, default=0)
+
+    def __init__(self):
+        GObject.GObject.__init__(self)
+        self.status = 0
+
+    def do_signal(self):
+        assert self.status == 0
+        self.status = 1
+
+
+class F(GObject.GObject):
+    __gsignals__ = {'signal': (GObject.SignalFlags.RUN_FIRST, None,
+                               ())}
+
+    def __init__(self):
+        GObject.GObject.__init__(self)
+        self.status = 0
+
+    def do_signal(self):
+        self.status += 1
+
+
+class TestEmissionHook(unittest.TestCase):
+    def test_add(self):
+        self.hook = True
+        e = E()
+        e.connect('signal', self._callback)
+        GObject.add_emission_hook(E, "signal", self._emission_hook)
+        e.emit('signal')
+        self.assertEqual(e.status, 3)
+
+    def test_remove(self):
+        self.hook = False
+        e = E()
+        e.connect('signal', self._callback)
+        hook_id = GObject.add_emission_hook(E, "signal", self._emission_hook)
+        GObject.remove_emission_hook(E, "signal", hook_id)
+        e.emit('signal')
+        self.assertEqual(e.status, 3)
+
+    def _emission_hook(self, e):
+        self.assertEqual(e.status, 1)
+        e.status = 2
+
+    def _callback(self, e):
+        if self.hook:
+            self.assertEqual(e.status, 2)
+        else:
+            self.assertEqual(e.status, 1)
+        e.status = 3
+
+    def test_callback_return_false(self):
+        self.hook = False
+        obj = F()
+
+        def _emission_hook(obj):
+            obj.status += 1
+            return False
+        GObject.add_emission_hook(obj, "signal", _emission_hook)
+        obj.emit('signal')
+        obj.emit('signal')
+        self.assertEqual(obj.status, 3)
+
+    def test_callback_return_true(self):
+        self.hook = False
+        obj = F()
+
+        def _emission_hook(obj):
+            obj.status += 1
+            return True
+        hook_id = GObject.add_emission_hook(obj, "signal", _emission_hook)
+        obj.emit('signal')
+        obj.emit('signal')
+        GObject.remove_emission_hook(obj, "signal", hook_id)
+        self.assertEqual(obj.status, 4)
+
+    def test_callback_return_true_but_remove(self):
+        self.hook = False
+        obj = F()
+
+        def _emission_hook(obj):
+            obj.status += 1
+            return True
+        hook_id = GObject.add_emission_hook(obj, "signal", _emission_hook)
+        obj.emit('signal')
+        GObject.remove_emission_hook(obj, "signal", hook_id)
+        obj.emit('signal')
+        self.assertEqual(obj.status, 3)
+
+
+class TestMatching(unittest.TestCase):
+    class Object(GObject.Object):
+        status = 0
+        prop = GObject.Property(type=int, default=0)
+
+        @GObject.Signal()
+        def my_signal(self):
+            pass
+
+    @unittest.expectedFailure  # https://bugzilla.gnome.org/show_bug.cgi?id=692918
+    def test_signal_handler_block_matching(self):
+        def dummy(*args):
+            "Hack to work around: "
+
+        def foo(obj):
+            obj.status += 1
+
+        obj = self.Object()
+        handler_id = GObject.signal_connect_closure(obj, 'my-signal', foo, after=False)
+        handler_id
+
+        self.assertEqual(obj.status, 0)
+        obj.emit('my-signal')
+        self.assertEqual(obj.status, 1)
+
+        # Blocking by match criteria disables the foo callback
+        signal_id, detail = GObject.signal_parse_name('my-signal', obj, True)
+        count = GObject.signal_handlers_block_matched(obj,
+                                                      GObject.SignalMatchType.ID | GObject.SignalMatchType.CLOSURE,
+                                                      signal_id=signal_id, detail=detail,
+                                                      closure=foo, func=dummy, data=dummy)
+        self.assertEqual(count, 1)
+        obj.emit('my-signal')
+        self.assertEqual(obj.status, 1)
+
+        # Unblocking by the same match criteria allows callback to work again
+        count = GObject.signal_handlers_unblock_matched(obj,
+                                                        GObject.SignalMatchType.ID | GObject.SignalMatchType.CLOSURE,
+                                                        signal_id=signal_id, detail=detail,
+                                                        closure=foo, func=dummy, data=dummy)
+        self.assertEqual(count, 1)
+        obj.emit('my-signal')
+        self.assertEqual(obj.status, 2)
+
+        # Disconnecting by match criteria completely removes the handler
+        count = GObject.signal_handlers_disconnect_matched(obj,
+                                                           GObject.SignalMatchType.ID | GObject.SignalMatchType.CLOSURE,
+                                                           signal_id=signal_id, detail=detail,
+                                                           closure=foo, func=dummy, data=dummy)
+        self.assertEqual(count, 1)
+        obj.emit('my-signal')
+        self.assertEqual(obj.status, 2)
+
+    def test_signal_handler_find(self):
+        def foo(obj):
+            obj.status += 1
+
+        obj = self.Object()
+        handler_id = GObject.signal_connect_closure(obj, 'my-signal', foo, after=False)
+
+        signal_id, detail = GObject.signal_parse_name('my-signal', obj, True)
+        found_id = GObject.signal_handler_find(obj,
+                                               GObject.SignalMatchType.ID,
+                                               signal_id=signal_id, detail=detail,
+                                               closure=None, func=0, data=0)
+        self.assertEqual(handler_id, found_id)
+
+
+class TestClosures(unittest.TestCase):
+    def setUp(self):
+        self.count = 0
+        self.emission_stopped = False
+        self.emission_error = False
+        self.handler_pending = False
+
+    def _callback_handler_pending(self, e):
+        signal_id, detail = GObject.signal_parse_name('signal', e, True)
+        self.handler_pending = GObject.signal_has_handler_pending(e, signal_id, detail,
+                                                                  may_be_blocked=False)
+
+    def _callback(self, e):
+        self.count += 1
+
+    def _callback_stop_emission(self, obj, prop, stop_it):
+        if stop_it:
+            obj.stop_emission_by_name('notify::prop')
+            self.emission_stopped = True
+        else:
+            self.count += 1
+
+    def _callback_invalid_stop_emission_name(self, obj, prop):
+        with capture_glib_warnings(allow_warnings=True) as warn:
+            obj.stop_emission_by_name('notasignal::baddetail')
+            self.emission_error = True
+            self.assertTrue(warn)
+
+    def test_disconnect_by_func(self):
+        e = E()
+        e.connect('signal', self._callback)
+        e.disconnect_by_func(self._callback)
+        e.emit('signal')
+        self.assertEqual(self.count, 0)
+
+    def test_disconnect(self):
+        e = E()
+        handler_id = e.connect('signal', self._callback)
+        self.assertTrue(e.handler_is_connected(handler_id))
+        e.disconnect(handler_id)
+        e.emit('signal')
+        self.assertEqual(self.count, 0)
+        self.assertFalse(e.handler_is_connected(handler_id))
+
+    def test_stop_emission_by_name(self):
+        e = E()
+
+        # Sandwich a callback that stops emission in between a callback that increments
+        e.connect('notify::prop', self._callback_stop_emission, False)
+        e.connect('notify::prop', self._callback_stop_emission, True)
+        e.connect('notify::prop', self._callback_stop_emission, False)
+
+        e.set_property('prop', 1234)
+        self.assertEqual(e.get_property('prop'), 1234)
+        self.assertEqual(self.count, 1)
+        self.assertTrue(self.emission_stopped)
+
+    def test_stop_emission_by_name_error(self):
+        e = E()
+
+        e.connect('notify::prop', self._callback_invalid_stop_emission_name)
+        with capture_glib_warnings():
+            e.set_property('prop', 1234)
+        self.assertTrue(self.emission_error)
+
+    def test_handler_block(self):
+        e = E()
+        e.connect('signal', self._callback)
+        e.handler_block_by_func(self._callback)
+        e.emit('signal')
+        self.assertEqual(self.count, 0)
+
+    def test_handler_unblock(self):
+        e = E()
+        handler_id = e.connect('signal', self._callback)
+        e.handler_block(handler_id)
+        e.handler_unblock_by_func(self._callback)
+        e.emit('signal')
+        self.assertEqual(self.count, 1)
+
+    def test_handler_block_method(self):
+        # Filed as #375589
+        class A:
+            def __init__(self):
+                self.a = 0
+
+            def callback(self, o):
+                self.a = 1
+                o.handler_block_by_func(self.callback)
+
+        inst = A()
+        e = E()
+        e.connect("signal", inst.callback)
+        e.emit('signal')
+        self.assertEqual(inst.a, 1)
+        gc.collect()
+
+    def test_gstring(self):
+        class C(GObject.GObject):
+            __gsignals__ = {'my_signal': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_GSTRING,
+                                          (GObject.TYPE_GSTRING,))}
+
+            def __init__(self, test):
+                GObject.GObject.__init__(self)
+                self.test = test
+
+            def do_my_signal(self, data):
+                self.data = data
+                self.test.assertEqual(len(data), 3)
+                return ''.join([data[2], data[1], data[0]])
+        c = C(self)
+        data = c.emit("my_signal", "\01\00\02")
+        self.assertEqual(data, "\02\00\01")
+
+    def test_handler_pending(self):
+        obj = F()
+        obj.connect('signal', self._callback_handler_pending)
+        obj.connect('signal', self._callback)
+
+        self.assertEqual(self.count, 0)
+        self.assertEqual(self.handler_pending, False)
+
+        obj.emit('signal')
+        self.assertEqual(self.count, 1)
+        self.assertEqual(self.handler_pending, True)
+
+    def test_signal_handlers_destroy(self):
+        obj = F()
+        obj.connect('signal', self._callback)
+        obj.connect('signal', self._callback)
+        obj.connect('signal', self._callback)
+
+        obj.emit('signal')
+        self.assertEqual(self.count, 3)
+
+        # count should remain at 3 after all handlers are destroyed
+        GObject.signal_handlers_destroy(obj)
+        obj.emit('signal')
+        self.assertEqual(self.count, 3)
+
+
+class SigPropClass(GObject.GObject):
+    __gsignals__ = {'my_signal': (GObject.SignalFlags.RUN_FIRST, None,
+                                  (GObject.TYPE_INT,))}
+
+    __gproperties__ = {
+        'foo': (str, None, None, '',
+                GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT),
+        }
+
+    signal_emission_failed = False
+
+    def do_my_signal(self, arg):
+        self.arg = arg
+
+    def do_set_property(self, pspec, value):
+        if pspec.name == 'foo':
+            self._foo = value
+        else:
+            raise AttributeError('unknown property %s' % pspec.name)
+        try:
+            self.emit("my-signal", 1)
+        except TypeError:
+            self.signal_emission_failed = True
+
+
+class TestSigProp(unittest.TestCase):
+    def test_emit_in_property_setter(self):
+        obj = SigPropClass()
+        self.assertFalse(obj.signal_emission_failed)
+
+
+class CM(GObject.GObject):
+    __gsignals__ = dict(
+        test1=(GObject.SignalFlags.RUN_FIRST, None, ()),
+        test2=(GObject.SignalFlags.RUN_LAST, None, (str,)),
+        test3=(GObject.SignalFlags.RUN_LAST, int, (GObject.TYPE_DOUBLE,)),
+        test4=(GObject.SignalFlags.RUN_FIRST, None,
+               (bool, long_, GObject.TYPE_FLOAT, GObject.TYPE_DOUBLE, int,
+                GObject.TYPE_UINT, GObject.TYPE_ULONG)),
+        test_float=(GObject.SignalFlags.RUN_LAST, GObject.TYPE_FLOAT, (GObject.TYPE_FLOAT,)),
+        test_double=(GObject.SignalFlags.RUN_LAST, GObject.TYPE_DOUBLE, (GObject.TYPE_DOUBLE,)),
+        test_int64=(GObject.SignalFlags.RUN_LAST, GObject.TYPE_INT64, (GObject.TYPE_INT64,)),
+        test_string=(GObject.SignalFlags.RUN_LAST, str, (str,)),
+        test_object=(GObject.SignalFlags.RUN_LAST, object, (object,)),
+        test_paramspec=(GObject.SignalFlags.RUN_LAST, GObject.ParamSpec, ()),
+        test_paramspec_in=(GObject.SignalFlags.RUN_LAST, GObject.ParamSpec, (GObject.ParamSpec, )),
+        test_gvalue=(GObject.SignalFlags.RUN_LAST, GObject.Value, (GObject.Value,)),
+        test_gvalue_ret=(GObject.SignalFlags.RUN_LAST, GObject.Value, (GObject.TYPE_GTYPE,)),
+    )
+
+    testprop = GObject.Property(type=int)
+
+
+class _TestCMarshaller:
+    def setUp(self):
+        self.obj = CM()
+        testhelper.connectcallbacks(self.obj)
+
+    def test_test1(self):
+        self.obj.emit("test1")
+
+    def test_test2(self):
+        self.obj.emit("test2", "string")
+
+    def test_test3(self):
+        rv = self.obj.emit("test3", 42.0)
+        self.assertEqual(rv, 20)
+
+    def test_test4(self):
+        self.obj.emit("test4", True, long_(10), 3.14, 1.78, 20, long_(30), long_(31))
+
+    def test_float(self):
+        rv = self.obj.emit("test-float", 1.234)
+        self.assertTrue(rv >= 1.233999 and rv <= 1.2400001, rv)
+
+    def test_double(self):
+        rv = self.obj.emit("test-double", 1.234)
+        self.assertEqual(rv, 1.234)
+
+    def test_int64(self):
+        rv = self.obj.emit("test-int64", 102030405)
+        self.assertEqual(rv, 102030405)
+
+        rv = self.obj.emit("test-int64", GLib.MAXINT64)
+        self.assertEqual(rv, GLib.MAXINT64 - 1)
+
+        rv = self.obj.emit("test-int64", GLib.MININT64)
+        self.assertEqual(rv, GLib.MININT64)
+
+    def test_string(self):
+        rv = self.obj.emit("test-string", "str")
+        self.assertEqual(rv, "str")
+
+    def test_object(self):
+        rv = self.obj.emit("test-object", self)
+        self.assertEqual(rv, self)
+
+    def test_paramspec(self):
+        rv = self.obj.emit("test-paramspec")
+        self.assertEqual(rv.name, "test-param")
+        self.assertEqual(rv.nick, "test")
+
+    @unittest.skipUnless(hasattr(GObject, 'param_spec_boolean'),
+                         'too old gobject-introspection')
+    def test_paramspec_in(self):
+        rv = GObject.param_spec_boolean('mybool', 'test-bool', 'do something',
+                                        True, GObject.ParamFlags.READABLE)
+
+        rv2 = self.obj.emit("test-paramspec-in", rv)
+        self.assertEqual(type(rv), type(rv2))
+        self.assertEqual(rv2.name, "mybool")
+        self.assertEqual(rv2.nick, "test-bool")
+
+    def test_C_paramspec(self):
+        self.notify_called = False
+
+        def cb_notify(obj, prop):
+            self.notify_called = True
+            self.assertEqual(obj, self.obj)
+            self.assertEqual(prop.name, "testprop")
+
+        self.obj.connect("notify", cb_notify)
+        self.obj.set_property("testprop", 42)
+        self.assertTrue(self.notify_called)
+
+    def test_gvalue(self):
+        # implicit int
+        rv = self.obj.emit("test-gvalue", 42)
+        self.assertEqual(rv, 42)
+
+        # explicit float
+        v = GObject.Value(GObject.TYPE_FLOAT, 1.234)
+        rv = self.obj.emit("test-gvalue", v)
+        self.assertAlmostEqual(rv, 1.234, places=4)
+
+        # implicit float
+        rv = self.obj.emit("test-gvalue", 1.234)
+        self.assertAlmostEqual(rv, 1.234, places=4)
+
+        # explicit int64
+        v = GObject.Value(GObject.TYPE_INT64, GLib.MAXINT64)
+        rv = self.obj.emit("test-gvalue", v)
+        self.assertEqual(rv, GLib.MAXINT64)
+
+        # explicit uint64
+        v = GObject.Value(GObject.TYPE_UINT64, GLib.MAXUINT64)
+        rv = self.obj.emit("test-gvalue", v)
+        self.assertEqual(rv, GLib.MAXUINT64)
+
+    @unittest.expectedFailure  # https://bugzilla.gnome.org/show_bug.cgi?id=705291
+    def test_gvalue_implicit_int64(self):
+        # implicit int64
+        rv = self.obj.emit("test-gvalue", GLib.MAXINT64)
+        self.assertEqual(rv, GLib.MAXINT64)
+
+        # implicit uint64
+        rv = self.obj.emit("test-gvalue", GLib.MAXUINT64)
+        self.assertEqual(rv, GLib.MAXUINT64)
+
+    def test_gvalue_ret(self):
+        self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_INT),
+                         GLib.MAXINT)
+        self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_UINT),
+                         GLib.MAXUINT)
+        self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_INT64),
+                         GLib.MAXINT64)
+        self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_UINT64),
+                         GLib.MAXUINT64)
+        self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_STRING),
+                         "hello")
+
+
+class TestCMarshaller(_TestCMarshaller, unittest.TestCase):
+    pass
+
+
+# Test for 374653
+
+
+class TestPyGValue(unittest.TestCase):
+    def test_none_null_boxed_conversion(self):
+        class C(GObject.GObject):
+            __gsignals__ = dict(my_boxed_signal=(
+                GObject.SignalFlags.RUN_LAST,
+                GObject.TYPE_STRV, ()))
+
+        obj = C()
+        obj.connect('my-boxed-signal', lambda obj: None)
+        sys.last_type = None
+        obj.emit('my-boxed-signal')
+        assert not sys.last_type
+
+
+class TestSignalDecorator(unittest.TestCase):
+    class Decorated(GObject.GObject):
+        value = 0
+
+        @GObject.Signal
+        def pushed(self):
+            """this will push"""
+            self.value += 1
+
+        @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
+        def pulled(self):
+            self.value -= 1
+
+        @GObject.Signal(flags=GObject.SignalFlags.DETAILED)
+        def detailed(self):
+            self.value -= 1
+
+        stomped = GObject.Signal('stomped', arg_types=(int,), doc='this will stomp')
+        unnamed = GObject.Signal()
+
+    class DecoratedOverride(GObject.GObject):
+        overridden_closure_called = False
+        notify_called = False
+        value = GObject.Property(type=int, default=0)
+
+        @GObject.SignalOverride
+        def notify(self, *args, **kargs):
+            self.overridden_closure_called = True
+
+        def on_notify(self, obj, prop):
+            self.notify_called = True
+
+    def setUp(self):
+        self.unnamedCalled = False
+
+    def onUnnamed(self, obj):
+        self.unnamedCalled = True
+
+    def test_disconnect(self):
+        decorated = self.Decorated()
+        id_ = decorated.pushed.connect(lambda *args: None)
+        decorated.pushed.disconnect(id_)
+
+    def test_signal_repr(self):
+        decorated = self.Decorated()
+        assert repr(decorated.pushed) == 'BoundSignal("pushed")'
+
+    def test_signal_call(self):
+        decorated = self.Decorated()
+        assert decorated.value == 0
+        decorated.pushed()
+        assert decorated.value == 1
+
+    def test_connect_detailed(self):
+        decorated = self.Decorated()
+        id_ = decorated.detailed.connect_detailed(lambda *args: None, "foo")
+        decorated.pushed.disconnect(id_)
+
+    def test_get_signal_args(self):
+        self.assertEqual(self.Decorated.pushed.get_signal_args(),
+                         (GObject.SignalFlags.RUN_FIRST, None, tuple(), None, None))
+        self.assertEqual(self.Decorated.pulled.get_signal_args(),
+                         (GObject.SignalFlags.RUN_LAST, None, tuple(), None, None))
+        self.assertEqual(self.Decorated.stomped.get_signal_args(),
+                         (GObject.SignalFlags.RUN_FIRST, None, (int,), None, None))
+
+    def test_closures_called(self):
+        decorated = self.Decorated()
+        self.assertEqual(decorated.value, 0)
+        decorated.pushed.emit()
+        self.assertEqual(decorated.value, 1)
+        decorated.pulled.emit()
+        self.assertEqual(decorated.value, 0)
+
+    def test_signal_copy(self):
+        blah = self.Decorated.stomped.copy('blah')
+        self.assertEqual(str(blah), blah)
+        self.assertEqual(blah.func, self.Decorated.stomped.func)
+        self.assertEqual(blah.flags, self.Decorated.stomped.flags)
+        self.assertEqual(blah.return_type, self.Decorated.stomped.return_type)
+        self.assertEqual(blah.arg_types, self.Decorated.stomped.arg_types)
+        self.assertEqual(blah.__doc__, self.Decorated.stomped.__doc__)
+
+    def test_doc_string(self):
+        # Test the two techniques for setting doc strings on the signals
+        # class variables, through the "doc" keyword or as the getter doc string.
+        self.assertEqual(self.Decorated.stomped.__doc__, 'this will stomp')
+        self.assertEqual(self.Decorated.pushed.__doc__, 'this will push')
+
+    def test_unnamed_signal_gets_named(self):
+        self.assertEqual(str(self.Decorated.unnamed), 'unnamed')
+
+    def test_unnamed_signal_gets_called(self):
+        obj = self.Decorated()
+        obj.connect('unnamed', self.onUnnamed)
+        self.assertEqual(self.unnamedCalled, False)
+        obj.emit('unnamed')
+        self.assertEqual(self.unnamedCalled, True)
+
+    def test_overridden_signal(self):
+        # Test that the pushed signal is called in with super and the override
+        # which should both increment the "value" to 3
+        obj = self.DecoratedOverride()
+        obj.connect("notify", obj.on_notify)
+        self.assertEqual(obj.value, 0)
+        obj.value = 1
+        self.assertEqual(obj.value, 1)
+        self.assertTrue(obj.overridden_closure_called)
+        self.assertTrue(obj.notify_called)
+
+
+class TestSignalConnectors(unittest.TestCase):
+    class CustomButton(GObject.GObject):
+        on_notify_called = False
+        value = GObject.Property(type=int)
+
+        @GObject.Signal(arg_types=(int,))
+        def clicked(self, value):
+            self.value = value
+
+    def setUp(self):
+        self.obj = None
+        self.value = None
+
+    def on_clicked(self, obj, value):
+        self.obj = obj
+        self.value = value
+
+    def test_signal_notify(self):
+        def on_notify(obj, param):
+            obj.on_notify_called = True
+
+        obj = self.CustomButton()
+        obj.connect('notify', on_notify)
+        self.assertFalse(obj.on_notify_called)
+        obj.notify('value')
+        self.assertTrue(obj.on_notify_called)
+
+    def test_signal_emit(self):
+        # standard callback connection with different forms of emit.
+        obj = self.CustomButton()
+        obj.connect('clicked', self.on_clicked)
+
+        # vanilla
+        obj.emit('clicked', 1)
+        self.assertEqual(obj.value, 1)
+        self.assertEqual(obj, self.obj)
+        self.assertEqual(self.value, 1)
+
+        # using class signal as param
+        self.obj = None
+        self.value = None
+        obj.emit(self.CustomButton.clicked, 1)
+        self.assertEqual(obj, self.obj)
+        self.assertEqual(self.value, 1)
+
+        # using bound signal as param
+        self.obj = None
+        self.value = None
+        obj.emit(obj.clicked, 1)
+        self.assertEqual(obj, self.obj)
+        self.assertEqual(self.value, 1)
+
+        # using bound signal with emit
+        self.obj = None
+        self.value = None
+        obj.clicked.emit(1)
+        self.assertEqual(obj, self.obj)
+        self.assertEqual(self.value, 1)
+
+    def test_signal_class_connect(self):
+        obj = self.CustomButton()
+        obj.connect(self.CustomButton.clicked, self.on_clicked)
+        obj.emit('clicked', 2)
+        self.assertEqual(obj, self.obj)
+        self.assertEqual(self.value, 2)
+
+    def test_signal_bound_connect(self):
+        obj = self.CustomButton()
+        obj.clicked.connect(self.on_clicked)
+        obj.emit('clicked', 3)
+        self.assertEqual(obj, self.obj)
+        self.assertEqual(self.value, 3)
+
+
+class _ConnectDataTestBase(object):
+    # Notes:
+    #  - self.Object is overridden in sub-classes.
+    #  - Numeric suffixes indicate the number of user data args passed in.
+    Object = None
+
+    def run_connect_test(self, emit_args, user_data, flags=0):
+        obj = self.Object()
+        callback_args = []
+
+        def callback(*args):
+            callback_args.append(args)
+            return 0
+
+        obj.connect_data('sig-with-int64-prop', callback, connect_flags=flags, *user_data)
+        obj.emit('sig-with-int64-prop', *emit_args)
+        self.assertEqual(len(callback_args), 1)
+        return callback_args[0]
+
+    def test_0(self):
+        obj, value = self.run_connect_test([GLib.MAXINT64], user_data=[])
+        self.assertIsInstance(obj, self.Object)
+        self.assertEqual(value, GLib.MAXINT64)
+
+    def test_1(self):
+        obj, value, data = self.run_connect_test([GLib.MAXINT64],
+                                                 user_data=['mydata'])
+        self.assertIsInstance(obj, self.Object)
+        self.assertEqual(value, GLib.MAXINT64)
+        self.assertEqual(data, 'mydata')
+
+    def test_after_0(self):
+        obj, value = self.run_connect_test([GLib.MAXINT64],
+                                           user_data=[],
+                                           flags=GObject.ConnectFlags.AFTER)
+        self.assertIsInstance(obj, self.Object)
+        self.assertEqual(value, GLib.MAXINT64)
+
+    def test_after_1(self):
+        obj, value, data = self.run_connect_test([GLib.MAXINT64],
+                                                 user_data=['mydata'],
+                                                 flags=GObject.ConnectFlags.AFTER)
+        self.assertIsInstance(obj, self.Object)
+        self.assertEqual(value, GLib.MAXINT64)
+        self.assertEqual(data, 'mydata')
+
+    def test_swaped_0(self):
+        # Swapped only works with a single user data argument.
+        with self.assertRaises(ValueError):
+            self.run_connect_test([GLib.MAXINT64],
+                                  user_data=[],
+                                  flags=GObject.ConnectFlags.SWAPPED)
+
+    def test_swaped_1(self):
+        # Notice obj and data are reversed in the return.
+        data, value, obj = self.run_connect_test([GLib.MAXINT64],
+                                                 user_data=['mydata'],
+                                                 flags=GObject.ConnectFlags.SWAPPED)
+        self.assertIsInstance(obj, self.Object)
+        self.assertEqual(value, GLib.MAXINT64)
+        self.assertEqual(data, 'mydata')
+
+    def test_swaped_2(self):
+        # Swapped only works with a single user data argument.
+        with self.assertRaises(ValueError):
+            self.run_connect_test([GLib.MAXINT64],
+                                  user_data=[1, 2],
+                                  flags=GObject.ConnectFlags.SWAPPED)
+
+    def test_after_and_swapped_0(self):
+        # Swapped only works with a single user data argument.
+        with self.assertRaises(ValueError):
+            self.run_connect_test([GLib.MAXINT64],
+                                  user_data=[],
+                                  flags=GObject.ConnectFlags.AFTER | GObject.ConnectFlags.SWAPPED)
+
+    def test_after_and_swapped_1(self):
+        # Notice obj and data are reversed in the return.
+        data, value, obj = self.run_connect_test([GLib.MAXINT64],
+                                                 user_data=['mydata'],
+                                                 flags=GObject.ConnectFlags.AFTER | GObject.ConnectFlags.SWAPPED)
+        self.assertIsInstance(obj, self.Object)
+        self.assertEqual(value, GLib.MAXINT64)
+        self.assertEqual(data, 'mydata')
+
+    def test_after_and_swapped_2(self):
+        # Swapped only works with a single user data argument.
+        with self.assertRaises(ValueError):
+            self.run_connect_test([GLib.MAXINT64],
+                                  user_data=[],
+                                  flags=GObject.ConnectFlags.AFTER | GObject.ConnectFlags.SWAPPED)
+
+
+class TestConnectDataNonIntrospected(unittest.TestCase, _ConnectDataTestBase):
+    # This tests connect_data with non-introspected signals
+    # (created in Python in this case).
+    class Object(GObject.Object):
+        test = GObject.Signal()
+        sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64,
+                                             arg_types=[GObject.TYPE_INT64],
+                                             flags=GObject.SignalFlags.RUN_LAST)
+
+
+class TestConnectDataIntrospected(unittest.TestCase, _ConnectDataTestBase):
+    # This tests connect_data with introspected signals brought in from Regress.
+    Object = Regress.TestObj
+
+
+class TestInstallSignals(unittest.TestCase):
+    # These tests only test how signalhelper.install_signals works
+    # with the __gsignals__ dict and therefore does not need to use
+    # GObject as a base class because that would automatically call
+    # install_signals within the meta-class.
+    class Base(object):
+        __gsignals__ = {'test': (0, None, tuple())}
+
+    class Sub1(Base):
+        pass
+
+    class Sub2(Base):
+        @GObject.Signal
+        def sub2test(self):
+            pass
+
+    def setUp(self):
+        self.assertEqual(len(self.Base.__gsignals__), 1)
+        signalhelper.install_signals(self.Base)
+        self.assertEqual(len(self.Base.__gsignals__), 1)
+
+    def test_subclass_gets_empty_gsignals_dict(self):
+        # Installing signals will add the __gsignals__ dict to a class
+        # if it doesn't already exists.
+        self.assertFalse('__gsignals__' in self.Sub1.__dict__)
+        signalhelper.install_signals(self.Sub1)
+        self.assertTrue('__gsignals__' in self.Sub1.__dict__)
+        # Sub1 should only contain an empty signals dict, this tests:
+        # https://bugzilla.gnome.org/show_bug.cgi?id=686496
+        self.assertEqual(self.Sub1.__dict__['__gsignals__'], {})
+
+    def test_subclass_with_decorator_gets_gsignals_dict(self):
+        self.assertFalse('__gsignals__' in self.Sub2.__dict__)
+        signalhelper.install_signals(self.Sub2)
+        self.assertTrue('__gsignals__' in self.Sub2.__dict__)
+        self.assertEqual(len(self.Base.__gsignals__), 1)
+        self.assertEqual(len(self.Sub2.__gsignals__), 1)
+        self.assertTrue('sub2test' in self.Sub2.__gsignals__)
+
+        # Make sure the vfunc was added
+        self.assertTrue(hasattr(self.Sub2, 'do_sub2test'))
+
+
+# For this test to work with both python2 and 3 we need to dynamically
+# exec the given code due to the new syntax causing an error in python 2.
+annotated_class_code = """
+class AnnotatedSignalClass(GObject.GObject):
+    @GObject.Signal
+    def sig1(self, a:int, b:float):
+        pass
+
+    @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
+    def sig2_with_return(self, a:int, b:float) -> str:
+        return "test"
+"""
+
+
+@unittest.skipUnless(PY3, 'Argument annotations require Python 3')
+class TestPython3Signals(unittest.TestCase):
+    AnnotatedClass = None
+
+    def setUp(self):
+        exec(annotated_class_code, globals(), globals())
+        self.assertTrue('AnnotatedSignalClass' in globals())
+        self.AnnotatedClass = globals()['AnnotatedSignalClass']
+
+    def test_annotations(self):
+        self.assertEqual(signalhelper.get_signal_annotations(self.AnnotatedClass.sig1.func),
+                         (None, (int, float)))
+        self.assertEqual(signalhelper.get_signal_annotations(self.AnnotatedClass.sig2_with_return.func),
+                         (str, (int, float)))
+
+        self.assertEqual(self.AnnotatedClass.sig2_with_return.get_signal_args(),
+                         (GObject.SignalFlags.RUN_LAST, str, (int, float), None, None))
+        self.assertEqual(self.AnnotatedClass.sig2_with_return.arg_types,
+                         (int, float))
+        self.assertEqual(self.AnnotatedClass.sig2_with_return.return_type,
+                         str)
+
+    def test_emit_return(self):
+        obj = self.AnnotatedClass()
+        self.assertEqual(obj.sig2_with_return.emit(1, 2.0),
+                         'test')
+
+
+class TestSignalModuleLevelFunctions(unittest.TestCase):
+    def test_signal_list_ids_with_invalid_type(self):
+        with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
+            GObject.signal_list_ids(GObject.TYPE_INVALID)
+
+    def test_signal_list_ids(self):
+        with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
+            GObject.signal_list_ids(GObject.TYPE_INT)
+
+        ids = GObject.signal_list_ids(C)
+        self.assertEqual(len(ids), 1)
+        # Note canonicalized names
+        self.assertEqual(GObject.signal_name(ids[0]), 'my-signal')
+        # There is no signal 0 in gobject
+        self.assertEqual(GObject.signal_name(0), None)
+
+    def test_signal_lookup_with_invalid_type(self):
+        with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
+            GObject.signal_lookup('NOT_A_SIGNAL_NAME', GObject.TYPE_INVALID)
+
+    def test_signal_lookup(self):
+        ids = GObject.signal_list_ids(C)
+        self.assertEqual(ids[0], GObject.signal_lookup('my_signal', C))
+        self.assertEqual(ids[0], GObject.signal_lookup('my-signal', C))
+
+        with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
+            GObject.signal_lookup('NOT_A_SIGNAL_NAME', GObject.TYPE_INT)
+
+        # Invalid signal names return 0 instead of raising
+        self.assertEqual(GObject.signal_lookup('NOT_A_SIGNAL_NAME', C),
+                         0)
+
+    def test_signal_query(self):
+        my_signal_id, = GObject.signal_list_ids(C)
+
+        # Form is: (id, name, gtype, arg_count, return_type, (arg_type1, ...))
+        my_signal_expected_query_result = [my_signal_id, 'my-signal', C.__gtype__,
+                                           1, GObject.TYPE_NONE, (GObject.TYPE_INT,)]
+        # signal_query(name, type)
+        self.assertEqual(list(GObject.signal_query('my-signal', C)), my_signal_expected_query_result)
+        # signal_query(signal_id)
+        self.assertEqual(list(GObject.signal_query(my_signal_id)), my_signal_expected_query_result)
+        # invalid query returns None instead of raising
+        self.assertEqual(GObject.signal_query(0), None)
+        self.assertEqual(GObject.signal_query('NOT_A_SIGNAL', C),
+                         None)
+
+
+class TestIntrospectedSignals(unittest.TestCase):
+    def test_object_param_signal(self):
+        obj = Regress.TestObj()
+
+        def callback(obj, obj_param):
+            self.assertEqual(obj_param.props.int, 3)
+            self.assertGreater(obj_param.__grefcount__, 1)
+            obj.called = True
+
+        obj.called = False
+        obj.connect('sig-with-obj', callback)
+        obj.emit_sig_with_obj()
+        self.assertTrue(obj.called)
+
+    def test_connect_after(self):
+        obj = Regress.TestObj()
+
+        def callback(obj, obj_param):
+            obj.called = True
+
+        obj.called = False
+        obj.connect_after('sig-with-obj', callback)
+        obj.emit_sig_with_obj()
+        self.assertTrue(obj.called)
+
+    def test_int64_param_from_py(self):
+        obj = Regress.TestObj()
+
+        def callback(obj, i):
+            obj.callback_i = i
+            return i
+
+        obj.callback_i = None
+        obj.connect('sig-with-int64-prop', callback)
+        rv = obj.emit('sig-with-int64-prop', GLib.MAXINT64)
+        self.assertEqual(rv, GLib.MAXINT64)
+        self.assertEqual(obj.callback_i, GLib.MAXINT64)
+
+    def test_uint64_param_from_py(self):
+        obj = Regress.TestObj()
+
+        def callback(obj, i):
+            obj.callback_i = i
+            return i
+
+        obj.callback_i = None
+        obj.connect('sig-with-uint64-prop', callback)
+        rv = obj.emit('sig-with-uint64-prop', GLib.MAXUINT64)
+        self.assertEqual(rv, GLib.MAXUINT64)
+        self.assertEqual(obj.callback_i, GLib.MAXUINT64)
+
+    def test_int64_param_from_c(self):
+        obj = Regress.TestObj()
+
+        def callback(obj, i):
+            obj.callback_i = i
+            return i
+
+        obj.callback_i = None
+
+        obj.connect('sig-with-int64-prop', callback)
+        obj.emit_sig_with_int64()
+        self.assertEqual(obj.callback_i, GLib.MAXINT64)
+
+    def test_uint64_param_from_c(self):
+        obj = Regress.TestObj()
+
+        def callback(obj, i):
+            obj.callback_i = i
+            return i
+
+        obj.callback_i = None
+
+        obj.connect('sig-with-uint64-prop', callback)
+        obj.emit_sig_with_uint64()
+        self.assertEqual(obj.callback_i, GLib.MAXUINT64)
+
+    def test_intarray_ret(self):
+        obj = Regress.TestObj()
+
+        def callback(obj, i):
+            obj.callback_i = i
+            return [i, i + 1]
+
+        obj.callback_i = None
+
+        try:
+            obj.connect('sig-with-intarray-ret', callback)
+        except TypeError as e:
+            # compat with g-i 1.34.x
+            if 'unknown signal' in str(e):
+                return
+            raise
+
+        rv = obj.emit('sig-with-intarray-ret', 42)
+        self.assertEqual(obj.callback_i, 42)
+        self.assertEqual(type(rv), GLib.Array)
+        self.assertEqual(rv.len, 2)
+
+    @unittest.skip('https://bugzilla.gnome.org/show_bug.cgi?id=669496')
+    def test_array_parm(self):
+        obj = Regress.TestObj()
+
+        def callback(obj, arr):
+            obj.callback_arr = arr
+
+        obj.connect('sig-with-array-prop', callback)
+        obj.callback_arr = None
+        self.assertEqual(obj.emit('sig-with-array-prop', [1, 2, GLib.MAXUINT]), None)
+        self.assertEqual(obj.callback_arr, [1, 2, GLib.MAXUINT])
+
+    def test_held_struct_ref(self):
+        held_structs = []
+
+        def callback(obj, struct):
+            # The struct held by Python will become a copy after this callback exits.
+            struct.some_int = 42
+            struct.some_int8 = 42
+            held_structs.append(struct)
+
+        struct = Regress.TestSimpleBoxedA()
+        obj = Regress.TestObj()
+
+        self.assertEqual(struct.some_int, 0)
+        self.assertEqual(struct.some_int8, 0)
+
+        obj.connect('test-with-static-scope-arg', callback)
+        obj.emit('test-with-static-scope-arg', struct)
+
+        # The held struct will be a copy of the modified struct.
+        self.assertEqual(len(held_structs), 1)
+        held_struct = held_structs[0]
+        self.assertEqual(held_struct.some_int, 42)
+        self.assertEqual(held_struct.some_int8, 42)
+
+        # Boxed equality checks pointers by default.
+        self.assertNotEqual(struct, held_struct)
+
+
+class TestIntrospectedSignalsIssue158(unittest.TestCase):
+    """
+    The test for https://gitlab.gnome.org/GNOME/pygobject/issues/158
+    """
+    _obj_sig_names = [sig.get_name() for sig in repo.find_by_name('Regress', 'TestObj').get_signals()]
+
+    def __init__(self, *args):
+        unittest.TestCase.__init__(self, *args)
+        self._gc_thread_stop = False
+
+    def _gc_thread(self):
+        while not self._gc_thread_stop:
+            gc.collect()
+            time.sleep(0.010)
+
+    def _callback(self, *args):
+        pass
+
+    def test_run(self):
+        """
+        Manually trigger GC from a different thread periodicaly
+        while the main thread keeps connecting/disconnecting to/from signals.
+
+        It takes a lot of time to reproduce the issue. It is possible to make it
+        fail reliably by changing the code of pygobject_unwatch_closure slightly from:
+          PyGObjectData *inst_data = data;
+          inst_data->closures = g_slist_remove (inst_data->closures, closure);
+        to
+          PyGObjectData *inst_data = data;
+          GSList *tmp = g_slist_remove (inst_data->closures, closure);
+          g_usleep(G_USEC_PER_SEC/10);
+          inst_data->closures = tmp;
+        """
+        obj = Regress.TestObj()
+        gc_thread = threading.Thread(target=self._gc_thread)
+        gc_thread.start()
+
+        for _ in range(8):
+            handlers = [obj.connect(sig, self._callback) for sig in self._obj_sig_names]
+            time.sleep(0.010)
+            while len(handlers) > 0:
+                obj.disconnect(handlers.pop())
+
+        self._gc_thread_stop = True
+        gc_thread.join()
+
+
+class _ConnectObjectTestBase(object):
+    # Notes:
+    #  - self.Object is overridden in sub-classes.
+    #  - Numeric suffixes indicate the number of user data args passed in.
+    Object = None
+    SwapObject = None
+
+    def run_connect_test(self, emit_args, user_data, flags=0):
+        obj = self.Object()
+        callback_args = []
+        swap_obj = self.SwapObject()
+
+        def callback(*args):
+            callback_args.append(args)
+            return 0
+
+        if flags & GObject.ConnectFlags.AFTER:
+            connect_func = obj.connect_object_after
+        else:
+            connect_func = obj.connect_object
+
+        with capture_gi_deprecation_warnings():
+            connect_func('sig-with-int64-prop', callback, swap_obj, *user_data)
+        obj.emit('sig-with-int64-prop', *emit_args)
+        self.assertEqual(len(callback_args), 1)
+        return callback_args[0]
+
+    def test_0(self):
+        obj, value = self.run_connect_test([GLib.MAXINT64], user_data=[])
+        self.assertIsInstance(obj, self.SwapObject)
+        self.assertEqual(value, GLib.MAXINT64)
+
+    def test_1(self):
+        obj, value, data = self.run_connect_test([GLib.MAXINT64],
+                                                 user_data=['mydata'])
+        self.assertIsInstance(obj, self.SwapObject)
+        self.assertEqual(value, GLib.MAXINT64)
+        self.assertEqual(data, 'mydata')
+
+    def test_2(self):
+        obj, value, data1, data2 = self.run_connect_test([GLib.MAXINT64],
+                                                         user_data=['mydata1', 'mydata2'])
+        self.assertIsInstance(obj, self.SwapObject)
+        self.assertEqual(value, GLib.MAXINT64)
+        self.assertEqual(data1, 'mydata1')
+        self.assertEqual(data2, 'mydata2')
+
+    def test_after_0(self):
+        obj, value = self.run_connect_test([GLib.MAXINT64],
+                                           user_data=[],
+                                           flags=GObject.ConnectFlags.AFTER)
+        self.assertIsInstance(obj, self.SwapObject)
+        self.assertEqual(value, GLib.MAXINT64)
+
+    def test_after_1(self):
+        obj, value, data = self.run_connect_test([GLib.MAXINT64],
+                                                 user_data=['mydata'],
+                                                 flags=GObject.ConnectFlags.AFTER)
+        self.assertIsInstance(obj, self.SwapObject)
+        self.assertEqual(value, GLib.MAXINT64)
+        self.assertEqual(data, 'mydata')
+
+    def test_after_2(self):
+        obj, value, data1, data2 = self.run_connect_test([GLib.MAXINT64],
+                                                         user_data=['mydata1', 'mydata2'],
+                                                         flags=GObject.ConnectFlags.AFTER)
+        self.assertIsInstance(obj, self.SwapObject)
+        self.assertEqual(value, GLib.MAXINT64)
+        self.assertEqual(data1, 'mydata1')
+        self.assertEqual(data2, 'mydata2')
+
+
+class TestConnectGObjectNonIntrospected(unittest.TestCase, _ConnectObjectTestBase):
+    # This tests connect_object with non-introspected signals
+    # (created in Python in this case).
+    class Object(GObject.Object):
+        test = GObject.Signal()
+        sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64,
+                                             arg_types=[GObject.TYPE_INT64],
+                                             flags=GObject.SignalFlags.RUN_LAST)
+
+    # Object passed for swapping is GObject based.
+    class SwapObject(GObject.Object):
+        pass
+
+
+class TestConnectGObjectIntrospected(unittest.TestCase, _ConnectObjectTestBase):
+    # This tests connect_object with introspected signals brought in from Regress.
+    Object = Regress.TestObj
+
+    # Object passed for swapping is GObject based.
+    class SwapObject(GObject.Object):
+        pass
+
+
+class TestConnectPyObjectNonIntrospected(unittest.TestCase, _ConnectObjectTestBase):
+    # This tests connect_object with non-introspected signals
+    # (created in Python in this case).
+    class Object(GObject.Object):
+        test = GObject.Signal()
+        sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64,
+                                             arg_types=[GObject.TYPE_INT64],
+                                             flags=GObject.SignalFlags.RUN_LAST)
+
+    # Object passed for swapping is pure Python
+    SwapObject = object
+
+
+class TestConnectPyObjectIntrospected(unittest.TestCase, _ConnectObjectTestBase):
+    # This tests connect_object with introspected signals brought in from Regress.
+    Object = Regress.TestObj
+
+    # Object passed for swapping is pure Python
+    SwapObject = object
+
+
+class _RefCountTestBase(object):
+    # NOTE: ref counts are always one more than expected because the getrefcount()
+    # function adds a ref for the input argument.
+
+    # Sub-classes set this
+    Object = None
+
+    class PyData(object):
+        pass
+
+    @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount")
+    def test_callback_ref_count_del(self):
+        def callback(obj, value):
+            return value // 2
+
+        callback_ref = weakref.ref(callback)
+        self.assertEqual(sys.getrefcount(callback), 2)
+
+        obj = self.Object()
+        obj.connect('sig-with-int64-prop', callback)
+        self.assertEqual(sys.getrefcount(callback), 3)
+
+        del callback
+        self.assertEqual(sys.getrefcount(callback_ref()), 2)
+
+        res = obj.emit('sig-with-int64-prop', 42)
+        self.assertEqual(res, 21)
+        self.assertEqual(sys.getrefcount(callback_ref), 2)
+
+        del obj
+        self.assertIsNone(callback_ref())
+
+    @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount")
+    def test_callback_ref_count_disconnect(self):
+        def callback(obj, value):
+            return value // 2
+
+        callback_ref = weakref.ref(callback)
+        self.assertEqual(sys.getrefcount(callback), 2)
+
+        obj = self.Object()
+        handler_id = obj.connect('sig-with-int64-prop', callback)
+        self.assertEqual(sys.getrefcount(callback), 3)
+
+        del callback
+        self.assertEqual(sys.getrefcount(callback_ref()), 2)
+
+        res = obj.emit('sig-with-int64-prop', 42)
+        self.assertEqual(res, 21)
+        self.assertEqual(sys.getrefcount(callback_ref), 2)
+
+        obj.disconnect(handler_id)
+        self.assertIsNone(callback_ref())
+
+    @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount")
+    def test_callback_ref_count_disconnect_by_func(self):
+        def callback(obj, value):
+            return value // 2
+
+        callback_ref = weakref.ref(callback)
+        self.assertEqual(sys.getrefcount(callback), 2)
+
+        obj = self.Object()
+        obj.connect('sig-with-int64-prop', callback)
+        self.assertEqual(sys.getrefcount(callback), 3)
+
+        del callback
+        self.assertEqual(sys.getrefcount(callback_ref()), 2)
+
+        res = obj.emit('sig-with-int64-prop', 42)
+        self.assertEqual(res, 21)
+        self.assertEqual(sys.getrefcount(callback_ref), 2)
+
+        obj.disconnect_by_func(callback_ref())
+        self.assertIsNone(callback_ref())
+
+    @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount")
+    def test_user_data_ref_count(self):
+        def callback(obj, value, data):
+            return value // 2
+
+        data = self.PyData()
+        data_ref = weakref.ref(data)
+        self.assertEqual(sys.getrefcount(data), 2)
+
+        obj = self.Object()
+        obj.connect('sig-with-int64-prop', callback, data)
+        self.assertEqual(sys.getrefcount(data), 3)
+
+        del data
+        self.assertEqual(sys.getrefcount(data_ref()), 2)
+
+        res = obj.emit('sig-with-int64-prop', 42)
+        self.assertEqual(res, 21)
+        self.assertEqual(sys.getrefcount(data_ref()), 2)
+
+        del obj
+        self.assertIsNone(data_ref())
+
+    @unittest.skipUnless(hasattr(sys, "getrefcount"), "no sys.getrefcount")
+    @unittest.expectedFailure  # https://bugzilla.gnome.org/show_bug.cgi?id=688064
+    def test_object_ref_count(self):
+        # connect_object() should only weakly reference the object passed in
+        # and auto-disconnect the signal when the object is destroyed.
+        def callback(data, value):
+            return value // 2
+
+        data = GObject.Object()
+        data_ref = weakref.ref(data)
+        self.assertEqual(sys.getrefcount(data), 2)
+
+        obj = self.Object()
+        handler_id = obj.connect_object('sig-with-int64-prop', callback, data)
+        self.assertEqual(sys.getrefcount(data), 2)
+
+        res = obj.emit('sig-with-int64-prop', 42)
+        self.assertEqual(res, 21)
+        self.assertEqual(sys.getrefcount(data), 2)
+
+        del data
+
+        self.assertIsNone(data_ref())
+        self.assertFalse(obj.handler_is_connected(handler_id))
+
+
+class TestRefCountsNonIntrospected(unittest.TestCase, _RefCountTestBase):
+    class Object(GObject.Object):
+        sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64,
+                                             arg_types=[GObject.TYPE_INT64],
+                                             flags=GObject.SignalFlags.RUN_LAST)
+
+
+class TestRefCountsIntrospected(unittest.TestCase, _RefCountTestBase):
+    Object = Regress.TestObj
+
+
+class TestClosureRefCycle(unittest.TestCase):
+
+    def test_closure_ref_cycle_unreachable(self):
+        # https://bugzilla.gnome.org/show_bug.cgi?id=731501
+
+        called = []
+
+        def on_add(store, *args):
+            called.append(store)
+
+        store = Gio.ListStore()
+        store.connect_object('items-changed', on_add, store)
+
+        # Remove all Python references to the object and keep it alive
+        # on the C level.
+        x = Gio.FileInfo()
+        x.set_attribute_object('store', store)
+        del store
+        gc.collect()
+
+        # get it back and trigger the signal
+        x.get_attribute_object('store').append(Gio.FileInfo())
+
+        self.assertEqual(len(called), 1)
+        self.assertTrue(called[0].__grefcount__ > 0)
diff --git a/tests/test_source.py b/tests/test_source.py
new file mode 100644 (file)
index 0000000..1a600fd
--- /dev/null
@@ -0,0 +1,422 @@
+# -*- Mode: Python -*-
+
+from __future__ import absolute_import
+
+import sys
+import gc
+import unittest
+import warnings
+
+from gi.repository import GLib
+from gi import PyGIDeprecationWarning
+
+from .helper import capture_glib_warnings
+
+
+class Idle(GLib.Idle):
+    def __init__(self, loop):
+        GLib.Idle.__init__(self)
+        self.count = 0
+        self.set_callback(self.callback, loop)
+
+    def callback(self, loop):
+        self.count += 1
+        return True
+
+
+class MySource(GLib.Source):
+    def __init__(self):
+        GLib.Source.__init__(self)
+
+    def prepare(self):
+        return True, 0
+
+    def check(self):
+        return True
+
+    def dispatch(self, callback, args):
+        return callback(*args)
+
+
+class TestSource(unittest.TestCase):
+    def timeout_callback(self, loop):
+        loop.quit()
+
+    def my_callback(self, loop):
+        self.pos += 1
+        return True
+
+    def setup_timeout(self, loop):
+        timeout = GLib.Timeout(500)
+        timeout.set_callback(self.timeout_callback, loop)
+        timeout.attach()
+
+    def test_sources(self):
+        loop = GLib.MainLoop()
+
+        self.setup_timeout(loop)
+
+        idle = Idle(loop)
+        self.assertEqual(idle.get_context(), None)
+        idle.attach()
+        self.assertEqual(idle.get_context(), GLib.main_context_default())
+
+        self.pos = 0
+
+        m = MySource()
+        self.assertEqual(m.get_context(), None)
+        m.set_callback(self.my_callback, loop)
+        m.attach()
+        self.assertEqual(m.get_context(), GLib.main_context_default())
+
+        loop.run()
+
+        m.destroy()
+        idle.destroy()
+
+        self.assertGreater(self.pos, 0)
+        self.assertGreaterEqual(idle.count, 0)
+        self.assertTrue(m.is_destroyed())
+        self.assertTrue(idle.is_destroyed())
+
+    def test_source_prepare(self):
+        # this test may not terminate if prepare() is wrapped incorrectly
+        dispatched = [False]
+        loop = GLib.MainLoop()
+
+        class CustomTimeout(GLib.Source):
+            def prepare(self):
+                return (False, 10)
+
+            def check(self):
+                return True
+
+            def dispatch(self, callback, args):
+                dispatched[0] = True
+
+                loop.quit()
+
+                return False
+
+        source = CustomTimeout()
+
+        source.attach()
+        source.set_callback(dir)
+
+        loop.run()
+
+        assert dispatched[0]
+
+    def test_is_destroyed_simple(self):
+        s = GLib.Source()
+
+        self.assertFalse(s.is_destroyed())
+        s.destroy()
+        self.assertTrue(s.is_destroyed())
+
+        c = GLib.MainContext()
+        s = GLib.Source()
+        s.attach(c)
+        self.assertFalse(s.is_destroyed())
+        s.destroy()
+        self.assertTrue(s.is_destroyed())
+
+    def test_is_destroyed_context(self):
+        def f():
+            c = GLib.MainContext()
+            s = GLib.Source()
+            s.attach(c)
+            return s
+
+        s = f()
+        gc.collect()
+        gc.collect()
+        self.assertTrue(s.is_destroyed())
+
+    def test_remove(self):
+        s = GLib.idle_add(dir)
+        self.assertEqual(GLib.source_remove(s), True)
+
+        # Removing sources not found cause critical
+        with capture_glib_warnings(allow_criticals=True):
+
+            # s is now removed, should fail now
+            self.assertEqual(GLib.source_remove(s), False)
+
+            # accepts large source IDs (they are unsigned)
+            self.assertEqual(GLib.source_remove(GLib.MAXINT32), False)
+            self.assertEqual(GLib.source_remove(GLib.MAXINT32 + 1), False)
+            self.assertEqual(GLib.source_remove(GLib.MAXUINT32), False)
+
+    def test_recurse_property(self):
+        s = GLib.Idle()
+        self.assertTrue(s.can_recurse in [False, True])
+        s.can_recurse = False
+        self.assertFalse(s.can_recurse)
+
+    def test_priority(self):
+        s = GLib.Idle()
+        self.assertEqual(s.priority, GLib.PRIORITY_DEFAULT_IDLE)
+        s.priority = GLib.PRIORITY_HIGH
+        self.assertEqual(s.priority, GLib.PRIORITY_HIGH)
+
+        s = GLib.Idle(GLib.PRIORITY_LOW)
+        self.assertEqual(s.priority, GLib.PRIORITY_LOW)
+
+        s = GLib.Timeout(1, GLib.PRIORITY_LOW)
+        self.assertEqual(s.priority, GLib.PRIORITY_LOW)
+
+        s = GLib.Source()
+        self.assertEqual(s.priority, GLib.PRIORITY_DEFAULT)
+
+    def test_get_current_time(self):
+        # Note, deprecated API
+        s = GLib.Idle()
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter('always')
+            time = s.get_current_time()
+            self.assertTrue(issubclass(w[0].category, PyGIDeprecationWarning))
+
+        self.assertTrue(isinstance(time, float))
+        # plausibility check, and check magnitude of result
+        self.assertGreater(time, 1300000000.0)
+        self.assertLess(time, 2000000000.0)
+
+    def test_add_remove_poll(self):
+        # FIXME: very shallow test, only verifies the API signature
+        pollfd = GLib.PollFD(99, GLib.IOCondition.IN | GLib.IOCondition.HUP)
+        self.assertEqual(pollfd.fd, 99)
+        source = GLib.Source()
+        source.add_poll(pollfd)
+        source.remove_poll(pollfd)
+
+    def test_out_of_scope_before_dispatch(self):
+        # https://bugzilla.gnome.org/show_bug.cgi?id=504337
+        GLib.Timeout(20)
+        GLib.Idle()
+
+    @unittest.skipIf(sys.platform == "darwin", "hangs")
+    def test_finalize(self):
+        self.dispatched = False
+        self.finalized = False
+
+        class S(GLib.Source):
+            def prepare(s):
+                return (True, 1)
+
+            def dispatch(s, callback, args):
+                self.dispatched = True
+                return False
+
+            def finalize(s):
+                self.finalized = True
+
+        source = S()
+        source.attach()
+        self.assertFalse(self.finalized)
+        self.assertFalse(source.is_destroyed())
+
+        while source.get_context().iteration(False):
+            pass
+
+        source.destroy()
+        self.assertTrue(self.dispatched)
+        self.assertFalse(self.finalized)
+        self.assertTrue(source.is_destroyed())
+        del source
+        gc.collect()
+        gc.collect()
+        self.assertTrue(self.finalized)
+
+    def test_python_unref_with_active_source(self):
+        # Tests a Python derived Source which is free'd in the context of
+        # Python, but which was attached to a MainContext (via source.attach())
+        self.dispatched = False
+        self.finalized = False
+
+        class S(GLib.Source):
+            def prepare(s):
+                return (True, 1)
+
+            def check(s):
+                pass
+
+            def dispatch(s, callback, args):
+                self.dispatched = True
+                return False
+
+            def finalize(s):
+                self.finalized = True
+
+        context = GLib.MainContext.new()
+        source = S()
+        id_ = source.attach(context)
+        self.assertFalse(self.finalized)
+        self.assertFalse(source.is_destroyed())
+
+        # Delete the source from Python, it should detach
+        del source
+        gc.collect()
+        gc.collect()
+
+        while context.iteration(may_block=False):
+            pass
+
+        assert self.finalized
+        assert not self.dispatched
+        assert context.find_source_by_id(id_) is None
+
+    def test_extra_init_args(self):
+        class SourceWithInitArgs(GLib.Source):
+            def __init__(self, arg, kwarg=None):
+                super(SourceWithInitArgs, self).__init__()
+                self.arg = arg
+                self.kwarg = kwarg
+
+        source = SourceWithInitArgs(1, kwarg=2)
+        self.assertEqual(source.arg, 1)
+        self.assertEqual(source.kwarg, 2)
+
+
+@unittest.skipIf(sys.platform == "darwin", "hangs")
+class TestUserData(unittest.TestCase):
+    def test_idle_no_data(self):
+        ml = GLib.MainLoop()
+
+        def cb():
+            ml.quit()
+        id = GLib.idle_add(cb)
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_DEFAULT_IDLE)
+        ml.run()
+
+    def test_timeout_no_data(self):
+        ml = GLib.MainLoop()
+
+        def cb():
+            ml.quit()
+        id = GLib.timeout_add(1, cb)
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_DEFAULT)
+        ml.run()
+
+    def test_idle_data(self):
+        ml = GLib.MainLoop()
+
+        def cb(data):
+            data['called'] = True
+            ml.quit()
+        data = {}
+        id = GLib.idle_add(cb, data)
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_DEFAULT_IDLE)
+        ml.run()
+        self.assertTrue(data['called'])
+
+    def test_idle_multidata(self):
+        ml = GLib.MainLoop()
+
+        def cb(data, data2):
+            data['called'] = True
+            data['data2'] = data2
+            ml.quit()
+        data = {}
+        id = GLib.idle_add(cb, data, 'hello')
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_DEFAULT_IDLE)
+        ml.run()
+        self.assertTrue(data['called'])
+        self.assertEqual(data['data2'], 'hello')
+
+    def test_timeout_data(self):
+        ml = GLib.MainLoop()
+
+        def cb(data):
+            data['called'] = True
+            ml.quit()
+        data = {}
+        id = GLib.timeout_add(1, cb, data)
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_DEFAULT)
+        ml.run()
+        self.assertTrue(data['called'])
+
+    def test_timeout_multidata(self):
+        ml = GLib.MainLoop()
+
+        def cb(data, data2):
+            data['called'] = True
+            data['data2'] = data2
+            ml.quit()
+        data = {}
+        id = GLib.timeout_add(1, cb, data, 'hello')
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_DEFAULT)
+        ml.run()
+        self.assertTrue(data['called'])
+        self.assertEqual(data['data2'], 'hello')
+
+    def test_idle_no_data_priority(self):
+        ml = GLib.MainLoop()
+
+        def cb():
+            ml.quit()
+        id = GLib.idle_add(cb, priority=GLib.PRIORITY_HIGH)
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+        ml.run()
+
+    def test_timeout_no_data_priority(self):
+        ml = GLib.MainLoop()
+
+        def cb():
+            ml.quit()
+        id = GLib.timeout_add(1, cb, priority=GLib.PRIORITY_HIGH)
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+        ml.run()
+
+    def test_idle_data_priority(self):
+        ml = GLib.MainLoop()
+
+        def cb(data):
+            data['called'] = True
+            ml.quit()
+        data = {}
+        id = GLib.idle_add(cb, data, priority=GLib.PRIORITY_HIGH)
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+        ml.run()
+        self.assertTrue(data['called'])
+
+    def test_timeout_data_priority(self):
+        ml = GLib.MainLoop()
+
+        def cb(data):
+            data['called'] = True
+            ml.quit()
+        data = {}
+        id = GLib.timeout_add(1, cb, data, priority=GLib.PRIORITY_HIGH)
+        self.assertEqual(ml.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+        ml.run()
+        self.assertTrue(data['called'])
+
+    def cb_no_data(self):
+        self.loop.quit()
+
+    def test_idle_method_callback_no_data(self):
+        self.loop = GLib.MainLoop()
+        GLib.idle_add(self.cb_no_data)
+        self.loop.run()
+
+    def cb_with_data(self, data):
+        data['called'] = True
+        self.loop.quit()
+
+    def test_idle_method_callback_with_data(self):
+        self.loop = GLib.MainLoop()
+        data = {}
+        GLib.idle_add(self.cb_with_data, data)
+        self.loop.run()
+        self.assertTrue(data['called'])
diff --git a/tests/test_subprocess.py b/tests/test_subprocess.py
new file mode 100644 (file)
index 0000000..3ffdf93
--- /dev/null
@@ -0,0 +1,159 @@
+# -*- Mode: Python -*-
+
+from __future__ import absolute_import
+
+import sys
+import os
+import unittest
+import warnings
+
+from gi.repository import GLib
+from gi import PyGIDeprecationWarning
+
+
+@unittest.skipIf(os.name == "nt", "not on Windows")
+class TestProcess(unittest.TestCase):
+
+    def test_deprecated_child_watch_no_data(self):
+        cb = lambda pid, status: None
+        pid = object()
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter('always')
+            res = GLib._child_watch_add_get_args(pid, cb)
+            self.assertTrue(issubclass(w[0].category, PyGIDeprecationWarning))
+
+        self.assertEqual(len(res), 4)
+        self.assertEqual(res[0], GLib.PRIORITY_DEFAULT)
+        self.assertEqual(res[1], pid)
+        self.assertTrue(callable(cb))
+        self.assertSequenceEqual(res[3], [])
+
+    def test_deprecated_child_watch_data_priority(self):
+        cb = lambda pid, status: None
+        pid = object()
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter('always')
+            res = GLib._child_watch_add_get_args(pid, cb, 12345, GLib.PRIORITY_HIGH)
+            self.assertTrue(issubclass(w[0].category, PyGIDeprecationWarning))
+
+        self.assertEqual(len(res), 4)
+        self.assertEqual(res[0], GLib.PRIORITY_HIGH)
+        self.assertEqual(res[1], pid)
+        self.assertEqual(res[2], cb)
+        self.assertSequenceEqual(res[3], [12345])
+
+    def test_deprecated_child_watch_data_priority_kwargs(self):
+        cb = lambda pid, status: None
+        pid = object()
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter('always')
+            res = GLib._child_watch_add_get_args(pid, cb, priority=GLib.PRIORITY_HIGH, data=12345)
+            self.assertTrue(issubclass(w[0].category, PyGIDeprecationWarning))
+
+        self.assertEqual(len(res), 4)
+        self.assertEqual(res[0], GLib.PRIORITY_HIGH)
+        self.assertEqual(res[1], pid)
+        self.assertEqual(res[2], cb)
+        self.assertSequenceEqual(res[3], [12345])
+
+    @unittest.expectedFailure  # using keyword args is fully supported by PyGObject machinery
+    def test_child_watch_all_kwargs(self):
+        cb = lambda pid, status: None
+        pid = object()
+
+        res = GLib._child_watch_add_get_args(priority=GLib.PRIORITY_HIGH, pid=pid, function=cb, data=12345)
+        self.assertEqual(len(res), 4)
+        self.assertEqual(res[0], GLib.PRIORITY_HIGH)
+        self.assertEqual(res[1], pid)
+        self.assertEqual(res[2], cb)
+        self.assertSequenceEqual(res[3], [12345])
+
+    def test_child_watch_no_data(self):
+        def cb(pid, status):
+            self.status = status
+            self.loop.quit()
+
+        self.status = None
+        self.loop = GLib.MainLoop()
+        argv = [sys.executable, '-c', 'import sys']
+        pid, stdin, stdout, stderr = GLib.spawn_async(
+            argv, flags=GLib.SpawnFlags.DO_NOT_REAP_CHILD)
+        pid.close()
+        id = GLib.child_watch_add(GLib.PRIORITY_HIGH, pid, cb)
+        self.assertEqual(self.loop.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+        self.loop.run()
+        self.assertEqual(self.status, 0)
+
+    def test_child_watch_with_data(self):
+        def cb(pid, status, data):
+            self.status = status
+            self.data = data
+            self.loop.quit()
+
+        self.data = None
+        self.status = None
+        self.loop = GLib.MainLoop()
+        argv = [sys.executable, '-c', 'import sys']
+        pid, stdin, stdout, stderr = GLib.spawn_async(
+            argv, flags=GLib.SpawnFlags.DO_NOT_REAP_CHILD)
+        self.assertEqual(stdin, None)
+        self.assertEqual(stdout, None)
+        self.assertEqual(stderr, None)
+        self.assertNotEqual(pid, 0)
+        pid.close()
+        id = GLib.child_watch_add(GLib.PRIORITY_HIGH, pid, cb, 12345)
+        self.assertEqual(self.loop.get_context().find_source_by_id(id).priority,
+                         GLib.PRIORITY_HIGH)
+        self.loop.run()
+        self.assertEqual(self.data, 12345)
+        self.assertEqual(self.status, 0)
+
+    def test_spawn_async_fds(self):
+        pid, stdin, stdout, stderr = GLib.spawn_async(
+            ['cat'], flags=GLib.SpawnFlags.SEARCH_PATH, standard_input=True,
+            standard_output=True, standard_error=True)
+        os.write(stdin, b'hello world!\n')
+        os.close(stdin)
+        out = os.read(stdout, 50)
+        os.close(stdout)
+        err = os.read(stderr, 50)
+        os.close(stderr)
+        pid.close()
+        self.assertEqual(out, b'hello world!\n')
+        self.assertEqual(err, b'')
+
+    def test_spawn_async_with_pipes(self):
+        res, pid, stdin, stdout, stderr = GLib.spawn_async_with_pipes(
+            working_directory=None,
+            argv=['cat'],
+            envp=None,
+            flags=GLib.SpawnFlags.SEARCH_PATH)
+
+        os.write(stdin, b'hello world!\n')
+        os.close(stdin)
+        out = os.read(stdout, 50)
+        os.close(stdout)
+        err = os.read(stderr, 50)
+        os.close(stderr)
+        GLib.spawn_close_pid(pid)
+        self.assertEqual(out, b'hello world!\n')
+        self.assertEqual(err, b'')
+
+    def test_spawn_async_envp(self):
+        pid, stdin, stdout, stderr = GLib.spawn_async(
+            ['sh', '-c', 'echo $TEST_VAR'], ['TEST_VAR=moo!'],
+            flags=GLib.SpawnFlags.SEARCH_PATH, standard_output=True)
+        self.assertEqual(stdin, None)
+        self.assertEqual(stderr, None)
+        out = os.read(stdout, 50)
+        os.close(stdout)
+        pid.close()
+        self.assertEqual(out, b'moo!\n')
+
+    def test_backwards_compat_flags(self):
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', PyGIDeprecationWarning)
+
+            self.assertEqual(GLib.SpawnFlags.DO_NOT_REAP_CHILD,
+                             GLib.SPAWN_DO_NOT_REAP_CHILD)
diff --git a/tests/test_thread.py b/tests/test_thread.py
new file mode 100644 (file)
index 0000000..45ebd0b
--- /dev/null
@@ -0,0 +1,36 @@
+# -*- Mode: Python -*-
+
+from __future__ import absolute_import
+
+import unittest
+
+from gi.repository import GLib
+
+import testhelper
+
+
+class TestThread(unittest.TestCase):
+    def setUp(self):
+        self.main = GLib.MainLoop()
+        self.called = False
+
+    def from_thread_cb(self, test, enum):
+        assert test == self.obj
+        assert int(enum) == 0
+        assert type(enum) != int
+        self.called = True
+        GLib.idle_add(self.timeout_cb)
+
+    def idle_cb(self):
+        self.obj = testhelper.get_test_thread()
+        self.obj.connect('from-thread', self.from_thread_cb)
+        self.obj.emit('emit-signal')
+
+    def test_extension_module(self):
+        GLib.idle_add(self.idle_cb)
+        GLib.timeout_add(2000, self.timeout_cb)
+        self.main.run()
+        self.assertTrue(self.called)
+
+    def timeout_cb(self):
+        self.main.quit()
diff --git a/tests/test_typeclass.py b/tests/test_typeclass.py
new file mode 100644 (file)
index 0000000..b584fdd
--- /dev/null
@@ -0,0 +1,78 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# test_typeclass.py: Tests for GTypeClass related methods and marshalling.
+#
+# Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from __future__ import absolute_import
+
+import unittest
+
+from gi.repository import GObject
+from gi.repository import GIMarshallingTests
+
+
+class TestCoercion(unittest.TestCase):
+    def test_coerce_from_class(self):
+        prop = GObject.ObjectClass.find_property(GIMarshallingTests.PropertiesObject,
+                                                 'some-int')
+
+        self.assertIsInstance(prop, GObject.GParamSpec)
+        self.assertEqual(prop.name, 'some-int')
+        self.assertEqual(prop.value_type, GObject.TYPE_INT)
+        self.assertEqual(prop.owner_type,
+                         GIMarshallingTests.PropertiesObject.__gtype__)
+
+    def test_coerce_from_gtype(self):
+        gtype = GIMarshallingTests.PropertiesObject.__gtype__
+        prop = GObject.ObjectClass.find_property(gtype, 'some-int')
+
+        self.assertIsInstance(prop, GObject.GParamSpec)
+        self.assertEqual(prop.name, 'some-int')
+        self.assertEqual(prop.value_type, GObject.TYPE_INT)
+        self.assertEqual(prop.owner_type, gtype)
+
+    def test_coerce_from_instance(self):
+        obj = GIMarshallingTests.PropertiesObject()
+        prop = GObject.ObjectClass.find_property(obj, 'some-int')
+
+        self.assertIsInstance(prop, GObject.GParamSpec)
+        self.assertEqual(prop.name, 'some-int')
+        self.assertEqual(prop.value_type, GObject.TYPE_INT)
+        self.assertEqual(prop.owner_type, obj.__gtype__)
+
+    def test_marshalling_error(self):
+        with self.assertRaises(TypeError):
+            GObject.ObjectClass.find_property(object, 'some-int')
+
+        with self.assertRaises(TypeError):
+            GObject.ObjectClass.find_property(42, 'some-int')
+
+
+class TestTypeClassMethodsMovedToClass(unittest.TestCase):
+    def test_list_child_properties(self):
+        pspecs = GIMarshallingTests.PropertiesObject.list_properties()
+        pnames = [pspec.name for pspec in pspecs]
+        self.assertTrue('some-int' in pnames)
+        self.assertTrue('some-float' in pnames)
+        self.assertTrue('some-char' in pnames)
+
+    def test_find_child_property(self):
+        pspec = GIMarshallingTests.PropertiesObject.find_property('some-int')
+        self.assertEqual(pspec.name, 'some-int')
diff --git a/tests/test_unknown.py b/tests/test_unknown.py
new file mode 100644 (file)
index 0000000..1ed1fb8
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- Mode: Python -*-
+
+from __future__ import absolute_import
+
+import unittest
+
+from gi.repository import GObject
+
+import testhelper
+
+
+TestInterface = GObject.GType.from_name('TestInterface')
+
+
+class TestUnknown(unittest.TestCase):
+    def test_unknown_interface(self):
+        obj = testhelper.get_unknown()
+        TestUnknownGType = GObject.GType.from_name('TestUnknown')
+        TestUnknown = GObject.new(TestUnknownGType).__class__
+        assert isinstance(obj, testhelper.Interface)
+        assert isinstance(obj, TestUnknown)
+
+    def test_property(self):
+        obj = testhelper.get_unknown()
+        self.assertEqual(obj.get_property('some-property'), None)
+        obj.set_property('some-property', 'foo')
+
+    def test_unknown_property(self):
+        obj = testhelper.get_unknown()
+        self.assertRaises(TypeError, obj.get_property, 'unknown')
+        self.assertRaises(TypeError, obj.set_property, 'unknown', '1')
diff --git a/tests/testhelpermodule.c b/tests/testhelpermodule.c
new file mode 100644 (file)
index 0000000..e26a004
--- /dev/null
@@ -0,0 +1,779 @@
+#include "pygobject.h"
+#include <gobject/gmarshal.h>
+
+#include "test-thread.h"
+#include "test-unknown.h"
+#include "test-floating.h"
+
+#include "pygi-python-compat.h"
+
+static PyObject * _wrap_TestInterface__do_iface_method(PyObject *cls,
+                                                      PyObject *args,
+                                                      PyObject *kwargs);
+
+static GType
+test_type_get_type(void)
+{
+    static GType gtype = 0;
+    GType parent_type;
+    
+    if (gtype == 0)
+    {
+        GTypeInfo *type_info;
+        GTypeQuery query;
+       
+       parent_type = g_type_from_name("PyGObject");
+       if (parent_type == 0)
+            g_error("could not get PyGObject from testmodule");
+
+       type_info = (GTypeInfo *)g_new0(GTypeInfo, 1);
+       
+        g_type_query(parent_type, &query);
+        type_info->class_size = (guint16)query.class_size;
+        type_info->instance_size = (guint16)query.instance_size;
+       
+        gtype = g_type_register_static(parent_type,
+                                      "TestType", type_info, 0);
+       if (!gtype)
+            g_error("Could not register TestType");
+    }
+    
+    return gtype;
+}
+
+#define TYPE_TEST (test_type_get_type())
+
+static PyObject *
+_wrap_get_test_thread (PyObject * self)
+{
+  GObject *obj;
+
+  test_thread_get_type();
+  g_assert (g_type_is_a (TEST_TYPE_THREAD, G_TYPE_OBJECT));
+  obj = g_object_new (TEST_TYPE_THREAD, NULL);
+  g_assert (obj != NULL);
+  
+  return pygobject_new(obj);
+}
+
+static PyObject *
+_wrap_get_unknown (PyObject * self)
+{
+  GObject *obj;
+  obj = g_object_new (TEST_TYPE_UNKNOWN, NULL);
+  return pygobject_new(obj);
+}
+
+static PyObject *
+_wrap_create_test_type (PyObject * self)
+{
+    GObject *obj;
+    PyObject *rv;
+    obj = g_object_new(TYPE_TEST, NULL);
+    rv = pygobject_new(obj);
+    g_object_unref(obj);
+    return rv;
+}
+
+static PyObject *
+_wrap_test_g_object_new (PyObject * self)
+{
+    GObject *obj;
+    PyObject *rv;
+
+    obj = g_object_new(g_type_from_name("PyGObject"), NULL);
+    rv = PYGLIB_PyLong_FromLong(obj->ref_count); /* should be == 2 at this point */
+    g_object_unref(obj);
+    return rv;
+}
+
+/* TestUnknown */
+static PyObject *
+_wrap_test_interface_iface_method(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { NULL };
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs,":", kwlist))
+        return NULL;
+    
+    test_interface_iface_method(TEST_INTERFACE(self->obj));
+    
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static const PyMethodDef _PyTestInterface_methods[] = {
+    { "iface_method", (PyCFunction)_wrap_test_interface_iface_method, METH_VARARGS|METH_KEYWORDS,
+      NULL },
+    { "do_iface_method", (PyCFunction)_wrap_TestInterface__do_iface_method, METH_VARARGS|METH_KEYWORDS|METH_CLASS,
+      NULL },
+    { NULL, NULL, 0, NULL }
+};
+
+/* TestInterface */
+PYGLIB_DEFINE_TYPE("test.Interface", PyTestInterface_Type, PyObject);
+
+static PyObject *
+_wrap_TestInterface__do_iface_method(PyObject *cls, PyObject *args, PyObject *kwargs)
+{
+  TestInterfaceIface *iface;
+  static char *kwlist[] = { "self", NULL };
+  PyGObject *self;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwargs,"O!:TestInterface.iface_method", kwlist, &PyTestInterface_Type, &self))
+    return NULL;
+  
+  iface = g_type_interface_peek(g_type_class_peek(pyg_type_from_object(cls)),
+                               TEST_TYPE_INTERFACE);
+  if (iface->iface_method)
+    iface->iface_method(TEST_INTERFACE(self->obj));
+  else {
+    PyErr_SetString(PyExc_NotImplementedError,
+                   "interface method TestInterface.iface_method not implemented");
+    return NULL;
+  }
+  Py_INCREF(Py_None);
+  return Py_None;
+}
+
+PYGLIB_DEFINE_TYPE("testhelper.Unknown", PyTestUnknown_Type, PyGObject);
+
+static void
+_wrap_TestInterface__proxy_do_iface_method(TestInterface *self)
+{
+    PyGILState_STATE __py_state;
+    PyObject *py_self;
+    PyObject *py_retval;
+    PyObject *py_args;
+    PyObject *py_method;
+    
+    __py_state = PyGILState_Ensure();
+    py_self = pygobject_new((GObject *) self);
+    if (!py_self) {
+        if (PyErr_Occurred())
+            PyErr_Print();
+        PyGILState_Release(__py_state);
+        return;
+    }
+    py_args = PyTuple_New(0);
+    py_method = PyObject_GetAttrString(py_self, "do_iface_method");
+    if (!py_method) {
+        if (PyErr_Occurred())
+            PyErr_Print();
+        Py_DECREF(py_args);
+        Py_DECREF(py_self);
+        PyGILState_Release(__py_state);
+        return;
+    }
+    py_retval = PyObject_CallObject(py_method, py_args);
+    if (!py_retval) {
+        if (PyErr_Occurred())
+            PyErr_Print();
+        Py_DECREF(py_method);
+        Py_DECREF(py_args);
+        Py_DECREF(py_self);
+        PyGILState_Release(__py_state);
+        return;
+    }
+    if (py_retval != Py_None) {
+        if (PyErr_Occurred())
+            PyErr_Print();
+        PyErr_SetString(PyExc_TypeError, "retval should be None");
+        Py_DECREF(py_retval);
+        Py_DECREF(py_method);
+        Py_DECREF(py_args);
+        Py_DECREF(py_self);
+        PyGILState_Release(__py_state);
+        return;
+    }
+    
+    Py_DECREF(py_retval);
+    Py_DECREF(py_method);
+    Py_DECREF(py_args);
+    Py_DECREF(py_self);
+    PyGILState_Release(__py_state);
+}
+
+static void
+__TestInterface__interface_init(TestInterfaceIface *iface,
+                               PyTypeObject *pytype)
+{
+    TestInterfaceIface *parent_iface = g_type_interface_peek_parent(iface);
+    PyObject *py_method;
+
+    py_method = pytype ? PyObject_GetAttrString((PyObject *) pytype,
+                                               "do_iface_method") : NULL;
+
+    if (py_method && !PyObject_TypeCheck(py_method, &PyCFunction_Type)) {
+        iface->iface_method = _wrap_TestInterface__proxy_do_iface_method;
+    } else {
+        PyErr_Clear();
+        if (parent_iface) {
+            iface->iface_method = parent_iface->iface_method;
+        }
+       Py_XDECREF(py_method);
+    }
+}
+
+static const GInterfaceInfo __TestInterface__iinfo = {
+    (GInterfaceInitFunc) __TestInterface__interface_init,
+    NULL,
+    NULL
+};
+
+/* TestFloating */
+PYGLIB_DEFINE_TYPE("testhelper.Floating", PyTestFloating_Type, PyGObject);
+
+/* TestOwnedByLibrary */
+PYGLIB_DEFINE_TYPE("testhelper.OwnedByLibrary", PyTestOwnedByLibrary_Type, PyGObject);
+
+static PyObject *
+_wrap_test_owned_by_library_release (PyGObject *self)
+{
+    test_owned_by_library_release (TEST_OWNED_BY_LIBRARY (self->obj));
+    return Py_None;
+}
+
+static const PyMethodDef _PyTestOwnedByLibrary_methods[] = {
+    { "release", (PyCFunction)_wrap_test_owned_by_library_release, METH_NOARGS, NULL },
+    { NULL, NULL, 0, NULL }
+};
+
+/* TestFloatingAndSunk */
+PYGLIB_DEFINE_TYPE("testhelper.FloatingAndSunk", PyTestFloatingAndSunk_Type, PyGObject);
+
+static PyObject *
+_wrap_test_floating_and_sunk_release (PyGObject *self)
+{
+    test_floating_and_sunk_release (TEST_FLOATING_AND_SUNK (self->obj));
+    return Py_None;
+}
+
+static const PyMethodDef _PyTestFloatingAndSunk_methods[] = {
+    { "release", (PyCFunction)_wrap_test_floating_and_sunk_release, METH_NOARGS, NULL },
+    { NULL, NULL, 0, NULL }
+};
+
+
+#include <string.h>
+#include <glib-object.h>
+
+static void
+test1_callback (GObject *object, char *data)
+{
+  g_return_if_fail (G_IS_OBJECT (object));
+  g_return_if_fail (!strcmp (data, "user-data"));
+}
+
+static void
+test1_callback_swapped (char *data, GObject *object)
+{
+  g_return_if_fail (G_IS_OBJECT (object));
+  g_return_if_fail (!strcmp (data, "user-data"));
+}
+
+static void
+test2_callback (GObject *object, char *string)
+{
+  g_return_if_fail (G_IS_OBJECT (object));
+  g_return_if_fail (!strcmp (string, "string"));
+}
+
+static int
+test3_callback (GObject *object, double d)
+{
+  g_return_val_if_fail (G_IS_OBJECT (object), -1);
+  g_return_val_if_fail (d == 42.0, -1);
+
+  return 20;
+}
+
+static void
+test4_callback (GObject *object,
+                gboolean b, long l, float f, double d, guint uint, gulong ulong,
+                gpointer user_data)
+{
+  g_return_if_fail (b == TRUE);
+  g_return_if_fail (l == 10L);
+  g_return_if_fail (f <= 3.14001 && f >= 3.13999);
+  g_return_if_fail (d <= 1.78001 && d >= 1.77999);
+  g_return_if_fail (uint == 20);
+  g_return_if_fail (ulong == 30L);
+}
+
+static float
+test_float_callback (GObject *object, float f)
+{
+  g_return_val_if_fail (G_IS_OBJECT (object), -1);
+  g_return_val_if_fail (f <= 1.234001 && f >= 1.123999, -1);
+
+  return f;
+}
+
+static double
+test_double_callback (GObject *object, double d)
+{
+  g_return_val_if_fail (G_IS_OBJECT (object), -1);
+  g_return_val_if_fail (d <= 1.234001 && d >= 1.123999, -1);
+
+  return d;
+}
+
+static gint64
+test_int64_callback (GObject *object, gint64 i)
+{
+  g_return_val_if_fail (G_IS_OBJECT (object), -1);
+
+  if (i == G_MAXINT64)
+      return i-1;
+  return i;
+}
+
+static char *
+test_string_callback (GObject *object, char *s)
+{
+  g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+  g_return_val_if_fail (!strcmp(s, "str"), NULL);
+
+  return g_strdup (s);
+}
+
+static GObject *
+test_object_callback (GObject *object, GObject *o)
+{
+  g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+
+  return o;
+}
+
+static GParamSpec *
+test_paramspec_callback (GObject *object)
+{
+  g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+
+  return g_param_spec_boolean ("test-param", "test", "test boolean", TRUE, G_PARAM_READABLE);
+}
+
+static GValue *
+test_gvalue_callback (GObject *object, const GValue *v)
+{
+  GValue *ret = g_malloc0 (sizeof (GValue));
+
+  g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+  g_return_val_if_fail (G_IS_VALUE (v), NULL);
+
+  g_value_init (ret, G_VALUE_TYPE (v));
+  g_value_copy (v, ret);
+  return ret;
+}
+
+static GValue *
+test_gvalue_ret_callback (GObject *object, GType type)
+{
+  GValue *ret = g_malloc0 (sizeof (GValue));
+
+  g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+
+  g_value_init (ret, type);
+
+  switch (type) {
+    case G_TYPE_INT:
+      g_value_set_int(ret, G_MAXINT);
+      break;
+    case G_TYPE_INT64:
+      g_value_set_int64(ret, G_MAXINT64);
+      break;
+    case G_TYPE_UINT:
+      g_value_set_uint(ret, G_MAXUINT);
+      break;
+    case G_TYPE_UINT64:
+      g_value_set_uint64(ret, G_MAXUINT64);
+      break;
+    case G_TYPE_STRING:
+      g_value_set_string(ret, "hello");
+      break;
+    default:
+      g_critical ("test_gvalue_ret_callback() does not support type %s", g_type_name (type));
+  }
+
+  return ret;
+}
+
+static GParamSpec *
+test_paramspec_in_callback (GObject *object, GParamSpec *p)
+{
+  g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+  g_return_val_if_fail (G_IS_PARAM_SPEC (p), NULL);
+
+  return p;
+}
+
+static void
+connectcallbacks (GObject *object)
+{
+
+  gchar *data = "user-data";
+  
+  g_signal_connect (G_OBJECT (object),
+                    "test1",
+                    G_CALLBACK (test1_callback), 
+                    data);
+  g_signal_connect_swapped (G_OBJECT (object),
+                    "test1",
+                    G_CALLBACK (test1_callback_swapped), 
+                    data);
+  g_signal_connect (G_OBJECT (object),
+                    "test2",
+                    G_CALLBACK (test2_callback), 
+                    NULL);
+  g_signal_connect (G_OBJECT (object),
+                    "test3",
+                    G_CALLBACK (test3_callback), 
+                    NULL);
+  g_signal_connect (G_OBJECT (object),
+                    "test4",
+                    G_CALLBACK (test4_callback), 
+                    NULL);
+  g_signal_connect (G_OBJECT (object),
+                    "test_float",
+                    G_CALLBACK (test_float_callback), 
+                    NULL);
+  g_signal_connect (G_OBJECT (object),
+                    "test_double",
+                    G_CALLBACK (test_double_callback), 
+                    NULL);
+  g_signal_connect (G_OBJECT (object),
+                    "test_int64",
+                    G_CALLBACK (test_int64_callback), 
+                    NULL);
+  g_signal_connect (G_OBJECT (object),
+                    "test_string",
+                    G_CALLBACK (test_string_callback), 
+                    NULL);
+  g_signal_connect (G_OBJECT (object),
+                    "test_object",
+                    G_CALLBACK (test_object_callback), 
+                    NULL);
+  g_signal_connect (G_OBJECT (object),
+                    "test_paramspec",
+                    G_CALLBACK (test_paramspec_callback), 
+                    NULL);
+  g_signal_connect (G_OBJECT (object),
+                    "test_gvalue",
+                    G_CALLBACK (test_gvalue_callback), 
+                    NULL);
+  g_signal_connect (G_OBJECT (object),
+                    "test_gvalue_ret",
+                    G_CALLBACK (test_gvalue_ret_callback), 
+                    NULL);
+  g_signal_connect (G_OBJECT (object),
+                    "test_paramspec_in",
+                    G_CALLBACK (test_paramspec_in_callback), 
+                    NULL);
+}
+
+static PyObject *
+_wrap_connectcallbacks(PyObject * self, PyObject *args)
+{
+    PyGObject *obj;
+
+    if (!PyArg_ParseTuple(args, "O", &obj))
+      return NULL;
+
+    connectcallbacks (G_OBJECT (obj->obj));
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+_wrap_test_value(PyObject *self, PyObject *args)
+{
+  GValue tvalue = {0,}, *value = &tvalue;
+  PyObject *obj;
+
+  if (!PyArg_ParseTuple(args, "O", &obj))
+    return NULL;
+
+  g_value_init(value, G_TYPE_VALUE);
+  if (pyg_value_from_pyobject(value, obj)) {
+    PyErr_SetString(PyExc_TypeError, "Could not convert to GValue");
+    return NULL;
+  }
+  
+  return pyg_value_as_pyobject(value, FALSE);
+}
+
+static PyObject *
+_wrap_test_state_ensure_release(PyObject *self, PyObject *args)
+{
+    int state = pyg_gil_state_ensure ();
+    pyg_gil_state_release (state);
+
+    Py_RETURN_NONE;
+}
+
+static PyObject *
+_wrap_test_value_array(PyObject *self, PyObject *args)
+{
+  GValue tvalue = {0,}, *value = &tvalue;
+  PyObject *obj;
+
+  if (!PyArg_ParseTuple(args, "O", &obj))
+    return NULL;
+
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+  g_value_init(value, G_TYPE_VALUE_ARRAY);
+  G_GNUC_END_IGNORE_DEPRECATIONS
+
+  if (pyg_value_from_pyobject(value, obj)) {
+    PyErr_SetString(PyExc_TypeError, "Could not convert to GValueArray");
+    return NULL;
+  }
+  
+  return pyg_value_as_pyobject(value, FALSE);
+}
+
+
+static PyObject *
+_wrap_value_array_get_nth_type(PyObject *self, PyObject *args)
+{
+  guint n;
+  GType type;
+  GValue *nth;
+  GValueArray *arr;
+  PyObject *obj;
+
+  if (!PyArg_ParseTuple(args, "OI", &obj, &n))
+    return NULL;
+
+  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
+  if (pyg_boxed_check(obj, G_TYPE_VALUE) &&
+      G_VALUE_HOLDS(pyg_boxed_get(obj, GValue), G_TYPE_VALUE_ARRAY)) {
+    arr = g_value_get_boxed(pyg_boxed_get(obj, GValue));
+  } else if (pyg_boxed_check(obj, G_TYPE_VALUE_ARRAY)) {
+    arr = pyg_boxed_get(obj, GValueArray);
+  } else {
+    PyErr_SetString(PyExc_TypeError, "First argument is not GValueArray");
+    return NULL;
+  }
+
+  if (n >= arr->n_values) {
+    PyErr_SetString(PyExc_TypeError, "Index is out of bounds");
+    return NULL;
+  }
+  nth = g_value_array_get_nth(arr, n);
+  type = G_VALUE_TYPE(nth);
+
+  G_GNUC_END_IGNORE_DEPRECATIONS
+
+  return pyg_type_wrapper_new(type);
+}
+
+static PyObject *
+_wrap_constant_strip_prefix(PyObject *self, PyObject *args)
+{
+    const char *name, *strip_prefix;
+    const gchar *result;
+
+    if (!PyArg_ParseTuple (args, "ss", &name, &strip_prefix))
+        return NULL;
+
+    result = pyg_constant_strip_prefix (name, strip_prefix);
+    return PYGLIB_PyUnicode_FromString (result);
+}
+
+static PyObject *
+_wrap_test_gerror_exception(PyObject *self, PyObject *args)
+{
+    PyObject *py_method;
+    PyObject *py_args;
+    PyObject *py_ret;
+    GError *err = NULL;
+
+    if (!PyArg_ParseTuple(args, "O", &py_method))
+        return NULL;
+
+    py_args = PyTuple_New(0);
+    py_ret = PyObject_CallObject(py_method, py_args);
+    if (pyg_gerror_exception_check(&err)) {
+        pyg_error_check(&err);
+        return NULL;
+    }      
+
+    Py_DECREF(py_args);
+    Py_DECREF(py_ret);
+
+    Py_INCREF(Py_None);
+    return Py_None;
+}
+
+static PyObject *
+_wrap_test_owned_by_library_get_instance_list (PyObject *self)
+{
+    PyObject *py_list, *py_obj;
+    GSList *list, *tmp;
+
+    list = test_owned_by_library_get_instance_list ();
+
+    if ((py_list = PyList_New (0)) == NULL) {
+       return NULL;
+    }
+    for (tmp = list; tmp != NULL; tmp = tmp->next) {
+       py_obj = pygobject_new (G_OBJECT (tmp->data));
+       if (py_obj == NULL) {
+           Py_DECREF (py_list);
+           return NULL;
+       }
+       PyList_Append (py_list, py_obj);
+       Py_DECREF (py_obj);
+    }
+    return py_list;
+}
+
+static PyObject *
+_wrap_test_floating_and_sunk_get_instance_list (PyObject *self)
+{
+    PyObject *py_list, *py_obj;
+    GSList *list, *tmp;
+
+    list = test_floating_and_sunk_get_instance_list ();
+
+    if ((py_list = PyList_New (0)) == NULL) {
+       return NULL;
+    }
+    for (tmp = list; tmp != NULL; tmp = tmp->next) {
+       py_obj = pygobject_new (G_OBJECT (tmp->data));
+       if (py_obj == NULL) {
+           Py_DECREF (py_list);
+           return NULL;
+       }
+       PyList_Append (py_list, py_obj);
+       Py_DECREF (py_obj);
+    }
+    return py_list;
+}
+
+
+static PyObject *
+_wrap_test_parse_constructor_args (PyObject *self, PyObject *args)
+{
+    char *arg_names[] = {"label", NULL};
+    char *prop_names[] = {"label", NULL};
+    GParameter params[1] = {{0}};
+    PyObject *parsed_args[1];
+    guint nparams = 0;
+
+    if (!PyArg_ParseTuple(args, "O", &(parsed_args[0])))
+        return NULL;
+
+    if (!pyg_parse_constructor_args (
+            TYPE_TEST, arg_names, prop_names, params, &nparams, parsed_args)) {
+        return NULL;
+    }
+
+    return PYGLIB_PyLong_FromLong (nparams);
+}
+
+static PyObject *
+_wrap_test_to_unichar_conv (PyObject * self, PyObject *args)
+{
+    PyObject *obj;
+    gunichar result;
+
+    if (!PyArg_ParseTuple(args, "O", &obj))
+      return NULL;
+
+    if (!pyg_pyobj_to_unichar_conv (obj, &result))
+        return NULL;
+
+    return PYGLIB_PyLong_FromLong (result);
+}
+
+static PyMethodDef testhelper_functions[] = {
+    { "test_parse_constructor_args", (PyCFunction)_wrap_test_parse_constructor_args, METH_VARARGS },
+    { "get_test_thread", (PyCFunction)_wrap_get_test_thread, METH_NOARGS },
+    { "test_to_unichar_conv", (PyCFunction)_wrap_test_to_unichar_conv, METH_VARARGS },
+    { "get_unknown", (PyCFunction)_wrap_get_unknown, METH_NOARGS },
+    { "create_test_type", (PyCFunction)_wrap_create_test_type, METH_NOARGS },
+    { "test_state_ensure_release", (PyCFunction)_wrap_test_state_ensure_release, METH_NOARGS },
+    { "test_g_object_new", (PyCFunction)_wrap_test_g_object_new, METH_NOARGS },
+    { "connectcallbacks", (PyCFunction)_wrap_connectcallbacks, METH_VARARGS },
+    { "test_value", (PyCFunction)_wrap_test_value, METH_VARARGS },      
+    { "test_value_array", (PyCFunction)_wrap_test_value_array, METH_VARARGS },
+    { "value_array_get_nth_type", (PyCFunction)_wrap_value_array_get_nth_type, METH_VARARGS },
+    { "constant_strip_prefix", (PyCFunction)_wrap_constant_strip_prefix, METH_VARARGS },
+    { "test_gerror_exception", (PyCFunction)_wrap_test_gerror_exception, METH_VARARGS },
+    { "owned_by_library_get_instance_list", (PyCFunction)_wrap_test_owned_by_library_get_instance_list, METH_NOARGS },
+    { "floating_and_sunk_get_instance_list", (PyCFunction)_wrap_test_floating_and_sunk_get_instance_list, METH_NOARGS },
+    { NULL, NULL }
+};
+
+PYGLIB_MODULE_START(testhelper, "testhelper")
+{
+  PyObject *gobject_module;
+  PyObject *m, *d;
+
+
+  if ((gobject_module = pygobject_init(-1, -1, -1)) == NULL)
+    return NULL;
+  Py_DECREF (gobject_module);
+
+  d = PyModule_GetDict(module);
+
+  if ((m = PyImport_ImportModule("gi.repository.GObject")) == NULL) {
+    PyErr_SetString(PyExc_ImportError,
+                   "could not import gobject");
+    return PYGLIB_MODULE_ERROR_RETURN;
+  }
+
+  /* TestInterface */
+  PyTestInterface_Type.tp_methods = (struct PyMethodDef*)_PyTestInterface_methods;
+  PyTestInterface_Type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE);  
+  pyg_register_interface(d, "Interface", TEST_TYPE_INTERFACE,
+                        &PyTestInterface_Type);
+  pyg_register_interface_info(TEST_TYPE_INTERFACE, &__TestInterface__iinfo);
+
+
+  /* TestUnknown */
+  PyTestUnknown_Type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE);
+  PyTestUnknown_Type.tp_weaklistoffset = offsetof(PyGObject, weakreflist);
+  PyTestUnknown_Type.tp_dictoffset = offsetof(PyGObject, inst_dict);
+  pygobject_register_class(d, "Unknown", TEST_TYPE_UNKNOWN,
+                          &PyTestUnknown_Type,
+                          Py_BuildValue("(O)",
+                           &PyGObject_Type,
+                           &PyTestInterface_Type));
+
+  /* TestFloating */
+  PyTestFloating_Type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE);
+  PyTestFloating_Type.tp_weaklistoffset = offsetof(PyGObject, weakreflist);
+  PyTestFloating_Type.tp_dictoffset = offsetof(PyGObject, inst_dict);
+  pygobject_register_class(d, "Floating", TEST_TYPE_FLOATING,
+                          &PyTestFloating_Type,
+                          Py_BuildValue("(O)",
+                           &PyGObject_Type));
+
+  /* TestOwnedByLibrary */
+  PyTestOwnedByLibrary_Type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE);
+  PyTestOwnedByLibrary_Type.tp_methods = (struct PyMethodDef*)_PyTestOwnedByLibrary_methods;
+  PyTestOwnedByLibrary_Type.tp_weaklistoffset = offsetof(PyGObject, weakreflist);
+  PyTestOwnedByLibrary_Type.tp_dictoffset = offsetof(PyGObject, inst_dict);
+  pygobject_register_class(d, "OwnedByLibrary", TEST_TYPE_OWNED_BY_LIBRARY,
+                          &PyTestOwnedByLibrary_Type,
+                          Py_BuildValue("(O)",
+                           &PyGObject_Type));
+
+  /* TestFloatingAndSunk */
+  PyTestFloatingAndSunk_Type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE);
+  PyTestFloatingAndSunk_Type.tp_methods = (struct PyMethodDef*)_PyTestFloatingAndSunk_methods;
+  PyTestFloatingAndSunk_Type.tp_weaklistoffset = offsetof(PyGObject, weakreflist);
+  PyTestFloatingAndSunk_Type.tp_dictoffset = offsetof(PyGObject, inst_dict);
+  pygobject_register_class(d, "FloatingAndSunk", TEST_TYPE_FLOATING_AND_SUNK,
+                           &PyTestFloatingAndSunk_Type,
+                           Py_BuildValue("(O)",
+                           &PyGObject_Type));
+}
+PYGLIB_MODULE_END
+
diff --git a/tests/valgrind.supp b/tests/valgrind.supp
new file mode 100644 (file)
index 0000000..5792a7c
--- /dev/null
@@ -0,0 +1,30 @@
+# https://bugzilla.redhat.com/show_bug.cgi?id=1538073
+
+{
+   <py36-start1>
+   Memcheck:Cond
+   fun:__wcsnlen_sse4_1
+   fun:wcsrtombs
+   fun:wcstombs
+   fun:wcstombs
+   fun:encode_current_locale*
+}
+
+{
+   <fontconfig>
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:malloc
+   fun:FcPatternObjectInsertElt
+   fun:FcPatternObjectAddWithBinding
+}
+
+{
+   <fontconfig-2>
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:malloc
+   fun:FcPatternCreate
+   fun:FcParsePattern
+   fun:FcEndElement
+}
diff --git a/tools/pygi-convert.sh b/tools/pygi-convert.sh
new file mode 100755 (executable)
index 0000000..8a2160a
--- /dev/null
@@ -0,0 +1,373 @@
+#!/bin/sh
+
+if [ -n "$1" ]; then
+    FILES_TO_CONVERT="$@"
+else
+    FILES_TO_CONVERT="$(find . -name '*.py')"
+fi
+
+for f in $FILES_TO_CONVERT; do
+    perl -i -0 \
+    -pe "s/import gconf\n/from gi.repository import GConf\n/g;" \
+    -pe "s/gconf\./GConf\./g;" \
+    -pe "s/GConf\.client_get_default/GConf.Client.get_default/g;" \
+    -pe "s/GConf\.CLIENT_/GConf.ClientPreloadType./g;" \
+    -pe "s/GConf\.VALUE_/GConf.ValueType./g;" \
+    -pe "s/gconf_client.notify_add\('\/desktop\/sugar\/collaboration\/publish_gadget',/return;gconf_client.notify_add\('\/desktop\/sugar\/collaboration\/publish_gadget',/g;" \
+\
+    -pe "s/import pygtk/import gi/g;" \
+    -pe "s/pygtk.require\('2.0'\)/gi.require_version\('Gtk', '3.0'\)/g;" \
+    -pe "s/pygtk.require\(\"2.0\"\)/gi.require_version\(\"Gtk\", \"3.0\"\)/g;" \
+    -pe "s/import gtk\n/from gi.repository import Gtk\n/g;" \
+    -pe "s/(?<!\.)gtk\./Gtk\./g;" \
+    -pe "s/Gtk.ACCEL_/Gtk.AccelFlags./g;" \
+    -pe "s/Gtk.ARROW_/Gtk.ArrowType./g;" \
+    -pe "s/Gtk.ASSISTANT_PAGE_/Gtk.AssistantPageType./g;" \
+    -pe "s/Gtk.BUTTONBOX_/Gtk.ButtonBoxStyle./g;" \
+    -pe "s/Gtk.BUTTONS_/Gtk.ButtonsType./g;" \
+    -pe "s/Gtk.CELL_RENDERER_MODE_/Gtk.CellRendererMode./g;" \
+    -pe "s/Gtk.CELL_RENDERER_FOCUSED/Gtk.CellRendererState.FOCUSED/g;" \
+    -pe "s/Gtk.CELL_RENDERER_INSENSITIVE/Gtk.CellRendererState.INSENSITIVE/g;" \
+    -pe "s/Gtk.CELL_RENDERER_PRELIT/Gtk.CellRendererState.PRELIT/g;" \
+    -pe "s/Gtk.CELL_RENDERER_SORTED/Gtk.CellRendererState.SORTED/g;" \
+    -pe "s/Gtk.CELL_RENDERER_SELECTED/Gtk.CellRendererState.SELECTED/g;" \
+    -pe "s/Gtk.CORNER_/Gtk.CornerType./g;" \
+    -pe "s/Gtk.DIALOG_/Gtk.DialogFlags./g;" \
+    -pe "s/Gtk.ENTRY_ICON_/Gtk.EntryIconPosition./g;" \
+    -pe "s/Gtk.EXPAND/Gtk.AttachOptions.EXPAND/g;" \
+    -pe "s/Gtk.FALSE/False/g;" \
+    -pe "s/Gtk.FILE_CHOOSER_ACTION_/Gtk.FileChooserAction./g;" \
+    -pe "s/Gtk.FILL/Gtk.AttachOptions.FILL/g;" \
+    -pe "s/Gtk.ICON_LOOKUP_/Gtk.IconLookupFlags./g;" \
+    -pe "s/Gtk.ICON_SIZE_/Gtk.IconSize./g;" \
+    -pe "s/Gtk.IMAGE_/Gtk.ImageType./g;" \
+    -pe "s/Gtk.JUSTIFY_/Gtk.Justification./g;" \
+    -pe "s/Gtk.MESSAGE_/Gtk.MessageType./g;" \
+    -pe "s/Gtk.MOVEMENT_/Gtk.MovementStep./g;" \
+    -pe "s/Gtk.ORIENTATION_/Gtk.Orientation./g;" \
+    -pe "s/Gtk.POLICY_/Gtk.PolicyType./g;" \
+    -pe "s/Gtk.POS_/Gtk.PositionType./g;" \
+    -pe "s/Gtk.RECENT_FILTER_/Gtk.RecentFilterFlags./g;" \
+    -pe "s/Gtk.RECENT_SORT_/Gtk.RecentSortType./g;" \
+    -pe "s/Gtk.RELIEF_/Gtk.ReliefStyle./g;" \
+    -pe "s/Gtk.RESPONSE_/Gtk.ResponseType./g;" \
+    -pe "s/Gtk.SELECTION_/Gtk.SelectionMode./g;" \
+    -pe "s/Gtk.SHADOW_/Gtk.ShadowType./g;" \
+    -pe "s/Gtk.SHADOW_NONE/Gtk.ShadowType.NONE/g;" \
+    -pe "s/Gtk.SHRINK/Gtk.AttachOptions.SHRINK/g;" \
+    -pe "s/Gtk.SIZE_GROUP_/Gtk.SizeGroupMode./g;" \
+    -pe "s/Gtk.SORT_/Gtk.SortType./g;" \
+    -pe "s/Gtk.STATE_/Gtk.StateType./g;" \
+    -pe "s/Gtk.TARGET_/Gtk.TargetFlags./g;" \
+    -pe "s/Gtk.TEXT_DIR_/Gtk.TextDirection./g;" \
+    -pe "s/Gtk.TEXT_SEARCH_/Gtk.TextSearchFlags./g;" \
+    -pe "s/Gtk.TEXT_WINDOW_/Gtk.TextWindowType./g;" \
+    -pe "s/Gtk.TOOLBAR_/Gtk.ToolbarStyle./g;" \
+    -pe "s/Gtk.TREE_MODEL_/Gtk.TreeModelFlags./g;" \
+    -pe "s/Gtk.TREE_VIEW_COLUMN_/Gtk.TreeViewColumnSizing./g;" \
+    -pe "s/Gtk.TREE_VIEW_DROP_/Gtk.TreeViewDropPosition./g;" \
+    -pe "s/Gtk.TRUE/True/g;" \
+    -pe "s/Gtk.WINDOW_/Gtk.WindowType./g;" \
+    -pe "s/Gtk.DEST_DEFAULT_/Gtk.DestDefaults./g;" \
+    -pe "s/Gtk.WIN_POS_/Gtk.WindowPosition./g;" \
+    -pe "s/Gtk.WRAP_/Gtk.WrapMode./g;" \
+    -pe "s/Gtk.UI_MANAGER_/Gtk.UIManagerItemType./g;" \
+    -pe "s/Gtk.accel_map_/Gtk.AccelMap./g;" \
+    -pe "s/Gtk.settings_get_/Gtk.Settings.get_/g;" \
+    -pe "s/Gtk.icon_theme_get_default/Gtk.IconTheme.get_default/g;" \
+    -pe "s/Gtk.recent_manager_get_default/Gtk.RecentManager.get_default/g;" \
+    -pe "s/Gtk.image_new_from_stock/Gtk.Image.new_from_stock/g;" \
+    -pe "s/Gtk.image_new_from_icon_name/Gtk.Image.new_from_icon_name/g;" \
+    -pe "s/Gtk.window_set_default_icon_name/Gtk.Window.set_default_icon_name/g; " \
+    -pe "s/Gtk.combo_box_new_text/Gtk.ComboBoxText/g;" \
+    -pe "s/Gtk.keysyms\./Gdk.KEY_/g;" \
+    -pe "s/set_flags\(Gtk.CAN_DEFAULT\)/set_can_default\(True\)/g;" \
+    -pe "s/.flags\(\) & Gtk.MAPPED/.get_mapped\(\)/g;" \
+    -pe "s/.flags\(\) & Gtk.REALIZED/.get_realized\(\)/g;" \
+    -pe "s/\.window\.set_type_hint/.set_type_hint/g;" \
+    -pe "s/\.window\.set_skip_taskbar_hint/.set_skip_taskbar_hint/g;" \
+    -pe "s/\.window\.set_transient_for/.set_transient_for/g;" \
+    -pe "s/Gtk.Alignment\(/Gtk.Alignment.new\(/g;" \
+    -pe "#s/Gtk.Window.__init__\(self\)/Gtk.Window.__init__\(Gtk.WindowType.TOPLEVEL\)/g;" \
+    -pe "s/\.child([^_A-Za-z])/.get_child\(\)\1/g;" \
+\
+    -pe "s/column.pack_start\(([^,\)]*)\)/column.pack_start\(\1, True\)/g;" \
+    -pe "s/pack_start\(([^,\)]*)\)/pack_start\(\1, True, True, 0\)/g;" \
+    -pe "s/pack_start\(([^,]*), fill=([^,\)]*)\)/pack_start\(\1, True, \2, 0\)/g;" \
+    -pe "s/pack_start\(([^,]*), expand=([^,\)]*)\)/pack_start\(\1, \2, True, 0\)/g;" \
+    -pe "s/pack_start\(([^,]*),(\s*)padding=([A-Za-z0-9._]*)\)/pack_start\(\1,\2True, True,\2\3\)/g;" \
+    -pe "s/column.pack_end\(([^,\)]*)\)/column.pack_end\(\1, True\)/g;" \
+    -pe "s/pack_end\(([^,\)]*)\)/pack_end\(\1, True, True, 0\)/g;" \
+    -pe "s/pack_end\(([^,]*), fill=([^,\)]*)\)/pack_end\(\1, True, \2, 0\)/g;" \
+    -pe "s/pack_end\(([^,]*), expand=([^,\)]*)\)/pack_end\(\1, \2, True, 0\)/g;" \
+    -pe "s/pack_end\(([^,]*),(\s*)padding=([A-Za-z0-9._]*)\)/pack_end\(\1,\2True, True,\2\3\)/g;" \
+    -pe "#s/Gtk.HBox\(\)/Gtk.HBox\(False, 0\)/g;" \
+    -pe "#s/Gtk.VBox\(\)/Gtk.VBox\(False, 0\)/g;" \
+    -pe "s/Gtk.Label\s*\(([^,\)]+)\)/Gtk.Label\(label=\1\)/g;" \
+    -pe "s/Gtk.AccelLabel\s*\(([^,\)]+)\)/Gtk.AccelLabel\(label=\1\)/g;" \
+    -pe "s/Gtk.((?:Accel)?Label)\(label=label=/Gtk.\1\(label=/g;" \
+    -pe "s/len\(self._content.get_children\(\)\) > 0/self._content.get_children\(\)/g;" \
+    -pe "s/len\(self.menu.get_children\(\)\) > 0/self.menu.get_children\(\)/g;" \
+    -pe "s/import gobject\n/from gi.repository import GObject\n/g;" \
+    -pe "s/Gtk\..*\.__init__/gobject.GObject.__init__/g;" \
+\
+    -pe "s/rsvg.Handle\s*\(data=([^,\)]+)\)/Rsvg.Handle.new_from_data(\1)/g;" \
+\
+    -pe "s/from gtk import gdk\n/from gi.repository import Gdk\n/g;" \
+    -pe "s/import gtk.gdk as gdk\n/from gi.repository import Gdk\n/g;" \
+    -pe "s/Gtk.gdk.x11_/GdkX11.x11_/g;" \
+    -pe "s/Gtk.gdk\./Gdk\./g;" \
+    -pe "s/(?<!\.)gdk\./Gdk\./g;" \
+    -pe "s/Gdk.screen_width/Gdk.Screen.width/g;" \
+    -pe "s/Gdk.screen_height/Gdk.Screen.height/g;" \
+    -pe "s/Gdk.screen_get_default/Gdk.Screen.get_default/g;" \
+    -pe "s/Gdk.display_get_default/Gdk.Display.get_default/g;" \
+    -pe "s/screen_, x_, y_, modmask = display.get_pointer\(\)/x_, y_, modmask = display.get_pointer\(None\)/g;" \
+    -pe "s/Gdk.WINDOW_TYPE_HINT_/Gdk.WindowTypeHint./g;" \
+    -pe "s/Gdk.SHIFT_MASK/Gdk.ModifierType.SHIFT_MASK/g;" \
+    -pe "s/Gdk.LOCK_MASK/Gdk.ModifierType.LOCK_MASK/g;" \
+    -pe "s/Gdk.CONTROL_MASK/Gdk.ModifierType.CONTROL_MASK/g;" \
+    -pe "s/Gdk.MOD1_MASK/Gdk.ModifierType.MOD1_MASK/g;" \
+    -pe "s/Gdk.MOD2_MASK/Gdk.ModifierType.MOD2_MASK/g;" \
+    -pe "s/Gdk.MOD3_MASK/Gdk.ModifierType.MOD3_MASK/g;" \
+    -pe "s/Gdk.MOD4_MASK/Gdk.ModifierType.MOD4_MASK/g;" \
+    -pe "s/Gdk.MOD5_MASK/Gdk.ModifierType.MOD5_MASK/g;" \
+    -pe "s/Gdk.BUTTON1_MASK/Gdk.ModifierType.BUTTON1_MASK/g;" \
+    -pe "s/Gdk.BUTTON2_MASK/Gdk.ModifierType.BUTTON2_MASK/g;" \
+    -pe "s/Gdk.BUTTON3_MASK/Gdk.ModifierType.BUTTON3_MASK/g;" \
+    -pe "s/Gdk.BUTTON4_MASK/Gdk.ModifierType.BUTTON4_MASK/g;" \
+    -pe "s/Gdk.BUTTON5_MASK/Gdk.ModifierType.BUTTON5_MASK/g;" \
+    -pe "s/Gdk.RELEASE_MASK/Gdk.ModifierType.RELEASE_MASK/g;" \
+    -pe "s/Gdk.MODIFIER_MASK/Gdk.ModifierType.MODIFIER_MASK/g;" \
+    -pe "s/Gdk.([A-Z_0-9]*)_MASK/Gdk.EventMask.\1_MASK/g;" \
+    -pe "s/Gdk.VISIBILITY_FULLY_OBSCURED/Gdk.VisibilityState.FULLY_OBSCURED/g;" \
+    -pe "s/Gdk.NOTIFY_ANCESTOR/Gdk.NotifyType.ANCESTOR/g;" \
+    -pe "s/Gdk.NOTIFY_INFERIOR/Gdk.NotifyType.INFERIOR/g;" \
+    -pe "s/Gdk.NOTIFY_NONLINEAR_VIRTUAL/Gdk.NotifyType.NONLINEAR_VIRTUAL/g;" \
+    -pe "s/Gdk.NOTIFY_NONLINEAR/Gdk.NotifyType.NONLINEAR/g;" \
+    -pe "s/Gdk.NOTIFY_UNKNOWN/Gdk.NotifyType.UNKNOWN/g;" \
+    -pe "s/Gdk.NOTIFY_VIRTUAL/Gdk.NotifyType.VIRTUAL/g;" \
+    -pe "s/Gdk.PROP_MODE_APPEND/Gdk.PropMode.APPEND/g;" \
+    -pe "s/Gdk.PROP_MODE_PREPEND/Gdk.PropMode.PREPEND/g;" \
+    -pe "s/Gdk.PROP_MODE_REPLACE/Gdk.PropMode.REPLACE/g;" \
+    -pe "s/Gdk.BUTTON_PRESS/Gdk.EventType.BUTTON_PRESS/g;" \
+    -pe "s/Gdk.ACTION_/Gdk.DragAction./g;" \
+    -pe "s/Gdk.GRAB_/Gdk.GrabStatus./g;" \
+    -pe "s/Gdk.SCROLL_(DOWN|LEFT|RIGHT|UP)/Gdk.ScrollDirection.\1/g;" \
+    -pe "s/Gdk.([A-Z]+_(PTR|CURSOR))/Gdk.CursorType.\1/g;" \
+    -pe "s/Gdk.(CROSSHAIR)/Gdk.CursorType.\1/g;" \
+    -pe "s/Gdk.(WATCH)/Gdk.CursorType.\1/g;" \
+    -pe "s/Gdk.(ARROW)/Gdk.CursorType.\1/g;" \
+    -pe "s/Gdk.(CLOCK)/Gdk.CursorType.\1/g;" \
+    -pe "s/Gdk.WINDOW_STATE_(ABOVE|BELOW|FOCUSED|FULLSCREEN|ICONIFIED|MAXIMIZED|STICKY|WITHDRAWN)/Gdk.WindowState.\1/g;" \
+    -pe "s/Gdk.Cursor\s*\(/Gdk.Cursor.new\(/g;" \
+    -pe "s/#Gdk.Rectangle\(([^,\)]*), ([^,\)]*), ([^,\)]*), ([^,\)]*)\)/\1, \2, \3, \4/g;" \
+    -pe "s/Gdk.Rectangle//g;" \
+    -pe "s/intersection = child_rect.intersect/intersects_, intersection = child_rect.intersect/g;" \
+    -pe "s/event.state/event.get_state\(\)/g;" \
+\
+    -pe "s/Gdk.pixbuf_/GdkPixbuf.Pixbuf./g;" \
+    -pe "s/Gdk.Pixbuf/GdkPixbuf.Pixbuf/g;" \
+    -pe "s/Gdk.INTERP_/GdkPixbuf.InterpType./g;" \
+    -pe "s/Gdk.COLORSPACE_RGB/GdkPixbuf.Colorspace.RGB/g;" \
+\
+    -pe "s/import pango\n/from gi.repository import Pango\n/g;" \
+    -pe "s/pango\./Pango\./g;" \
+    -pe "s/Pango.ALIGN_/Pango.Alignment./g;" \
+    -pe "s/Pango.ELLIPSIZE_/Pango.EllipsizeMode./g;" \
+    -pe "s/Pango.STYLE_/Pango.Style./g;" \
+    -pe "s/Pango.UNDERLINE_/Pango.Underline./g;" \
+    -pe "s/Pango.WEIGHT_/Pango.Weight./g;" \
+    -pe "s/Pango.WRAP_/Pango.WrapMode./g;" \
+    -pe "s/Pango.TAB_/Pango.TabAlign./g;" \
+\
+    -pe "s/import atk\n/from gi.repository import Atk\n/g;" \
+    -pe "s/atk\./Atk\./g;" \
+    -pe "s/Atk.HYPERLINK_/Atk.HyperlinkStateFlags./g;" \
+    -pe "s/Atk.KEY_EVENT_/Atk.KeyEventType./g;" \
+    -pe "s/Atk.LAYER_/Atk.Layer./g;" \
+    -pe "s/Atk.RELATION_/Atk.RelationType./g;" \
+    -pe "s/Atk.ROLE_/Atk.Role./g;" \
+    -pe "s/Atk.STATE_/Atk.StateType./g;" \
+    -pe "s/Atk.TEXT_ATTR_/Atk.TextAttribute./g;" \
+    -pe "s/Atk.TEXT_BOUNDARY_/Atk.TextBoundary./g;" \
+    -pe "s/Atk.TEXT_CLIP_/Atk.TextClipType./g;" \
+\
+    -pe "s/import gio\n/from gi.repository import Gio\n/g;" \
+    -pe "s/gio\./Gio\./g;" \
+    -pe "s/Gio\.File\(uri=/Gio\.File\.new_for_uri\(/g;" \
+    -pe "s/Gio\.File\(path=/Gio\.File\.new_for_path\(/g;" \
+    -pe "s/Gio.FILE_COPY_/Gio.FileCopyFlags./g;" \
+    -pe "s/Gio.FILE_CREATE_/Gio.FileCreateFlags./g;" \
+    -pe "s/Gio.FILE_MONITOR_EVENT_/Gio.FileMonitorEvent./g;" \
+    -pe "s/Gio.FILE_MONITOR_/Gio.FileMonitorFlags./g;" \
+    -pe "s/Gio.FILE_TYPE_/Gio.FileType./g;" \
+    -pe "s/Gio.FILE_QUERY_INFO_/Gio.FileQueryInfoFlags./g;" \
+    -pe "s/Gio.MOUNT_MOUNT_/Gio.MountMountFlags./g;" \
+    -pe "s/Gio.MOUNT_OPERATION_/Gio.MountOperationResult./g;" \
+    -pe "s/Gio.MOUNT_UNMOUNT_/Gio.MountUnmountFlags./g;" \
+    -pe "s/Gio.OUTPUT_STREAM_SPLICE_/Gio.OutputStreamSpliceFlags./g;" \
+    -pe "s/Gio.vfs_/Gio.Vfs./g;" \
+\
+    -pe "s/import glib\n/from gi.repository import GLib\n/g;" \
+    -pe "s/(?<!\.)glib\./GLib\./g;" \
+    -pe "s/GLib.IO_(ERR|HUP|IN|NVAL|OUT|PRI)/GLib.IOCondition.\1/g;" \
+    -pe "s/GLib.IO_FLAG_/GLib.IOFlags./g;" \
+    -pe "s/GLib.OPTION_FLAG_/GLib.OptionFlags./g;" \
+    -pe "s/GLib.SPAWN_/GLib.SpawnFlags./g;" \
+    -pe "s/GLib.USER_DIRECTORY_/GLib.UserDirectory.DIRECTORY_/g;" \
+\
+    -pe "s/(?<!\.)gobject\./GObject\./g;" \
+    -pe "s/GObject.SIGNAL_/GObject.SignalFlags./g;" \
+    -pe "s/GObject.TYPE_NONE/None/g;" \
+\
+    -pe "s/import hippo\n/from gi.repository import Hippo\n/g;" \
+    -pe "s/hippo\./Hippo\./g;" \
+    -pe "s/Hippo\..*\.__init__/gobject.GObject.__init__/g;" \
+    -pe "s/Hippo.PACK_/Hippo.PackFlags./g;" \
+    -pe "s/Hippo.ORIENTATION_/Hippo.Orientation./g;" \
+    -pe "#s/insert_sorted\(([^,\)]*), ([^,\)]*), ([^,\)]*)\)/insert_sorted\(\1, \2, \3, None\)/g;" \
+    -pe "s/self\._box\.insert_sorted/#self\._box\.insert_sorted/g;" \
+    -pe "s/self._box.append\(([^,\)]*)\)/self._box.append\(\1, 0\)/g;" \
+    -pe "s/self.append\(self._buddy_icon\)/self.append\(self._buddy_icon, 0\)/g;" \
+    -pe "s/self._box.sort\(([^,\)]*)\)/self._box.sort\(\1, None\)/g;" \
+\
+    -pe "s/import wnck\n/from gi.repository import Wnck\n/g;" \
+    -pe "s/wnck\./Wnck\./g;" \
+    -pe "s/Wnck.screen_get_default/Wnck.Screen.get_default/g;" \
+    -pe "s/Wnck.WINDOW_/Wnck.WindowType./g;" \
+\
+    -pe "s/from sugar import _sugarext\n/from gi.repository import SugarExt\n/g;" \
+    -pe "s/_sugarext\.ICON_ENTRY_/SugarExt.SexyIconEntryPosition./g;" \
+    -pe "s/_sugarext\.IconEntry/SugarExt.SexyIconEntry/g;" \
+    -pe "s/_sugarext\.SMClientXSMP/SugarExt.GsmClientXSMP/g;" \
+    -pe "s/_sugarext\.VolumeAlsa/SugarExt.AcmeVolumeAlsa/g;" \
+    -pe "s/_sugarext\./SugarExt\./g;" \
+\
+    -pe "s/import gtksourceview2\n/from gi.repository import GtkSource\n/g;" \
+    -pe "s/import gtksourceview2 as gsv\n/from gi.repository import GtkSource\n/g;" \
+    -pe "s/gtksourceview2\./GtkSource\./g;" \
+    -pe "s/gsv\./GtkSource\./g;" \
+    -pe "s/GtkSource.DRAW_SPACES_/GtkSource.DrawSpacesFlags./g;" \
+    -pe "s/GtkSource.SMART_HOME_END_/GtkSource.SmartHomeEndType./g;" \
+    -pe "s/GtkSource.style_scheme_manager_get_default/GtkSource.StyleSchemeManager.get_default/g;" \
+    -pe "s/GtkSource.language_manager_get_default/GtkSource.LanguageManager.get_default/g;" \
+\
+    -pe "#s/import cairo\n/from gi.repository import cairo\n/g;" \
+\
+    -pe "s/SugarExt.xsmp_init\(\)/'mec'/g;" \
+    -pe "s/SugarExt.xsmp_run\(\)/#SugarExt.xsmp_run\(\)/g;" \
+    -pe "s/SugarExt.session_create_global\(\)/None #SugarExt.session_create_global\(\)/g;" \
+    -pe "s/self.session.start\(\)/return #self.session.start\(\)/g;" \
+\
+    -pe "s/self._box.sort\(self._layout.compare_activities, None\)/pass #self._box.sort(self._layout.compare_activities, None)/g;" \
+    -pe "s/attach_points = info.get_attach_points/has_attach_points_, attach_points = info.get_attach_points/g;" \
+    -pe "s/attach_points\[0\]\[0\]/attach_points\[0\].x/g;" \
+    -pe "s/attach_points\[0\]\[1\]/attach_points\[0\].y/g;" \
+    -pe "s/has_attach_points_/return 0,0;has_attach_points_/g;" \
+    -pe "s/gobject.GObject.__init__\(self, self._model_filter\)/gobject.GObject.__init__\(self, model=self._model_filter\)/g;" \
+    -pe "s/self._model_filter.set_visible_func/return;self._model_filter.set_visible_func/g;" \
+    -pe "s/buddies_column.set_cell_data_func/return;buddies_column.set_cell_data_func/g;" \
+    -pe "s/Hippo\.cairo_surface_from_gdk_pixbuf/SugarExt\.cairo_surface_from_pixbuf/g;" \
+\
+    -pe "s/import pynotify\n/from gi.repository import Notify\n/g;" \
+    -pe "s/pynotify\./Notify\./g;" \
+\
+    -pe "s/import webkit\n/from gi.repository import WebKit\n/g;" \
+    -pe "s/import clutter\n/from gi.repository import Clutter\n/g;" \
+    -pe "s/from clutter import cogl\n/from gi.repository import Cogl\n/g;" \
+    -pe "s/(?<!\.)clutter\./Clutter\./g;" \
+    -pe "s/(?<!\.)cogl\./Cogl\./g;" \
+    -pe "s/Clutter.ACTOR_/Clutter.ActorFlags./g;" \
+    -pe "s/Clutter.ALLOCATION_/Clutter.AllocationFlags./g;" \
+    -pe "s/Clutter.BIND_/Clutter.BindCoordinate./g;" \
+    -pe "s/Clutter.BIN_ALIGNMENT_/Clutter.BinAlignment./g;" \
+    -pe "s/Clutter.BOX_ALIGNMENT_/Clutter.BoxAlignment./g;" \
+    -pe "s/Clutter.DRAG_/Clutter.DragAxis./g;" \
+    -pe "s/Clutter.EASE_/Clutter.AnimationMode./g;" \
+    -pe "s/Clutter.FEATURE_/Clutter.FeatureFlags./g;" \
+    -pe "s/Clutter.FLOW_/Clutter.FLOW_ORIENTATION./g;" \
+    -pe "s/Clutter.FONT_/Clutter.FontFlags./g;" \
+    -pe "s/Clutter.GRAVITY_/Clutter.Gravity./g;" \
+    -pe "s/Clutter.INTERPOLATION/Clutter.Interpolation./g;" \
+    -pe "s/Clutter.LINEAR/Clutter.AnimationMode.LINEAR/g;" \
+    -pe "s/Clutter.PATH_/Clutter.PathNodeType./g;" \
+    -pe "s/Clutter.PICK_/Clutter.PickMode./g;" \
+    -pe "s/Clutter.REQUEST_/Clutter.RequestMode./g;" \
+    -pe "s/Clutter.ROTATE_/Clutter.RotateDirection./g;" \
+    -pe "s/Clutter.SCRIPT_/Clutter.ScriptError./g;" \
+    -pe "s/Clutter.STAGE_STATE_/Clutter.StageState./g;" \
+    -pe "s/Clutter.TABLE_ALIGNMENT_/Clutter.TableAlignment./g;" \
+    -pe "s/Clutter.TEXTURE_ERROR_/Clutter.TextureError./g;" \
+    -pe "s/Clutter.TEXTURE_/Clutter.TextureFlags./g;" \
+    -pe "s/Clutter.TEXT_/Clutter.TextDirection./g;" \
+    -pe "s/Clutter.TIMELINE_/Clutter.TimelineDirection./g;" \
+    -pe "s/Clutter.UNIT_/Clutter.UnitType./g;" \
+    -pe "s/Clutter.X_AXIS/Clutter.RotateAxis.X_AXIS/g;" \
+    -pe "s/Clutter.Y_AXIS/Clutter.RotateAxis.Y_AXIS/g;" \
+    -pe "s/Clutter.Z_AXIS/Clutter.RotateAxis.Z_AXIS/g;" \
+    -pe "s/Clutter.ENTER/Clutter.EventType.ENTER/g;" \
+    -pe "s/Clutter.LEAVE/Clutter.EventType.LEAVE/g;" \
+    -pe "s/Clutter.BUTTON_PRESS/Clutter.EventType.BUTTON_PRESS/g;" \
+    -pe "s/Clutter.BUTTON_RELEASE/Clutter.EventType.BUTTON_RELEASE/g;" \
+    -pe "s/Clutter.KEY_PRESS/Clutter.EventType.KEY_PRESS/g;" \
+    -pe "s/Clutter.KEY_RELEASE/Clutter.EventType.KEY_RELEASE/g;" \
+    -pe "s/Clutter.SCROLL/Clutter.EventType.SCROLL/g;" \
+    -pe "s/Clutter.DELETE/Clutter.EventType.DELETE/g;" \
+    -pe "s/Clutter.CLIENT_MESSAGE/Clutter.EventType.CLIENT_MESSAGE/g;" \
+    -pe "s/Clutter.DESTROY_NOTIFY/Clutter.EventType.DESTROY_NOTIFY/g;" \
+    -pe "s/Clutter.STAGE_STATE/Clutter.EventType.STAGE_STATE/g;" \
+    -pe "s/Clutter.MOTION/Clutter.EventType.MOTION/g;" \
+    -pe "s/Clutter.BUTTON1_MASK/Clutter.ModifierType.BUTTON1_MASK/g;" \
+    -pe "s/Clutter.BUTTON2_MASK/Clutter.ModifierType.BUTTON2_MASK/g;" \
+    -pe "s/Clutter.BUTTON3_MASK/Clutter.ModifierType.BUTTON3_MASK/g;" \
+    -pe "s/Clutter.BUTTON4_MASK/Clutter.ModifierType.BUTTON4_MASK/g;" \
+    -pe "s/Clutter.BUTTON5_MASK/Clutter.ModifierType.BUTTON5_MASK/g;" \
+    -pe "s/Clutter.CONTROL_MASK/Clutter.ModifierType.CONTROL_MASK/g;" \
+    -pe "s/Clutter.HYPER_MASK/Clutter.ModifierType.HYPER_MASK/g;" \
+    -pe "s/Clutter.LOCK_MASK/Clutter.ModifierType.LOCK_MASK/g;" \
+    -pe "s/Clutter.META_MASK/Clutter.ModifierType.META_MASK/g;" \
+    -pe "s/Clutter.MOD1_MASK/Clutter.ModifierType.MOD1_MASK/g;" \
+    -pe "s/Clutter.MOD2_MASK/Clutter.ModifierType.MOD2_MASK/g;" \
+    -pe "s/Clutter.MOD3_MASK/Clutter.ModifierType.MOD3_MASK/g;" \
+    -pe "s/Clutter.MOD4_MASK/Clutter.ModifierType.MOD4_MASK/g;" \
+    -pe "s/Clutter.MOD5_MASK/Clutter.ModifierType.MOD5_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_MASK/Clutter.ModifierType.MODIFIER_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_13_MASK/Clutter.ModifierType.MODIFIER_RESERVED_13_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_14_MASK/Clutter.ModifierType.MODIFIER_RESERVED_14_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_15_MASK/Clutter.ModifierType.MODIFIER_RESERVED_15_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_16_MASK/Clutter.ModifierType.MODIFIER_RESERVED_16_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_17_MASK/Clutter.ModifierType.MODIFIER_RESERVED_17_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_18_MASK/Clutter.ModifierType.MODIFIER_RESERVED_18_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_19_MASK/Clutter.ModifierType.MODIFIER_RESERVED_19_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_20_MASK/Clutter.ModifierType.MODIFIER_RESERVED_20_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_21_MASK/Clutter.ModifierType.MODIFIER_RESERVED_21_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_22_MASK/Clutter.ModifierType.MODIFIER_RESERVED_22_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_23_MASK/Clutter.ModifierType.MODIFIER_RESERVED_23_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_24_MASK/Clutter.ModifierType.MODIFIER_RESERVED_24_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_25_MASK/Clutter.ModifierType.MODIFIER_RESERVED_25_MASK/g;" \
+    -pe "s/Clutter.MODIFIER_RESERVED_29_MASK/Clutter.ModifierType.MODIFIER_RESERVED_29_MASK/g;" \
+    -pe "s/Clutter.RELEASE_MASK/Clutter.ModifierType.RELEASE_MASK/g;" \
+    -pe "s/Clutter.SHIFT_MASK/Clutter.ModifierType.SHIFT_MASK/g;" \
+    -pe "s/Clutter.SUPER_MASK/Clutter.ModifierType.SUPER_MASK/g;" \
+\
+    -pe "s/import gst\n/from gi.repository import Gst\n/g;" \
+    -pe "s/(?<!\.)gst\./Gst\./g;" \
+    -pe "s/Gst.element_factory_find/Gst.ElementFactory.find/g;" \
+    -pe "s/Gst.element_factory_make/Gst.ElementFactory.make/g;" \
+    -pe "s/Gst.caps_from_string/Gst.Caps.from_string/g;" \
+    -pe "s/Gst.STATE_CHANGE_/Gst.StateChangeReturn./g;" \
+    -pe "s/Gst.STATE_/Gst.State./g;" \
+    -pe "s/Gst.MESSAGE_/Gst.MessageType./g;" \
+    -pe "s/Gst.FORMAT_/Gst.Format./g;" \
+    -pe "s/Gst.SEEK_FLAG_/Gst.SeekFlags./g;" \
+    -pe "s/Gst.SEEK_TYPE_/Gst.SeekType./g;" \
+    -pe "s/Gst.LEVEL_/Gst.DebugLevel./g;" \
+    -pe "s/Gst.URI_/Gst.URIType./g;" \
+    -pe "s/Gst.element_make_from_uri/Gst.Element.make_from_uri/g;" \
+    -pe "s/Gst.event_new_seek/Gst.Event.new_seek/g;" \
+    -pe "s/Gst.GhostPad\(/Gst.GhostPad.new\(/g;" \
+    $f
+done
+
+