Commit d03696c1 authored by John (J5) Palmieri's avatar John (J5) Palmieri Committed by Martin Pitt

Add a ccallback type which is used to invoke callbacks passed to a vfunc

Used when overriding methods like gtk_container_forall wich pass in a
callback that needs to be executed on internal children:
    def do_forall(self, callback, userdata):
        callback(self.custom_child, userdata)

https://bugzilla.gnome.org/show_bug.cgi?id=644926Co-authored-by: default avatarTomeu Vizoso <tomeu.vizoso@collabora.co.uk>
Co-authored-by: default avatarSimon Schampijer <simon@laptop.org>
Signed-off-by: default avatarMartin Pitt <martinpitt@gnome.org>
parent db7e1d07
......@@ -54,6 +54,8 @@ _gi_la_SOURCES = \
pygi-boxed.h \
pygi-closure.c \
pygi-closure.h \
pygi-ccallback.c \
pygi-ccallback.h \
pygi-callbacks.c \
pygi-callbacks.h \
pygi.h \
......
......@@ -492,6 +492,7 @@ PYGLIB_MODULE_START(_gi, "_gi")
_pygi_info_register_types (module);
_pygi_struct_register_types (module);
_pygi_boxed_register_types (module);
_pygi_ccallback_register_types (module);
_pygi_argument_init();
api = PYGLIB_CPointer_WrapPointer ( (void *) &CAPI, "gi._API");
......
......@@ -42,8 +42,10 @@ from ._gi import \
ConstantInfo, \
StructInfo, \
UnionInfo, \
CallbackInfo, \
Struct, \
Boxed, \
CCallback, \
enum_add, \
enum_register_new_gtype_and_add, \
flags_add, \
......@@ -155,6 +157,9 @@ class IntrospectionModule(object):
if not issubclass(parent, interface))
bases = (parent,) + interfaces
metaclass = GObjectMeta
elif isinstance(info, CallbackInfo):
bases = (CCallback,)
metaclass = GObjectMeta
elif isinstance(info, InterfaceInfo):
bases = (_gobject.GInterface,)
metaclass = GObjectMeta
......
......@@ -1547,17 +1547,7 @@ _pygi_argument_to_object (GIArgument *arg,
switch (info_type) {
case GI_INFO_TYPE_CALLBACK:
{
/* There is no way we can support a callback return
* as we are never sure if the callback was set from C
* or Python. API that return callbacks are broken
* so we print a warning and send back a None
*/
g_warning ("You are trying to use an API which returns a callback."
"Callback returns can not be supported. Returning None instead.");
object = Py_None;
Py_INCREF (object);
break;
g_assert_not_reached();
}
case GI_INFO_TYPE_BOXED:
case GI_INFO_TYPE_STRUCT:
......
......@@ -1233,6 +1233,7 @@ _arg_name_list_generate (PyGICallableCache *callable_cache)
arg_cache = callable_cache->args_cache[i];
if (arg_cache->meta_type != PYGI_META_ARG_TYPE_CHILD &&
arg_cache->meta_type != PYGI_META_ARG_TYPE_CLOSURE &&
(arg_cache->direction == PYGI_DIRECTION_FROM_PYTHON ||
arg_cache->direction == PYGI_DIRECTION_BIDIRECTIONAL)) {
......@@ -1331,8 +1332,24 @@ _args_cache_generate (GICallableInfo *callable_info,
gboolean is_caller_allocates = FALSE;
gssize py_arg_index = -1;
arg_info =
g_callable_info_get_arg (callable_info, i);
arg_info = g_callable_info_get_arg (callable_info, i);
if (g_arg_info_get_closure (arg_info) == i) {
arg_cache = _arg_cache_alloc ();
callable_cache->args_cache[arg_index] = arg_cache;
arg_cache->arg_name = g_base_info_get_name ((GIBaseInfo *) arg_info);
arg_cache->direction = PYGI_DIRECTION_FROM_PYTHON;
arg_cache->meta_type = PYGI_META_ARG_TYPE_CLOSURE;
arg_cache->c_arg_index = i;
callable_cache->n_from_py_args++;
g_base_info_unref ( (GIBaseInfo *)arg_info);
continue;
}
/* For vfuncs and callbacks our marshalling directions
are reversed */
......@@ -1430,7 +1447,7 @@ arg_err:
}
PyGICallableCache *
_pygi_callable_cache_new (GICallableInfo *callable_info)
_pygi_callable_cache_new (GICallableInfo *callable_info, gboolean is_ccallback)
{
PyGICallableCache *cache;
GIInfoType type = g_base_info_get_type ( (GIBaseInfo *)callable_info);
......@@ -1454,7 +1471,10 @@ _pygi_callable_cache_new (GICallableInfo *callable_info)
} else if (type == GI_INFO_TYPE_VFUNC) {
cache->function_type = PYGI_FUNCTION_TYPE_VFUNC;
} else if (type == GI_INFO_TYPE_CALLBACK) {
cache->function_type = PYGI_FUNCTION_TYPE_CALLBACK;
if (is_ccallback)
cache->function_type = PYGI_FUNCTION_TYPE_CCALLBACK;
else
cache->function_type = PYGI_FUNCTION_TYPE_CALLBACK;
} else {
cache->function_type = PYGI_FUNCTION_TYPE_METHOD;
}
......
......@@ -68,7 +68,8 @@ typedef enum {
PYGI_META_ARG_TYPE_PARENT,
PYGI_META_ARG_TYPE_CHILD,
PYGI_META_ARG_TYPE_CHILD_NEEDS_UPDATE,
PYGI_META_ARG_TYPE_CHILD_WITH_PYARG
PYGI_META_ARG_TYPE_CHILD_WITH_PYARG,
PYGI_META_ARG_TYPE_CLOSURE,
} PyGIMetaArgType;
/*
......@@ -82,7 +83,8 @@ typedef enum {
PYGI_FUNCTION_TYPE_METHOD,
PYGI_FUNCTION_TYPE_CONSTRUCTOR,
PYGI_FUNCTION_TYPE_VFUNC,
PYGI_FUNCTION_TYPE_CALLBACK
PYGI_FUNCTION_TYPE_CALLBACK,
PYGI_FUNCTION_TYPE_CCALLBACK,
} PyGIFunctionType;
/*
......@@ -187,7 +189,8 @@ struct _PyGICallableCache
void _pygi_arg_cache_clear (PyGIArgCache *cache);
void _pygi_callable_cache_free (PyGICallableCache *cache);
PyGICallableCache *_pygi_callable_cache_new (GICallableInfo *callable_info);
PyGICallableCache *_pygi_callable_cache_new (GICallableInfo *callable_info,
gboolean is_ccallback);
G_END_DECLS
......
/* -*- Mode: C; c-basic-offset: 4 -*-
* vim: tabstop=4 shiftwidth=4 expandtab
*
* Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>, Red Hat, Inc.
*
* pygi-boxed-closure.c: wrapper to handle GClosure box types with C callbacks.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "pygi-private.h"
#include <pygobject.h>
#include <girepository.h>
#include <pyglib-python-compat.h>
static PyObject *
_ccallback_call(PyGICCallback *self, PyObject *args, PyObject *kwargs)
{
PyObject *result;
GCallback *func;
if (self->cache == NULL) {
self->cache = _pygi_callable_cache_new (self->info, TRUE);
if (self->cache == NULL)
return NULL;
}
result = pygi_callable_info_invoke( (GIBaseInfo *) self->info,
args,
kwargs,
self->cache,
self->callback,
self->user_data);
return result;
}
PYGLIB_DEFINE_TYPE("gi.CCallback", PyGICCallback_Type, PyGICCallback);
PyObject *
_pygi_ccallback_new (GCallback callback,
gpointer user_data,
GIScopeType scope,
GIFunctionInfo *info,
GDestroyNotify destroy_notify)
{
PyGICCallback *self;
if (!callback) {
Py_RETURN_NONE;
}
self = (PyGICCallback *) PyGICCallback_Type.tp_alloc (&PyGICCallback_Type, 0);
if (self == NULL) {
return NULL;
}
self->callback = (GCallback) callback;
self->user_data = user_data;
self->scope = scope;
self->destroy_notify_func = destroy_notify;
self->info = g_base_info_ref( (GIBaseInfo *) info);
return (PyObject *) self;
}
static void
_ccallback_dealloc (PyGICCallback *self)
{
g_base_info_unref ( (GIBaseInfo *)self->info);
}
void
_pygi_ccallback_register_types (PyObject *m)
{
Py_TYPE(&PyGICCallback_Type) = &PyType_Type;
PyGICCallback_Type.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE);
PyGICCallback_Type.tp_dealloc = (destructor) _ccallback_dealloc;
PyGICCallback_Type.tp_call = (ternaryfunc) _ccallback_call;
if (PyType_Ready (&PyGICCallback_Type))
return;
if (PyModule_AddObject (m, "CCallback", (PyObject *) &PyGICCallback_Type))
return;
}
/* -*- Mode: C; c-basic-offset: 4 -*-
* vim: tabstop=4 shiftwidth=4 expandtab
*
* Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>, Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#ifndef __PYGI_CCLOSURE_H__
#define __PYGI_CCLOSURE_H__
#include <Python.h>
G_BEGIN_DECLS
extern PyTypeObject PyGICCallback_Type;
PyObject * _pygi_ccallback_new (GCallback callback,
gpointer user_data,
GIScopeType scope,
GIFunctionInfo *info,
GDestroyNotify destroy_notify);
void _pygi_ccallback_register_types (PyObject *m);
G_END_DECLS
#endif /* __PYGI_CCLOSURE_H__ */
......@@ -155,7 +155,8 @@ _pygi_closure_convert_ffi_arguments (GICallableInfo *callable_info, void **args)
g_args[i].v_double = * (double *) args[i];
g_base_info_unref (interface);
break;
} else if (interface_type == GI_INFO_TYPE_STRUCT) {
} else if (interface_type == GI_INFO_TYPE_STRUCT ||
interface_type == GI_INFO_TYPE_CALLBACK) {
g_args[i].v_pointer = * (gpointer *) args[i];
g_base_info_unref (interface);
break;
......@@ -167,6 +168,7 @@ _pygi_closure_convert_ffi_arguments (GICallableInfo *callable_info, void **args)
case GI_TYPE_TAG_GHASH:
case GI_TYPE_TAG_GLIST:
case GI_TYPE_TAG_GSLIST:
case GI_TYPE_TAG_VOID:
g_args[i].v_pointer = * (gpointer *) args[i];
break;
default:
......@@ -188,6 +190,9 @@ _pygi_closure_convert_arguments (GICallableInfo *callable_info, void **args,
int n_in_args = 0;
int n_out_args = 0;
int i;
int user_data_arg = -1;
int destroy_notify_arg = -1;
GIArgument *g_args = NULL;
*py_args = NULL;
......@@ -200,6 +205,10 @@ _pygi_closure_convert_arguments (GICallableInfo *callable_info, void **args,
g_args = _pygi_closure_convert_ffi_arguments (callable_info, args);
for (i = 0; i < n_args; i++) {
/* Special case callbacks and skip over userdata and Destroy Notify */
if (i == user_data_arg || i == destroy_notify_arg)
continue;
GIArgInfo *arg_info = g_callable_info_get_arg (callable_info, i);
GIDirection direction = g_arg_info_get_direction (arg_info);
......@@ -220,6 +229,45 @@ _pygi_closure_convert_arguments (GICallableInfo *callable_info, void **args,
value = user_data;
Py_INCREF (value);
}
} else if (direction == GI_DIRECTION_IN &&
arg_tag == GI_TYPE_TAG_INTERFACE) {
/* Handle callbacks as a special case */
GIBaseInfo *info;
GIInfoType info_type;
info = g_type_info_get_interface (arg_type);
info_type = g_base_info_get_type (info);
arg = (GIArgument*) &g_args[i];
if (info_type == GI_INFO_TYPE_CALLBACK) {
gpointer user_data = NULL;
GDestroyNotify destroy_notify = NULL;
GIScopeType scope = g_arg_info_get_scope(arg_info);
user_data_arg = g_arg_info_get_closure(arg_info);
destroy_notify_arg = g_arg_info_get_destroy(arg_info);
if (user_data_arg != -1)
user_data = g_args[user_data_arg].v_pointer;
if (destroy_notify_arg != -1)
user_data = (GDestroyNotify) g_args[destroy_notify_arg].v_pointer;
value = _pygi_ccallback_new(arg->v_pointer,
user_data,
scope,
(GIFunctionInfo *) info,
destroy_notify);
} else
value = _pygi_argument_to_object (arg, arg_type, transfer);
g_base_info_unref (info);
if (value == NULL) {
g_base_info_unref (arg_type);
g_base_info_unref (arg_info);
goto error;
}
} else {
if (direction == GI_DIRECTION_IN)
arg = (GIArgument*) &g_args[i];
......
......@@ -37,6 +37,8 @@ typedef struct _PyGIInvokeState
GError *error;
gboolean failed;
gpointer user_data;
} PyGIInvokeState;
G_END_DECLS
......
......@@ -29,7 +29,8 @@
static inline gboolean
_invoke_callable (PyGIInvokeState *state,
PyGICallableCache *cache,
GICallableInfo *callable_info)
GICallableInfo *callable_info,
GCallback function_ptr)
{
GError *error;
gint retval;
......@@ -48,6 +49,17 @@ _invoke_callable (PyGIInvokeState *state,
cache->n_to_py_args,
&state->return_arg,
&error);
else if (g_base_info_get_type (callable_info) == GI_INFO_TYPE_CALLBACK)
retval = g_callable_info_invoke (callable_info,
function_ptr,
state->in_args,
cache->n_from_py_args,
state->out_args,
cache->n_to_py_args,
&state->return_arg,
FALSE,
FALSE,
&error);
else
retval = g_function_info_invoke ( callable_info,
state->in_args,
......@@ -149,10 +161,12 @@ _py_args_combine_and_check_length (const gchar *function_name,
GSList *l;
n_py_args = PyTuple_GET_SIZE (py_args);
n_py_kwargs = PyDict_Size (py_kwargs);
if (py_kwargs == NULL)
n_py_kwargs = 0;
else
n_py_kwargs = PyDict_Size (py_kwargs);
n_expected_args = g_slist_length (arg_name_list);
if (n_py_kwargs == 0 && n_py_args == n_expected_args) {
return py_args;
}
......@@ -170,7 +184,9 @@ _py_args_combine_and_check_length (const gchar *function_name,
return NULL;
}
if (!_check_for_unexpected_kwargs (function_name, arg_name_hash, py_kwargs)) {
if (n_py_kwargs > 0 && !_check_for_unexpected_kwargs (function_name,
arg_name_hash,
py_kwargs)) {
Py_DECREF (py_args);
return NULL;
}
......@@ -191,7 +207,7 @@ _py_args_combine_and_check_length (const gchar *function_name,
PyObject *py_arg_item, *kw_arg_item = NULL;
const gchar *arg_name = l->data;
if (arg_name != NULL) {
if (n_py_kwargs > 0 && arg_name != NULL) {
/* NULL means this argument has no keyword name */
/* ex. the first argument to a method or constructor */
kw_arg_item = PyDict_GetItemString (py_kwargs, arg_name);
......@@ -400,7 +416,10 @@ _invoke_marshal_in_args (PyGIInvokeState *state, PyGICallableCache *cache)
state->args[i] = &(state->in_args[in_count]);
in_count++;
if (arg_cache->meta_type > 0)
if (arg_cache->meta_type == PYGI_META_ARG_TYPE_CLOSURE) {
state->args[i]->v_pointer = state->user_data;
continue;
} else if (arg_cache->meta_type != PYGI_META_ARG_TYPE_PARENT)
continue;
if (arg_cache->py_arg_index >= state->n_py_in_args) {
......@@ -611,34 +630,44 @@ _invoke_marshal_out_args (PyGIInvokeState *state, PyGICallableCache *cache)
}
PyObject *
_wrap_g_callable_info_invoke (PyGIBaseInfo *self,
PyObject *py_args,
PyObject *kwargs)
pygi_callable_info_invoke (GIBaseInfo *info, PyObject *py_args,
PyObject *kwargs, PyGICallableCache *cache,
GCallback function_ptr, gpointer user_data)
{
PyGIInvokeState state = { 0, };
PyObject *ret = NULL;
if (self->cache == NULL) {
self->cache = _pygi_callable_cache_new (self->info);
if (self->cache == NULL)
return NULL;
}
if (!_invoke_state_init_from_callable_cache (&state, self->cache, py_args, kwargs))
if (!_invoke_state_init_from_callable_cache (&state, cache, py_args, kwargs))
goto err;
if (!_invoke_marshal_in_args (&state, self->cache))
if (cache->function_type == PYGI_FUNCTION_TYPE_CCALLBACK)
state.user_data = user_data;
if (!_invoke_marshal_in_args (&state, cache))
goto err;
if (!_invoke_callable (&state, self->cache, self->info))
if (!_invoke_callable (&state, cache, info, function_ptr))
goto err;
pygi_marshal_cleanup_args_from_py_marshal_success (&state, self->cache);
pygi_marshal_cleanup_args_from_py_marshal_success (&state, cache);
ret = _invoke_marshal_out_args (&state, self->cache);
ret = _invoke_marshal_out_args (&state, cache);
if (ret)
pygi_marshal_cleanup_args_to_py_marshal_success (&state, self->cache);
pygi_marshal_cleanup_args_to_py_marshal_success (&state, cache);
err:
_invoke_state_clear (&state, self->cache);
_invoke_state_clear (&state, cache);
return ret;
}
PyObject *
_wrap_g_callable_info_invoke (PyGIBaseInfo *self, PyObject *py_args,
PyObject *kwargs)
{
if (self->cache == NULL) {
self->cache = _pygi_callable_cache_new (self->info, FALSE);
if (self->cache == NULL)
return NULL;
}
return pygi_callable_info_invoke (self->info, py_args, kwargs, self->cache, NULL, NULL);
}
......@@ -30,6 +30,9 @@
#include "pygi-invoke-state-struct.h"
G_BEGIN_DECLS
PyObject *pygi_callable_info_invoke (GIBaseInfo *info, PyObject *py_args,
PyObject *kwargs, PyGICallableCache *cache,
GCallback function_ptr, gpointer user_data);
PyObject *_wrap_g_callable_info_invoke (PyGIBaseInfo *self, PyObject *py_args,
PyObject *kwargs);
......
......@@ -26,6 +26,7 @@
#include "pygi-type.h"
#include "pygi-foreign.h"
#include "pygi-closure.h"
#include "pygi-ccallback.h"
#include "pygi-callbacks.h"
#include "pygi-property.h"
#include "pygi-signal-closure.h"
......
......@@ -55,6 +55,16 @@ typedef struct {
gsize size;
} PyGIBoxed;
typedef struct {
PyObject_HEAD
GCallback callback;
GIFunctionInfo *info;
gpointer user_data;
GIScopeType scope;
GDestroyNotify destroy_notify_func;
PyGICallableCache *cache;
} PyGICCallback;
typedef PyObject * (*PyGIArgOverrideToGIArgumentFunc) (PyObject *value,
GIInterfaceInfo *interface_info,
GITransfer transfer,
......
......@@ -1690,6 +1690,22 @@ class TestPythonGObject(unittest.TestCase):
GIMarshallingTests.SubSubObject.do_method_deep_hierarchy(sub_sub_sub_object, 5)
self.assertEqual(sub_sub_sub_object.props.int, 5)
def test_callback_in_vfunc(self):
class SubObject(GIMarshallingTests.Object):
def __init__(self):
GObject.GObject.__init__(self)
self.worked = False
def do_vfunc_with_callback(self, callback):
self.worked = callback(42) == 42
_object = SubObject()
_object.call_vfunc_with_callback()
self.assertTrue(_object.worked == True)
_object.worked = False
_object.call_vfunc_with_callback()
self.assertTrue(_object.worked == True)
class TestMultiOutputArgs(unittest.TestCase):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment