Tutorial to encapsulate GObject modules for graphical programming in Python

Source: Internet
Author: User
Tags glob gtk naming convention wrappers
Python is a very good language for coding graphical interfaces. Because work code can be written quickly and without time-consuming compilation cycles, the interface can be started and run immediately, and these interfaces can be used soon. Combining this with the Python's ability to easily link to a native library can create an excellent environment.

Gnome-python is a package that encapsulates GNOME and its associated libraries for Python. This allows you to use Python to write applications that look exactly like the core GNOME application, and spend only a fraction of the time it takes to write the application in C.

However, there is a drawback to programming without C. Most GNOME is written in C, and they 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, unless widgets are part of the core GNOME library or at least very useful, they will not be encapsulated. C programmers may have to write more complex code, but they do it first!

But that's not necessarily the case! While the technology of encapsulating widgets is traditionally only a handful of people know, it's not really that difficult. If you can encapsulate new widgets when they are discovered, 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 + widgets 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 software package, make sure that the development package is installed. In addition, Python 2.2 and its header files must also be installed. Let's say you understand make, Python, GTK + 2, and some C-related knowledge.

To demonstrate the process, I'll encapsulate Eggtrayicon, a GTK + widget that is used to abstract representations of icons 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 named TrayIcon that contains a TrayIcon object.

At the beginning, get eggtrayicon.c and Eggtrayicon.h (which are linked in the Resources section at the end of this article), and then 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 the #include in these files, or create an empty file named Config.h, and then create an empty makefile; Next, we'll populate it.
Create an interface definition

The first step in the object encapsulation process is to create a trayicon.defs that specifies the API for that 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 does not actually parse the C code, but just uses regular expressions, it does require a traditional formatted GObject and does not parse the malformed 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 is located.

If we look at the generated definition file now, it should have some meaning. The file contains definitions of class Eggtrayicon, constructors, and methods Send_message and Cancel_message. There is no obvious error in the file, we do not want to remove any methods or fields, so we do not need to edit it. Note: The file is not Python-specific and can be used by other language bindings.

Generating wrappers

Now that we have the interface definition, we can generate a code block for the Python wrapper. This includes generating an overwrite file. The overwrite file tells the code generator which header files to include, what the module name is, and so on.

The overwrite file is divided 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 finally all the functions that are manually encapsulated. The following is the initial overwrite file for the TrayIcon module.
Listing 1. Trayicon.override

%%headers#include 
 
  
   
          #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 
 
  
   
    #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.

ModuleName TrayIcon

The ModuleName specification declares what module the wrapper will be in.

Import GTK. Plug as Pygtkplug_type

These are the Python imports for the wrapper. Note the naming convention, which must be followed for the module to be compiled. Typically, the superclass of the imported object is sufficient. For example, if an object inherits directly from GObject, use:

  Import GObject. GObject as Pygobject_type  ignore-glob  *_get_type

This is a glob pattern (a shell-style regular expression) for the function name to ignore. Python handles the type code for us, so we ignore the *_get_type function; otherwise, they are encapsulated.

Now that we have constructed the overwrite file, we can use it to generate the wrapper. Gnome-python binding provides a magical tool for generating wrappers, which we can use at will. Add the following to makefile:
Listing 2. Initial makefile

Again, explain in detail:

  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 calls 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 arbitrarily, but you can use the module name to keep the symbol name consistent.

  --register $ (DEFS)/gdk-types.defs \  --register $ (DEFS)/gtk-types.defs \

The modules use the types in GLib and GTK +, so we must also tell the code generator to mount these types.

  --override trayicon.override \

This parameter passes the overwrite file that we created to the code generator.

  Trayicon.defs > $@

Here, the last option of 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 look at the generated files, we'll see each of the functions 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.

Create a module

Now that you have created the wrapper's code block, you need a way to start it. This involves creating trayiconmodule.c, which can be considered the main () function of the Python module. The file is a boilerplate file code (similar to the overwrite file), and we modify it slightly. Here are the trayiconmodule.c we will use:
Listing 3. TrayIcon Module Code

#include 
 
  
   
   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, as there are multiple source codes that use the word TrayIcon. The name of the function Inittrayicon and the name of the initialization module is the real name of the Python module, and therefore the name of the final shared object. The array trayicon_functions and function trayicon_register_classes are named according to the--prefix parameter of the code generator. As mentioned earlier, it is best to keep these names consistent so that encoding the file does not become confusing.

Although there may be confusion with the name source, 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 code blocks, we can build the shared object. Add the following to 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 double-check the line again:

  CFLAGS = ' pkg-config--cflags gtk+-2.0 pygtk-2.0 '-i/usr/include/python2.2/-I.

The row defines the C compile flag. We use Pkg-config to get the include paths for GTK + and PyGTK.

Ldflags = ' pkg-config--libs gtk+-2.0 pygtk-2.0 '

The row defines the linker flags. Use Pkg-config again to get the correct library path.

TRAYICON.SO:TRAYICON.O EGGTRAYICON.O TRAYICONMODULE.O

Shared objects are constructed based on the generated code, the module code we just wrote, and the implementation of Eggtrayicon. Implicit rules build. o files based on the. c file that we created.

  $ (CC) $ (ldflags)-shared $^-O $@

Here we build the final shared library.

Now run make trayicon.so should generate C code based on 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, check these stages carefully and ensure that there were no warnings that would cause a later error.

Now that we have the trayicon.so, we can try and use it in a Python program. It's 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

$ Pythonpython 2.2.2 (#1, Jan 2003, 10:18:59) [GCC 3.2.2 20030109 (Debian Prerelease)] on Linux2type "help", "copyright" , "credits" or "license" for more information.>>> import pygtk>>> pygtk.require ("2.0") >>> Import trayicon>>> dir (trayicon) [' TrayIcon ', ' __doc__ ', ' __file__ ', ' __name__ ']

Hopefully the result from Dir is the same as here. Now we are ready to start a larger example!
Listing 6. Hello Example

#! /usr/bin/pythonimport Pygtkpygtk.require ("2.0") import gtkimport trayicon                t = TrayIcon. TrayIcon ("Myfirsttrayicon")   T.add (GTK). Label ("Hello"))           T.show_all () Gtk.main ()

Refine it row by line:

  #! /usr/bin/python  Import pygtk  pygtk.require ("2.0")  import gtk  import TrayIcon

Here, we first request and import the GTK + bindings, and then import the new modules.

  t = TrayIcon. TrayIcon ("Myfirsttrayicon")

Now create the TrayIcon. An instance of TrayIcon. Note: The constructor has a string parameter-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.

  T.show_all ()  Gtk.main ()

Here, I set the widget to visual, and then start the GTK + main event loop.

Now, if you have not yet done so, add the Notification area applet to the GNOME panel (right-click on the panel and choose Add to Panel, Utility-Notification area). Running the test program should display "Hello" in the bar. It's cool, isn't it?

What else does the notification area allow us to do? Well, the program can tell the notification area to display the message. The actual display of the message is implementation-specific; Currently, the GNOME notification area displays a tooltip. We can send a message by calling the Send_message () function. The Quick View API can tell that it wants to have timeouts and messages, so it should work as follows:

... t = TrayIcon. TrayIcon ("Test") ... t.send_message ("My first message")

But that's not true. 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 First message" T.send_message (+, message, len (message))

However, this is a bit ugly. This is Python, and programming should be simple. If we persist along this route, then we will end in C, but there is no semicolon. Fortunately, each method can be encapsulated manually when using the Gnome-python code generator.

Tuning interface

So far, we have send_message (int timeout, char *message, int length) functions. It would be better if Eggtrayicon's Python API allowed us to call Send_message (timeout, message). Fortunately, it's not too difficult.

Completing this step will again involve editing the trayicon.override. This is exactly what the filename means: The file mainly contains the wrapper functions that are overwritten manually. These functions work much harder than demonstrating an example and gradually explaining its contents, so the following is a manual encapsulated Send_message code.
Listing 7. Manual override

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 the sake of clarity, we'll refine the list by line again:

  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 parameter arrays, 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 can make code with many parameters much 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 parameter parsing. We give it the keyword parameter we know and a list of all the given parameters, and it sets the value that the final argument points to. The seemingly convoluted string declares the type of variable required, and we'll explain it 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 relevant function from the generated C, add the Magic Overlay line and edit the code until it is as you wish.

The most important change is to modify the required parameters. The Pyarg_parsetupleandkeywords function in the seemingly convoluted string defines the required parameters. Initially, it is isi:TrayIcon.send_message; This means that the parameters are int, char* (s) and int, and, if an exception is thrown, the function is called Trayicon.send_message. We don't want to have to specify the string length in Python code, so we'll change ISI to is#. Using s# instead of s means that pyarg_parsetupleandkeywords will automatically calculate the length of the string and set another variable for us-that's 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 (+, message)

If everything goes as usual, the modified example should have the same behavior, but with clearer code.

End Game

We used a small but useful C GObject to encapsulate it so that we could use it in Python, and even tailor the wrapper to fit our needs. The techniques here can be applied multiple times to different objects, allowing you to use any GObject found in Python.

  • Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.