commit aa73189be6fbcd58e856063c944667e25f40b932
Author: Zach Goldberg <zgold@bluesata.internal.invitemedia.com>
Date:   Wed Nov 18 13:38:42 2009 -0500

    Implement basic callbacks in PyGI

diff --git a/Makefile.am b/Makefile.am
index cbe9000..8a247d7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,7 @@ ACLOCAL_AMFLAGS = -I m4
 
 AM_CFLAGS = \
 	 -Wall \
-	 -g
+	 -g $(FFI_CFLAGS)
 
 SUBDIRS = \
 	gi \
diff --git a/configure.ac b/configure.ac
index 33f30e9..e3c065e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -33,6 +33,8 @@ CPPFLAGS+="${PYTHON_INCLUDES}"
 AC_CHECK_HEADER(Python.h, , AC_MSG_ERROR(Python headers not found))
 CPPFLAGS="${save_CPPFLAGS}"
 
+#libffi
+PKG_CHECK_MODULES(FFI, libffi >= 3.0)
 
 # GNOME
 PKG_CHECK_MODULES(GNOME,
diff --git a/gi/Makefile.am b/gi/Makefile.am
index bda8a34..4536679 100644
--- a/gi/Makefile.am
+++ b/gi/Makefile.am
@@ -36,6 +36,8 @@ _gi_la_SOURCES = \
 	pygi-type.h \
 	pygi-boxed.c \
 	pygi-boxed.h \
+	pygi-closure.c \
+	pygi-closure.h \
 	pygi.h \
 	pygi-private.h \
 	pygobject-external.h \
diff --git a/gi/pygi-argument.c b/gi/pygi-argument.c
index edbb31f..3bc53cd 100644
--- a/gi/pygi-argument.c
+++ b/gi/pygi-argument.c
@@ -372,8 +372,11 @@ check_number_release:
 
             switch (info_type) {
                 case GI_INFO_TYPE_CALLBACK:
-                    /* TODO */
-                    PyErr_SetString(PyExc_NotImplementedError, "callback marshalling is not supported yet");
+                    if (!PyCallable_Check(object)) {
+                        PyErr_Format(PyExc_TypeError, "Must be callable, not %s",
+                                object->ob_type->tp_name);
+                        retval = 0;
+                    }
                     break;
                 case GI_INFO_TYPE_ENUM:
                     retval = _pygi_g_registered_type_info_check_object(
@@ -626,6 +629,9 @@ _pygi_argument_from_object (PyObject   *object,
     GArgument arg;
     GITypeTag type_tag;
 
+    GSList *call_free_list = NULL; /* list of PyGiCallbackInvokeInfo* */
+    GSList *data_for_notify = NULL; /* list of PyGiCallbackInfo* */
+
     type_tag = g_type_info_get_tag(type_info);
 
     switch (type_tag) {
@@ -867,17 +873,25 @@ array_item_error:
         }
         case GI_TYPE_TAG_INTERFACE:
         {
-            GIBaseInfo *info;
+            GIBaseInfo *interface_info;
             GIInfoType info_type;
 
-            info = g_type_info_get_interface(type_info);
-            info_type = g_base_info_get_type(info);
+            interface_info = g_type_info_get_interface(type_info);
+            info_type = g_base_info_get_type(interface_info);
 
             switch (info_type) {
                 case GI_INFO_TYPE_CALLBACK:
-                    PyErr_SetString(PyExc_NotImplementedError, "callback marshalling is not supported yet");
-                    /* TODO */
-                    break;
+		    {
+			
+			if (!_pygi_make_native_closure((GICallableInfo*)interface_info,
+						       type_info,
+						       object,
+						       &(arg.v_pointer)))
+			    {
+				// TODO: Raise an errorg
+			    }
+			break;
+		    }
                 case GI_INFO_TYPE_BOXED:
                 case GI_INFO_TYPE_STRUCT:
                 {
@@ -972,7 +986,7 @@ array_item_error:
                 default:
                     g_assert_not_reached();
             }
-            g_base_info_unref(info);
+            g_base_info_unref(interface_info);
             break;
         }
         case GI_TYPE_TAG_GLIST:
@@ -1640,7 +1654,7 @@ _pygi_argument_release (GArgument   *arg,
         case GI_TYPE_TAG_UTF8:
             if ((direction == GI_DIRECTION_IN && transfer == GI_TRANSFER_NOTHING)
                     || (direction == GI_DIRECTION_OUT && transfer == GI_TRANSFER_EVERYTHING)) {
-                g_free(arg->v_string);
+            //    g_free(arg->v_string);
             }
             break;
         case GI_TYPE_TAG_ARRAY:
diff --git a/gi/pygi-closure.c b/gi/pygi-closure.c
new file mode 100644
index 0000000..2ed82fa
--- /dev/null
+++ b/gi/pygi-closure.c
@@ -0,0 +1,136 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * vim: tabstop=4 shiftwidth=4 expandtab
+ *
+ *   pygi-closure.c: PyGI Native 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ */
+
+#include "pygi-private.h"
+
+#include <string.h>
+#include <time.h>
+
+#include <datetime.h>
+#include <pygobject.h>
+
+void _pygi_handle_closure(ffi_cif *cif, void *result, void **args, void *userdata){
+  PyGILState_STATE gstate;
+  PyGINativeClosure *closure = userdata;
+  gint n_args, i;
+  GITypeTag return_tag;
+  GIArgInfo *arg_info;
+  GITypeInfo *return_type;
+  GITypeInfo *arg_type;
+  GITypeTag tag;
+  GITransfer arg_transfer;
+  GITransfer return_transfer;
+  GArgument  return_arg;
+  PyObject *retval;
+  PyObject *pyargs;
+  PyObject *pyarg;
+
+
+  // Lock the GIL as we are coming into this code without and we
+  // may be executing python code 
+  gstate = PyGILState_Ensure ();
+
+  n_args = g_callable_info_get_n_args (closure->info);
+  return_type = g_callable_info_get_return_type (closure->info);
+  return_tag = g_type_info_get_tag (return_type);
+  return_transfer = g_callable_info_get_caller_owns(closure->info);
+  
+  pyargs = PyTuple_New(n_args);
+
+  if (pyargs == NULL){
+     return;
+  }
+
+  for (i = 0; i < n_args; i++)
+    {
+      arg_info = g_callable_info_get_arg (closure->info, i);
+      arg_type = g_arg_info_get_type (arg_info);
+      arg_transfer = g_arg_info_get_ownership_transfer(arg_info);
+
+      pyarg = _pygi_argument_to_object (args[i],				
+					arg_type,
+					arg_transfer);
+
+      PyTuple_SetItem(pyargs, i, pyarg);
+    }
+
+
+  retval = PyEval_CallObject((PyObject *)closure->function, pyargs);
+  Py_DECREF(pyargs);
+
+  if (retval == NULL){
+     result = NULL;
+     PyGILState_Release (gstate);
+     return;
+  }
+
+  *(GArgument*)result = _pygi_argument_from_object(retval, return_type, return_transfer);
+
+  PyGILState_Release (gstate);
+}
+
+
+PyGINativeClosure*
+_pygi_make_native_closure (GICallableInfo* info,
+			   GITypeInfo* type_info, 
+			   PyObject *function, 
+			   gpointer* closure)
+{
+  /* Code copied with pythonic modifications from seed's make_native_closure() method */
+  ffi_cif *cif;
+  ffi_closure *fficlosure;
+  PyObject* cache;
+  PyObject* dict;
+  PyGINativeClosure *invoke_closure;
+
+  // TODO: Assert that function is in fact a function
+  Py_XINCREF(function);
+
+
+  if (PyObject_HasAttrString(function, "__pygi_native_closure")){
+    cache =  (PyObject*) PyObject_GetAttrString(function, 
+                                                          "__pygi_native_closure");
+    closure = PyCObject_AsVoidPtr(cache);
+  }else{      
+    cif = g_new0 (ffi_cif, 1);
+
+    invoke_closure = g_slice_new0(PyGINativeClosure);
+    invoke_closure->info = (GICallableInfo *) g_base_info_ref ((GIBaseInfo *) info);  
+    invoke_closure->function = function;
+    invoke_closure->cif = cif;
+    invoke_closure->type_info = type_info;
+
+    fficlosure =
+        g_callable_info_prepare_closure (info, cif, _pygi_handle_closure,
+					 invoke_closure);
+    invoke_closure->closure = fficlosure;
+
+    *closure = invoke_closure->closure;
+
+
+    dict = PyObject_GetAttrString(function, "__dict__");
+    PyObject_SetItem(dict, PyString_FromString("__pygi_native_closure"),
+                           PyCObject_FromVoidPtr(closure, NULL));                       
+
+  }
+
+  return closure;
+}
diff --git a/gi/pygi-closure.h b/gi/pygi-closure.h
new file mode 100644
index 0000000..768cc85
--- /dev/null
+++ b/gi/pygi-closure.h
@@ -0,0 +1,50 @@
+/* -*- 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ */
+
+#ifndef __PYGI_CLOSURE_H__
+#define __PYGI_CLOSURE_H__
+
+#include <Python.h>
+#include <girffi.h>
+#include <ffi.h>
+
+G_BEGIN_DECLS
+
+
+/* Private */
+
+typedef struct _PyGiNativeClosure
+{
+    GICallableInfo *info;
+    GITypeInfo *type_info;
+    PyObject *function;
+    
+    ffi_closure *closure;
+    ffi_cif *cif;
+} PyGINativeClosure; // == GjsCallbackInvokeInfo
+
+
+
+void _pygi_handle_closure(ffi_cif *cif, void *result, void 
+			  **args, void *userdata);
+
+PyGINativeClosure*
+_pygi_make_native_closure (GICallableInfo* info, GITypeInfo* type_info, 
+			   PyObject *function, gpointer *closure);
+#endif
diff --git a/gi/pygi-info.c b/gi/pygi-info.c
index 18981a2..4d8086c 100644
--- a/gi/pygi-info.c
+++ b/gi/pygi-info.c
@@ -179,8 +179,8 @@ _pygi_info_new (GIBaseInfo *info)
             type = &PyGIFunctionInfo_Type;
             break;
         case GI_INFO_TYPE_CALLBACK:
-            PyErr_SetString(PyExc_NotImplementedError, "GICallbackInfo bindings not implemented");
-            return NULL;
+	    type = &PyGICallableInfo_Type;
+	    break;
         case GI_INFO_TYPE_STRUCT:
             type = &PyGIStructInfo_Type;
             break;
diff --git a/gi/pygi-private.h b/gi/pygi-private.h
index 562c8ab..7ad0321 100644
--- a/gi/pygi-private.h
+++ b/gi/pygi-private.h
@@ -24,6 +24,7 @@
 #include "pygi-boxed.h"
 #include "pygi-argument.h"
 #include "pygi-type.h"
+#include "pygi-closure.h"
 
 G_BEGIN_DECLS
 
@@ -63,6 +64,26 @@ g_array_get_element_size (GArray *array)
 }
 #endif
 
+#if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION == 20
+/* Private stuff copied from glib-2.20.x sources */
+struct _GRealArray
+{
+    guint8 *data;
+    guint   len;
+    guint   alloc;
+    guint   elt_size;
+    guint   zero_terminated : 1;
+    guint   clear : 1;
+};
+
+static inline guint
+g_array_get_element_size (GArray *array)
+{
+    struct _GRealArray *rarray = (struct _GRealArray*) array;
+    return rarray->elt_size;
+}
+#endif
+
 /* Redefine g_array_index because we want it to return the i-th element, casted
  * to the type t, of the array a, and not the i-th element of the array a
  * casted to the type t. */
diff --git a/tests/libtestgi.c b/tests/libtestgi.c
index a9edf21..67b9d15 100644
--- a/tests/libtestgi.c
+++ b/tests/libtestgi.c
@@ -2875,6 +2875,7 @@ test_gi__overrides_struct_return (void)
 }
 
 
+
 G_DEFINE_TYPE (TestGIOverridesObject, test_gi_overrides_object, G_TYPE_OBJECT);
 
 static void
@@ -2922,3 +2923,82 @@ test_gi__overrides_object_return (void)
 {
     return g_object_new (TESTGI_TYPE_OVERRIDES_OBJECT, NULL);
 }
+
+/**
+ * test_gi_simple_callback:
+ * Test basic callback with no arguements
+ * Returns::
+ */
+void test_gi_simple_callback(SimpleCallback callback)
+{
+  callback();
+  return;
+}
+
+/**
+ * test_gi_native_type_args_callback:
+ * Test basic callback with a couple native typed arguements (int, str)
+ * Returns::
+ */
+void test_gi_native_type_args_callback(NativeTypedArgsCallback callback)
+{
+  callback(1337, "l33t");
+  return;
+}
+
+/**
+ * test_gi_simple_struct_callback:
+ * Tests calling a callback with a simple struct as an arguement
+ * Returns::
+ */
+void test_gi_simple_struct_callback(SimpleStructCallback callback)
+{
+    static TestGISimpleStruct *struct_ = NULL;
+
+    if (struct_ == NULL) {
+        struct_ = g_new(TestGISimpleStruct, 1);
+
+        struct_->long_ = 6;
+        struct_->int8 = 7;
+    }
+
+    callback(struct_);
+    return;
+}
+
+/**
+ * test_gi_native_return_callback:
+ * Test a callback that returns a native value
+ * Returns: (transfer none):
+ */
+int test_gi_native_return_callback(SimpleReturnCallback callback)
+{
+    return callback();
+}
+
+/**
+ * test_gi_native_return_callback:
+ * Test a callback that returns a native value
+ * Returns: (transfer none):
+ */
+
+/**
+ * SimpleReturnCallback:
+ * Returns: (transfer none):
+ */
+StructReturnCallback;
+
+TestGISimpleStruct*
+test_gi_struct_return_callback(StructReturnCallback callback);
+
+
+/**
+ * test_gi_struct_return_callback:
+ * Test a callback that returns a native value
+ * Returns: (transfer none):
+ */
+TestGISimpleStruct* test_gi_struct_return_callback(StructReturnCallback callback)
+{
+    return callback();
+}
+
diff --git a/tests/libtestgi.h b/tests/libtestgi.h
index a08a7fc..6f9f5ae 100644
--- a/tests/libtestgi.h
+++ b/tests/libtestgi.h
@@ -625,4 +625,26 @@ glong test_gi_overrides_object_method (TestGIOverridesObject *object);
 
 TestGIOverridesObject *test_gi__overrides_object_return (void);
 
+/* Callbacks */
+typedef void (*SimpleCallback)();
+typedef void (*NativeTypedArgsCallback)(gint arg1, gchar* arg2);
+typedef void (*SimpleStructCallback)(TestGISimpleStruct *val1);
+typedef int (*SimpleReturnCallback)();
+
+
+void test_gi_simple_callback(SimpleCallback callback);
+void test_gi_native_type_args_callback(NativeTypedArgsCallback callback);
+void test_gi_simple_struct_callback(SimpleStructCallback callback);
+
+int test_gi_native_return_callback(SimpleReturnCallback callback);
+
+/**
+ * SimpleReturnCallback:
+ * Returns: (transfer none):
+ */
+typedef TestGISimpleStruct* (*StructReturnCallback)();
+
+TestGISimpleStruct*
+test_gi_struct_return_callback(StructReturnCallback callback);
+
 #endif /* __TEST_GI_H__ */
diff --git a/tests/test_gi.py b/tests/test_gi.py
index 36337ed..27158e0 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -1392,3 +1392,56 @@ class TestOverrides(unittest.TestCase):
         object_ = TestGI.overrides_object_return()
 
         self.assertTrue(isinstance(object_, TestGI.OverridesObject))
+
+class TestCallbacks(unittest.TestCase):
+
+    def simple_callback(self):
+        self.simple_callback.__dict__["called"] = True
+
+    def native_args_callback(self, intval, strval):
+        self.native_args_callback.__dict__["called"] = True
+	self.assertEquals(intval, 1337)
+	self.assertEquals(strval, "l33t")
+
+    def simple_struct_callback(self, obj):
+        self.simple_struct_callback.__dict__["called"] = True
+        self.assertTrue(isinstance(obj, TestGI.SimpleStruct))
+        self.assertEqual(obj.long_, 6)
+        self.assertEqual(obj.int8, 7)
+
+    def native_return_callback(self):
+        return 1337
+
+    def struct_return_callback(self):
+        return TestGI.SimpleStruct()
+
+    def test_simple_callback(self):
+	TestGI.simple_callback(self.simple_callback)
+        self.assertTrue(self.simple_callback.called, "Expected the simple callback to be called")
+
+    # FIXME: Turns out this is really hard to check in python.  On the TODO
+    def _test_invalid_arg_count(self):
+        exception = False
+	try:
+            TestGI.simple_callback(self.native_args_callback)
+	except TypeError:
+	    # We tried to call the python function from C and got an error... meaning
+	    # that we didnt catch the error before calling C.  This should not happen.
+	    pass
+	except:
+            exception = True
+	self.assertTrue(exception, "Expected an exception when calling a callback with wrong signature")
+
+    def test_native_args_callback(self):
+        TestGI.native_type_args_callback(self.native_args_callback)
+        self.assertTrue(self.native_args_callback.called, "Expected native type callback to be called")
+	   
+    def test_struct_arg_callback(self):
+        TestGI.simple_struct_callback(self.simple_struct_callback)
+        self.assertTrue(self.simple_struct_callback.called, "Expected simple struct callback to be called")
+
+    def test_native_return_callback(self):
+        self.assertEqual(TestGI.native_return_callback(self.native_return_callback), 1337)
+
+    def test_struct_return_callback(self):
+        self.assertTrue(isinstance(TestGI.struct_return_callback(self.struct_return_callback), TestGI.SimpleStruct))
