Building the XO: Porting a PyGTK game to Sugar, part two
by John (J5) Palmieri
In the last lesson we learned about what made Block Party tick. In this lesson, we will turn the same PyGtk codebase into a Sugar activity with only minimal modification of the core code. As always, the code for this lesson can be found at:
http://dev.laptop.org/~j5/BlockParty_tutorial/block_party_lesson_2.tar.gz
For this lesson you will need a computer with the latest version of the Sugar environment running on it. This can either be from sugar-jhbuild or any build after or including build 303 running in an emulated environment like qemu, from the LiveCD or on the laptop itself.
Entry points
The first thing we need to clean up in the Block Party source is the entry point. When running standalone, we call init() from the main() function. This creates a window, sets up all the signal handlers, and then goes into the mainloop.
When being run by Sugar, however, the main loop and toplevel window are all taken care of by the Activity class. In fact, the Activity class is just a subclass of GtkWindow. Because of this we will define init() as the common entry point and shift the window creation code from init() to main().
def main(): toplevel_window = gtk.Window(gtk.WINDOW_TOPLEVEL) init(toplevel_window) gtk.main() return 0
In the init() function we now take a GtkWindow as a parameter and use that, instead of creating the window ourselves.
def init(toplevel_window): global window window = toplevel_window # remove any children of the window that Sugar may have added for widget in window.get_children(): window.remove(widget)
You will also notice we remove the window of any child widgets. This is because Sugar comes stacked with a HippoCanvas widget, which is usually what the main UI layer activities are built on. At some point it may be nice to port Block Party to use the canvas — but for right now we are dealing with direct drawing layers, so we need to discard the canvas.
That’s all that needs to be touched in the current code. Now that we have our entry points, we can still run and debug the program without Sugar by simply running the command python BlockParty.py, but now we also have the API call necessary to hook into Sugar.
The activity class
These next parts require Block Party to be run in a Sugar environment. The Activity class is the central API for hooking into Sugar and the entry point when Sugar launches an activity. In order to create a Block Party activity, we simply inherit from the Activity class, hook up to a couple of signals, and run the entry point for Block Party. We will do this all in the BlockPartyActivity.py file.
import BlockParty
from sugar.activity import activity
class BlockPartyActivity(activity.Activity):
def __init__(self, handle):
activity.Activity.__init__(self, handle)
self.connect('destroy', self._cleanup_cb)
self.gamename = 'blockparty'
self.set_title("BlockParty")
# connect to the in/out events
self.connect('focus_in_event', self._focus_in)
self.connect('focus_out_event', self._focus_out)
BlockParty.init(self)
def _cleanup_cb(self, data=None):
return
def _focus_in(self, event, data=None):
return
def _focus_out(self, event, data=None):
return
As you see, we import BlockParty which loads the BlockParty.py file and then imports the activity module from sugar.activity. We then have the BlockPartyActivity class inherit from activity.Activity which takes a handle argument for its constructor. The handle is the id given to the instance of the activity when it is launched.
Notice we attach to the focus_in_event and focus_out_event. Right now we don’t do anything in those handlers, but we could use them to conserve power by having the activity stop processing when it is in the background. In the case of Block Party, we could simply pause the game.
At the end of the constructor, we call the Block Party entry point. We pass self to init because, if you remember, the Activity class is itself the toplevel window we wish to draw on.
That’s it. That’s all the code needed to sugarize a PyGtk application.
Now let’s look at some of the support files that go into making this activity into a bundle.
Bundles
An activity bundle is a self contained set of files which make up a Sugar activity. They are usually zipped up into a file ending with the .xo extension. This allows activities to be run simply by placing the bundle in the correct place in the file system. Now that we have created the activity, we need to create the support files.
The first support file is the setup.py file. This file makes it easy to manage building and distributing the activity. In most cases we are dealing with python so the setup.py will look the same, but in the future it will be easy to extend setup.py to support other types of projects.
#!/usr/bin/env python from sugar.activity import bundlebuilder bundlebuilder.start()
As you can see, it is very simple script which handles most of what you will need to do without further coding.
The setup.py script still needs a couple of other files in order to function. The MANIFEST file is a simple list of source files that should be added to the bundle for distribution. Sometimes there are files which are needed for the version of the bundle in version control repositories but make little sense inside the actual shipping bundle. These files can be left out of the MANIFEST and will not be bundled up. Let’s take a look at our MANIFEST file.
BlockParty.py BlockPartyActivity.py
Notice we only list the source files and not the files in the activity directory or the setup.py file. These files are automatically added to the bundle and should not be in the MANIFEST file.
The activity.info file in the Activity directory is another of the support files. This tells Sugar about the activity and how to launch it. Let’s take a look at it.
[Activity] name = BlockParty service_name = org.laptop.BlockPartyActivity class = BlockPartyActivity.BlockPartyActivity icon = activity-blockparty activity_version = 1 show_launcher = yes
The activity.info file is a simple .ini formatted configuration file. We start with the [Activity] block header and then give a name, the D-Bus service name, the module and class we are launching, an icon, version and if the icon should show up on the launcher panel.
The D-Bus service name usually looks like a reverse URL with the organizations namespace followed by the activity name. In our case we namespace with org.laptop.
The class also includes the module namespace. Here we find the BlockPartyActivity class in the BlockPartyActivity.py file so we prepend the class name with the BlockPartyActivity module name.
The icon field specifies the svg icon which should reside in the activity folder alongside the info file.
Versioning for bundles is handled by the activity_vesion key and is a straight integer. Right now this is used by the setup.py script to take on the version number at the end of the bundle name (i.e. BlockParty-1.xo). In the future this will be used to determine when to upgrade the bundle if another user has a newer version.
Some activities are special activities which are managed by Sugar or other activities. In those cases it doesn’t make sense to show them to the user so show_launcher can be set to “no”.
The final file we need to deal with is the svg icon file in the activity directory. We use a special formatted svg for our icons.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [ <!ENTITY fill_color "#FFFFFF"> <!ENTITY stroke_color "#000000"> ]> <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50"> <rect x="6" y="19" width="10" height="10" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> <rect x="21" y="5" width="10" height="10" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> <rect x="21" y="19" width="10" height="10" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> <rect x="36" y="19" width="10" height="10" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:2"/> </svg>
Notice the two entities at the top of the file: fill_color and stroke_color. Sugar icons must be two-toned, and we use the two entities in order to fit in with the look and feel of Sugar. Sugar will take those entities and fill them in with the user’s personal colors.
Producing a shippable bundle
After all the files are created and modification done we are ready to build a bundle to be shipped and run in Sugar. All one has to do is run ‘python setup.py dist’ in a Sugar environment and it will produce a bundle which looks like BlockParty-1.xo.
To install the bundle, simply run ’sugar-install-bundle BlockParty-1.xo’ as the olpc user. You should see the Block Party icon pop up on the launcher. Click on the icon to run.
Conclusion
As we now see, sugarizing a PyGtk application takes little time or effort. This is only the tip of the iceberg, though; deep integration will take a bit more effort for other applications. In the next lesson, we will explore more of the python side of things and look into cleaning up the code to use classes in preparation for moving to the cairo drawing layer.







May 4th, 2007 at 9:30 am
You still don’t say what XO stands for. Would it kill you to say what it is? Or to respond to reader queries? sheesh.
May 4th, 2007 at 11:24 am
XO is the name of the machine. It is a symbol or hieroglyph if you will. It doesn’t mean anything in particular. Tell you what, turn the characters 90 degrees counter clockwise. Now look at the OLPC logo.
June 2nd, 2007 at 8:54 pm
I use glade to design GUIs for my python programs. Does Sugar have a port of glade?