Python is an excellent language for coding graphical interfaces. Because you can quickly write work code and do not require time-consuming compilation cycles, you can immediately start and run the interface, and you will soon be able to use these interfaces. By combining this with Python's ability to easily link to the native library, you can create an excellent environment.
Gnome-python is a software package that encapsulates GNOME and its associated libraries for Python. This allows you to use Python to write an application that looks exactly like the core GNOME application, and it takes only a fraction of the time it takes to write the application in C.
However, there is a disadvantage in programming without C. Most GNOME is written in C and must be encapsulated for widgets to be used in Python. This is a quick task for those who know how the encapsulation process works, but it is not automatic, and will not be encapsulated unless widgets belong to the core GNOME library or at least very useful. C programmers may have to write more complex code, but they did it first!
But that's not necessarily true! Although the technology that encapsulates the widget process has traditionally been known only by a handful of people, it's not really that hard. If you can encapsulate new widgets when they are found, you can use them immediately in a Python program.
This article describes how to encapsulate the C-encoded GObject (the final base class for all GTK + widget and many related objects) so that it can be used from Python code. Suppose you have Gnome-python v1.99.x installed on your machine (see Resources for Links if not installed). If you are using a package, make sure that the development package is installed. In addition, you must install Python 2.2 and its header files. Let's say you understand make, Python, GTK + 2, and some C knowledge.
To demonstrate this process, I'll encapsulate Eggtrayicon, which is a GTK + widget that is used to abstract the icon in the notification area. The library is located in GNOME CVS, in the Libegg module. At the end of this article, we will have a native Python module called TrayIcon, which contains a TrayIcon object.
At the beginning, get eggtrayicon.c and Eggtrayicon.h (whose links are in the Resources section at the end of this article) and put them in the new directory. The source file should be built in the automake environment (but we will not be in this environment), so either drop #include <config.h> in those files, or create an empty file named Config.h, and then create an empty makefile ; Next, we will populate it.
Create an interface definition
The first step in encapsulating the object is to create the trayicon.defs, which specifies the API for the object. Definition files are written in a class Scheme language, and although they are easy to generate for small interfaces, it can be difficult for large interfaces or beginners to write them.
Gnome-python is provided with a tool called H2def. The tool parses the header file and generates a rough definition file. Note: Because it actually does not parse C code, but only uses regular expressions, it does require the GObject of the traditional format and does not parse the strangely formatted C code correctly.
To generate the initial definition file, we call h2def:python/usr/share/pygtk/2.0/codegen/h2def.py eggtrayicon.h > Trayicon.defs as follows
Note: If h2def.py is not installed in/usr, you must change the path to point to where it resides.
If we now look at the generated definition file, it should have some meaning. The file contains the definition, constructor, and method Send_message and Cancel_message of the class Eggtrayicon. There are no obvious errors in the file and we don't want to remove any methods or fields, so we don't need to edit it. Note: This file is not specific to Python and can be used by other language bindings.
Build Wrapper
Now that we have an interface definition, we can generate a code block for the Python wrapper. This includes generating an overlay file. The overwrite file tells the code generator which header files to include, what the module name is, and so on.
Divide the overwriting file into sections (in LEX/YACC style) by using percent%. These sections define which header files to include, the module names, which Python modules to include, which functions to ignore, and all the last manually encapsulated functions. The following is the initial overwrite file for the TrayIcon module.
Listing 1. Trayicon.override
%%
Headers
#include <Python.h>
#include "pygobject.h"
#include "eggtrayicon.h"
%
ModuleName TrayIcon
%
import GTK. Plug as Pygtkplug_type
%
ignore-glob
*_get_type
%
Let's examine the code in more detail again:
Headers
#include <Python.h>
#include "pygobject.h"
#include "eggtrayicon.h"
These are the header files to include when building the wrapper. It is always necessary to include Python.h and pygobject.h, and we must include them when we encapsulate eggtrayicon.h.
The ModuleName specification declares what modules the wrapper will be in.
Import GTK. Plug as Pygtkplug_type
These are Python imports for the wrapper. Note the naming convention, which must be adhered to for the module to be compiled. Typically, the superclass of the imported object is sufficient. For example, if an object inherits directly from GObject, it uses:
Import GObject. GObject as Pygobject_type
ignore-glob
*_get_type
This is the glob pattern (shell-style regular expression) of a function name to ignore. Python handles the type code for us, so we ignore the *_get_type functions; otherwise, they are encapsulated.
Now that we've constructed the overwrite file, we can use it to build the wrapper. Gnome-python binding provides a magical tool for generating wrappers that we can use at will. Add the following content to the makefile:
Listing 2. Initial makefile
Again detailed description:
Defs= ' pkg-config--variable=defsdir pygtk-2.0 '
DEFS is the path that contains the Python GTK + binding definition file.
Trayicon.c:trayicon.defs Trayicon.override
The generated C code depends on the definition file and the overwrite file.
pygtk-codegen-2.0--prefix trayicon \
This invokes the Gnome-python code generator. The prefix parameter is used as a prefix to the variable name inside the generated code. You can name the parameter at will, but you can keep the symbol name consistent by using the module name.
--register $ (DEFS)/gdk-types.defs \
--register $ (DEFS)/gtk-types.defs \
Modules use the types in GLib and GTK +, so we must also tell the code generators to mount these types.
--override trayicon.override \
This parameter passes the overwrite file we created to the code generator.
Here, the last option for the code generator is to define the file itself. The code generator outputs on standard output, so we redirect it to the target trayicon.c.
If we run make TRAYICON.C now and then look at the generated files, we'll see each function in the C code wrapper Eggtrayicon. Don't worry about warning no argtype for gdkscreen*-this is normal.
As you can see, the encapsulation code looks complicated, so we're thankful for every line that the code generator has written for us. Later, we'll learn how to encapsulate each method manually when we want to tune the package, and we don't have to write all the wrappers ourselves.
Creating a Module
Now that you have created the code block for the wrapper, you need a way to start it. This involves creating trayiconmodule.c, which can be treated as the main () function of the Python module. The file is a boilerplate code (similar to an overlay file) and we modify it slightly. Here are the trayiconmodule.c we will use:
Listing 3. TrayIcon Module Code
#include <pygobject.h>
void trayicon_register_classes (Pyobject *d);
extern pymethoddef trayicon_functions[];
Dl_export (void)
Inittrayicon (void)
{
pyobject *m, *d;
Init_pygobject ();
m = Py_initmodule ("TrayIcon", trayicon_functions);
D = pymodule_getdict (m);
Trayicon_register_classes (d);
if (pyerr_occurred ()) {
py_fatalerror ("Can ' t initialise module TrayIcon");
}
Here are some subtle differences, because there are multiple source codes that use the word TrayIcon. The name of the function Inittrayicon and the name of the initialization module are the true name of the Python module, and therefore the name of the end-shared object. Array trayicon_functions and function trayicon_register_classes are named according to the--prefix parameters of the code generator. As mentioned earlier, it is a good idea to keep these names consistent so that encoding the file does not become confusing.
Although the name source may be confusing, the C code is very simple. It initializes the GObject and TrayIcon modules, and then registers these classes with Python.
Now that we have all the blocks of code, we can generate the shared objects. Add the following content to the makefile:
Listing 4. Makefile Additional Code section
Cflags = ' pkg-config--cflags gtk+-2.0 pygtk-2.0 '-i/usr/include/python2.2/-
I. Ldflags = ' pkg-config--libs gtk+-2.0 pygtk-2.0 '
trayicon.so:trayicon.o eggtrayicon.o trayiconmodule.o
$ (CC) $ (ldflags)-shared $^-O $@
Let's take a closer look at the following lines again:
Cflags = ' pkg-config--cflags gtk+-2.0 pygtk-2.0 '-i/usr/include/python2.2/-I.
The row defines a C compilation flag. We use Pkg-config to get the include path for GTK + and PyGTK.
Ldflags = ' pkg-config--libs gtk+-2.0 pygtk-2.0 '
The row defines the link program flag. Use Pkg-config again to get the correct library path.
TRAYICON.SO:TRAYICON.O EGGTRAYICON.O TRAYICONMODULE.O
Shared objects are constructed from the generated code, the module code we have just written, and the implementation of the Eggtrayicon. Implicit rules build. o files based on the. c file we created.
$ (CC) $ (ldflags)-shared $^-O $@
Here we build the final shared library.
Now run make trayicon.so should generate C code according to the definition, compile three C files, and finally link them all together. Well done-we've built the first native Python module. If it is not compiled and linked, examine these phases carefully and make sure that there are no warnings earlier that would cause a later error.
Now that we have trayicon.so, we can try and use it in a Python program. It is best to load it at first and then list its members. Run Python in the shell to open the interactive interpreter, and then enter the following command.
Listing 5. Interactive testing of TrayIcon
$ python
python 2.2.2 (#1, with 2003, 10:18:59)
[GCC 3.2.2 20030109 (Debian Prerelease)] on linux2
Type "hel P "," copyright "," credits "or" license "for the more information.
>>> Import pygtk
>>> pygtk.require ("2.0")
>>> import TrayIcon
>>> Dir (trayicon)
[' TrayIcon ', ' __doc__ ', ' __file__ ', ' __name__ ']
Hopefully the results from Dir will be the same as here. Now we are ready to start a larger example!
Listing 6. Hello sample
#! /usr/bin/python
Import pygtk
pygtk.require ("2.0")
import gtk
import trayicon
t = TrayIcon. TrayIcon ("Myfirsttrayicon")
T.add (GTK. Label ("Hello"))
T.show_all ()
Gtk.main ()
Refine it line by row:
#! /usr/bin/python
Import pygtk
pygtk.require ("2.0")
import gtk
import TrayIcon
Here, we first request and import the GTK + binding, and then import the new module.
t = TrayIcon. TrayIcon ("Myfirsttrayicon")
Now create the TrayIcon. An instance of TrayIcon. Note: constructor with string argument-icon name.
T.add (GTK. Label ("Hello"))
The TrayIcon element is a GTK + container, so you can add anything to it. Here, I add a tab widget to the widget.
T.show_all ()
Gtk.main ()
Here, I set the widget to visual and start the GTK + main event loop.
Now, if you have not done so, add the Notification area applet to the GNOME panel (right-click on the panel and select "Add to Panel"-> Utility-> Notification area). Running the test program should display "Hello" in the column. It's cool, isn't it?
What else does the notification area allow us to do? OK, the program can tell the notification area to display the message. The actual display of the message is implementation-specific; The GNOME notification area now displays tooltips. We can send a message by calling the Send_message () function. The Quick View API lets you know that it wants to have timeouts and messages, so it should work as follows:
...
t = TrayIcon. TrayIcon ("test")
...
T.send_message (1000, "my")
But it's not like that. The C prototype is send_message (int timeout, char* message, int length), so the Python API also requires character pointers and lengths. This does work:
...
t = TrayIcon. TrayIcon ("test")
...
message = ' My A '
t.send_message (1000, message, Len)
However, this is a little ugly. This is Python; programming should be simple. If we stick along this route, it will end in C, but no semicolon. Fortunately, when you use the Gnome-python code generator, you can encapsulate each method manually.
Tuning interface
So far, we've got the send_message (int timeout, char *message, int length) function. It would be better if Eggtrayicon's Python API allowed us to invoke Send_message (timeout, message). Luckily, it's not too hard.
Completing this step will again involve editing trayicon.override. This is exactly what the file name means: The file mainly contains the wrapper functions that are manually overwritten. Rather than demonstrating an example and gradually explaining its contents, these functions are much harder to work with, so the following are hand-encapsulated send_message code.
Listing 7. Manual Overlay
Override Egg_tray_icon_send_message Kwargs
static pyobject*
_wrap_egg_tray_icon_send_message (Pygobject * Self,
pyobject *args, Pyobject *kwargs)
{
static char *kwlist[] = {"Timeout", "message", NULL};
int timeout, Len, ret;
char *message;
if (! Pyarg_parsetupleandkeywords (args, Kwargs,
"Is#:trayicon.send_message", Kwlist,
&timeout, & Message, &len)] return
NULL;
ret = Egg_tray_icon_send_message (Egg_tray_icon (self->obj),
timeout, message, Len);
Return Pyint_fromlong (ret);
}
For clarity, we'll refine the list again by line:
Override Egg_tray_icon_send_message Kwargs
The line tells the code generator that we will provide a manual definition of egg_tray_icon_send_message, which itself should not generate a definition.
Static pyobject*
_wrap_egg_tray_icon_send_message (pygobject *self,
pyobject *args, Pyobject *kwargs)
This is the prototype of the Python-to-c Bridge. It consists of a pointer to the GObject that is calling the method, an array of arguments, and a keyword parameter array. The return value is always pyobject*, because all values in Python are objects (even integers).
{
static char *kwlist[] = {"Timeout", "message", NULL};
int timeout, Len, ret;
Char *message;
The array defines the name of the keyword parameter that the function accepts. Providing the ability to use keyword parameters is not required, but it makes code with many parameters clearer and does not require a lot of extra work.
if (! Pyarg_parsetupleandkeywords (args, Kwargs,
"Is#:trayicon.send_message", Kwlist,
&timeout, & Message, &len)] return
NULL;
This complex function call performs a parameter parsing. We provide it with the known keyword parameters and a list of all the given parameters, which sets the value to which the final argument points. The seemingly convoluted string declares the desired variable type, which we'll explain later.
ret = Egg_tray_icon_send_message (Egg_tray_icon (self->obj),
timeout, message, Len);
Return Pyint_fromlong (ret);
}
Here, we actually call egg_tray_icon_send_message and then convert the returned int to Pyobject.
At first it looked a bit scary, but it was originally copied from the generated code in the TRAYICON.C. In most cases, this is entirely possible if you only want to tune the required parameters. Just copy and paste the related function from the generated C, add a magical overlay line and edit the code until it's as you wish.
The most important change is to modify the required parameters. The seemingly convoluted string in the Pyarg_parsetupleandkeywords function defines the required parameters. Initially, it is isi:TrayIcon.send_message; this means that the arguments are int, char* (s), and int, and if an exception is thrown, the function is called Trayicon.send_message. We don't want to specify the string length in Python code, so change the ISI to is#. Using s# instead of s means that the pyarg_parsetupleandkeywords will automatically compute the length of the string and set another variable for us-that's exactly what we want.
To use the new wrapper, simply rebuild the shared object and change the Send_message call in the test program to:
T.send_message (1000, message)
If everything goes as usual, the modified example should have the same behavior, but with clearer code.
End Game
We use a small but useful C GObject to encapsulate it so that it can be used in Python, and even tailor the wrapper to suit our needs. The technology here can be applied to different objects multiple times, allowing you to use any GObject found in Python.