/* -*- 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),
#if PY_VERSION_HEX < 0x03000000
                                  offsetof(PyTypeObject, tp_print),
#endif
    };
    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) {
      gboolean was_floating = FALSE;

      if (G_VALUE_HOLDS_OBJECT (&ret)) {
        GObject *obj = g_value_get_object (&ret);
        was_floating = g_object_is_floating (obj);
      }
	py_ret = pyg_value_as_pyobject(&ret, TRUE);
      if (!was_floating)
	      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;
}
