Integrating a GTK+ C widget into Python
Last year I wrote a GTK+ widget to show and modify the properties of any
GObject
. The widget was written in C for use in a C application. For another
project, I also have to modify the properties of GObject
s but this time the
application is written in Python using PyGObject. Here’s how I did it.
Python extension
Obviously, a widget that is written in pure C and does not come with any
introspection data is unknown to Python’s type system. For this reason, we must
write a small extension module, that instantiates the object. In my case, I want
to call egg_property_tree_view_new()
that either takes NULL
or an object
that is going to be observed. First we include headers for Python, Python
GObject access and our widget:
#include <Python.h>
#include <pygobject.h>
#include "egg-property-tree-view.h"
Our module provides one function called create_property_view
that takes a
GObject object and returns the constructed tree view object:
static PyObject *
create_property_view (PyObject *self,
PyObject *args)
{
PyObject *object;
GObject *observed;
GtkWidget *widget;
First, let’s check if an argument was passed:
if (!PyArg_ParseTuple (args, "O", &object))
return NULL;
If the argument is not None
, we unwrap the contained object with
pygobject_get()
:
observed = object == Py_None ? NULL : pygobject_get (object);
if ((observed != NULL) && (!G_IS_OBJECT (observed)))
goto err;
If everything is okay, we create a new widget with our C API, wrap it using
pygobject_new()
and return it:
widget = egg_property_tree_view_new (observed);
object = pygobject_new (G_OBJECT (widget));
return object;
If something unexpected happened, we throw a type error exception:
err:
PyErr_SetString (PyExc_TypeError,
"Argument must be a GObject");
return NULL;
}
Last but not least, we define our exported function and initialize our module.
Note, that you have to call init_pygobject()
for GTK+ 2 or
pygobject_init()
for GTK+ 3, or any attempt to call pygobject_foo()
will
fail badly.
static PyMethodDef methods[] = {
{ "create_property_view", create_property_view,
METH_VARARGS, "Create EggPropertyTreeView" },
{ NULL, NULL, 0, NULL }
};
PyMODINIT_FUNC
initfoomodule (void)
{
PyObject *module = Py_InitModule ("foomodule", methods);
if (module == NULL)
return;
init_pygobject ();
}
Build environment
Once we have our Python wrapper together, we need to build it. For distribution
purposes it is a good idea to use distutils
for that. Unfortunately, distutils
does not come with built-in support for pkg-config. Here I used a function
from the German Python forums that calls pkg-config and creates a dict1
for consumption by distutils.core.Extension
. The following setup.py
builds
the module using
from distutils.core import setup, Extension
from subprocess import PIPE, Popen
def pkgconfig(*packages):
flags = {
'-D': 'define_macros',
'-I': 'include_dirs',
'-L': 'library_dirs',
'-l': 'libraries'}
cmd = ['pkg-config', '--cflags', '--libs'] + list(packages)
proc = Popen(cmd, stdout=PIPE, stderr=PIPE)
output, error = proc.stdout.read(), proc.stderr.read()
if error:
raise ValueError(error)
config = {}
for token in output.split():
if token != '-pthread':
flag, value = token[:2], token[2:]
config.setdefault(flags[flag], []).append(value)
if 'define_macros' in config:
macros = [(name, None) for name in config['define_macros']]
config['define_macros'] = macros
return config
module = Extension('foomodule',
sources=['module/egg-property-cell-renderer.c',
'module/egg-property-tree-view.c',
'module/foomodule.c'],
extra_compile_args=['-std=c99'],
**pkgconfig('gtk+-2.0 pygobject-2.0'))
setup(
ext_modules=[module]
# Further package description
)
I had to change the code a bit, because GTK+ requires the -pthread
cflag, on which the token “parser” chokes on.
Glueing everything together
Now,
python setup.py install
builds our extension module and installs it into the global site-packages
or
– as I usually do – into a virtualenv. With everything in place, we can
happily mix and match objects created via GObject introspection or our own
wrappers:
import foomodule
from gi.repository import Gtk
window = Gtk.Window()
view = foomodule.create_property_view(window)
window.add(view)
window.show_all()
Gtk.main()
There is a disadvantage of a hand-written wrapper compared to the generated
introspection data: you have to translate every C method manually. However, in
most cases, widgets are entirely characterized by their properties. Hence, a
Python programmer can use object.set_properties
or object.props
to change
the behaviour of the widget instead of calling
object.set_certain_property(value)
.