Archive for the ‘awn’ Category

What I learned from contributing to an Open Source project

Wednesday, January 23rd, 2008

A while ago, I had the opportunity to contribute to the Avant Window Navigator open source project. My participation in the project has waned lately, but I’ve had a little time to reflect on the time that I spent working on it. In no particular order, here were some of the things I took away from the project. Note that this is just based on a single project, and that my experience might have been quite different had I participated in other projects:

Learn to detach yourself from from your code. Once your stuff is checked into a public repository, anything can happen. Generally, most people will respect what they see as “your turf,” but that isn’t always the case. People will suggest improvements, implement them, and share them with the world before you even get a chance to review them. Overall, it’s a good thing and part of the ecology of an open-source project. At your typical office-programmer job, people usually end up becoming specialists in specific areas and the demarkation of responsibilities are quite clear, sometimes at the expense of progress. The boundaries are a lot looser in the open source community.

Interpersonal Relationships are Weird. When you’re in an office, you know that Fred is a little odd, and gets touchy when people criticize his font selection, because your co-worker warns you when you’re at the coffee machine. Anyone who lives and dies by their e-mail knows that emotions don’t get conveyed adequately in text, and the same is true with online forums and IRC channels. Some people lose their inhibitions when they don’t understand that they’re communicating with other people, and turn into jerks. Fortunately, there aren’t very many of these people.

There’s a wide range in quality. There’s no entrance requirement, so there’s a pretty wide variety in code quality. There are some incredible, amazing, talented people out there who are coding for their love of the craft. There are also people who make some sub-par stuff.

Apprehension about bugs. When I started, I was really worried about what would happen when I released software that was buggy. In a commercial environment, there are often layers of people between the programmer and the end-user, so bug reports come to you filtered and sanitized. In the open source world, people will report bugs openly, directly to you, on forums, for all the world to see. That made me nervous as hell. It turned out to be not so bad. The users knew that they were using bleeding edge stuff, and they expected a degree of instability and seemed to accept it quite well. They were helpful and sympathetic when reporting problems. I think it cuts both ways; when users report their software defects to someone far removed from the programmer, they’re less sympathetic and more irate than if they’re communicating directly with a person who can help them. Users are people, just like programmers, and they usually realize that the programmers are doing this for free and are quite polite.

Uptake on Localization was less enthusiastic than I thought it’d be. My weather applet was among the first to support multiple languages. Because there were so many contributers from outside the U.S. working on AWN, I thought people would jump at the opportunity to translate my applet to their native language. In the end, there were only three people who volunteered.

I felt old. Most of the people contributing to the project were in college or in their mid-twenties. I’m in my mid-thirties, and after working for 10 hours, coming home, feeding the kids and getting them ready for the next day, it’s 8:00 and I don’t feel much like getting out the laptop and coding. I managed to sneak in a few cycles at work, but I always felt a little guilty about it. While the other contributers were complaining about their upcoming finals, I had to deal with the upcoming PTA meeting.

You can learn a lot. I got into this project to “scratch an itch.” I was using Linux at work, I needed a dock, because I’m a Mac guy and I like docks. AWN was a Dock for Linux, but it was missing a clock. So I learned Python to create one. Now I know some Python, and I have a clock on my dock. I’m a happy man.

Python and GConf

Friday, November 16th, 2007

The weather applet and clock/calendar applet for the Avant Window Navigator both use GConf to store their configuration settings. GConf is part of the GNOME environment on Linux. It maintains a hierarchical set of configuration data in (key,value) pairs, much like the registry on Windows or the “plist files” on OSX. One of the nice things about GConf is that you can register your application to receive notifications in a callback function whenever any interesting configuration values change.

I noticed that there weren’t a lot of tutorials out there on the topic of mixing Python with GConf, so I decided to write one.

First, you’ll need to import the Python bindings for GConf:

import gconf

Next, you want to create a GConf “client” in your Python app. You’ll probably want to do this in an __init__() function somewhere. You’ll also want to register your application to receive notifications when ever your configuration values change. The hierarchy of configuration values in GConf follows the familiar slash-separated path notation. For example, the configuration values for my AWN weather applet are stored in /apps/avant-window-navigator/applets/weather. So, if I want to register a callback named config_event to be called whenever configuration values on that path are modified, I’d do the following:

self.gconf_client = gconf.client_get_default()
self.gconf_client.notify_add("/apps/avant-window-navigator/applets/weather", self.config_event)

I usually write a generic “get_config” function, and have the callback call get_config. That way, I can use the same configuration code when I initialize. The config callback then looks simple… something like this:

def config_event(self, gconf_client, *args, **kwargs):
  self.get_config()

The kwargs parameter gives you a list of parameters that changed. You can fine-tune your configuration code based on this, but I usually just ignore it and re-read everything because I don’t usually have very many parameters.

GConf provides functions for reading your parameters. They look like this:

foo = self.gconf_client.get_string("/path/to/my/config/data/foo")
bar = self.gconf_client.get_int("/path/to/my/config/data/bar")
baz = self.gconf_client.get_bool("/path/to/my/config/data/baz")

All of the functions except get_boolreturn None if the key isn’t found. Oddly, get_bool seems to return False if the key isn’t found. In my configuration code, I like to initialize my GConf values when the key isn’t found. So when if my code were to read the “foo” parameter like the above example, it’d actually be coded something like this:

foo = self.gconf_client.get_string("/path/to/my/config/foo")
if foo == None:
  self.gconf_client.set_string("/path/to/my/config/foo", "Default Value")
  foo = "Default Value"


And I usually wrap the above idiom in its own function that accepts a key name and a default value.


Note that you can edit and interact with your GConf settings in realtime using the GNOME configuration tool. If you use Ubuntu, then this utility may be found under the “System Tools” menu. Editing configuration values in the configuration tool will result in your callback being executed as you might expect.

This also makes creating a configuration dialog easy. The configuration dialog just needs to write its updated values to gconf when the user clicks Apply or OK. If you’ve created a callback and generic configuration function, then the application will automatically reconfigure itself after the user applies their modifications!

L10N with Python in Avant Window Navigator Applets

Tuesday, October 30th, 2007

It’s been a while since I blogged about my latest “hobby” (oh, how my wife would cringe), writing Python-based applets for the Avant Window Navigator. I’ve been spending a lot of time working on my clock/calendar applet lately, but today I’m going to go back to my weather applet because it’s more interesting to write about.

A while ago, I decided it was time to add some alternate (that is, non-English) language support to the applet. The de-facto standard tool to accomplish this is the Python variant of GNU’s gettext. I found a couple of resources that helped guide me through this. The first was the wiki for the “one laptop per child” project, which does a lot of L10N in Python. The second was an excellent post in the “Learning Python” blog (the author doesn’t give his/her name in the “Who am I” section, otherwise I’d give props). And of course, there’s always the official Python library documentation, too.

The first step is to import the gettext libraries into your Python code, and do some setup work, thusly:

APP="awn-weather-applet"
DIR=os.path.dirname (__file__) + '/locale'
import locale
import gettext
#locale.setlocale(locale.LC_ALL, '')
gettext.bindtextdomain(APP, DIR)
gettext.textdomain(APP)
_ = gettext.gettext

(Note: Made an edit to the above source code on Nov 16 2007 – evidently, the DIR parameter needs the full path to work properly)

Now, here’s what we’re actually doing. The call to bindtextdomain is binding the awn-weather-applet domain to the locale subdirectory. When the applet is deployed, it has a locale directory right underneath the main script, weather.py. The directory argument of bindtextdomain is relative, so we’re pointing gettext at that directory. We’re basically telling gettext to look in the locale subdirectory for files named language/LC_MESSAGES/awn-weather-applet.mo, where language is the two-letter language code defined by the environment (more on that later).

It should be noted that, customarily, locale files are stored in a default location which is a more global place in the filesystem, e.g. /usr/share/locale/language/LC_MESSAGES. If you don’t supply a directory to the bindtextdomain method call, gettext will use the default directory for the system. I opted not to use the default filesystem location because I wanted non-superusers to be able to easily use language files that I supply with the applet, using the standard AWN applet installation mechanism. Requiring the user to put .mo files in the /usr/share directory tree isn’t an option.

The call to textdomain sets the global domain to awn-weather-applet. Essentially we’re telling gettext that all future calls into gettext should use the awn-weather-applet domain as its source for translations.

Lastly, the line that reads _ = gettext.gettext() defines a convenient alias that we will use to identify strings in our code that need translation. If you’re familiar with i18n of C applications, this will look quite familiar; in the C implementation, an identically named macro is used for the same purpose.

The next step is to read through the code to identify the literal strings that will be translated. Generally, any UI element that a user will see is a candidate for translation. Things like log and console output are not as important. When we find a candidate string, we wrap it in _( ). For example, this:

self.dialog.set_title("Forecast")

becomes

self.dialog.set_title(_("Forecast"))

Because of the alias at the top of the source file, what we’re really doing is this:

self.dialog.set_title(gettext.gettext("Forecast"))

It’s a good thing we have the shorthand version!

As I alluded to earlier, gettext does its magic by reading files that ends in an .mo extension. At runtime, it reads the string that’s passed into it (in the above example, “Forecast”), then looks up that string in the proper .mo file to see if a translation is available. If it finds an entry, it returns the translated string, otherwise it just returns the string that was passed into it. So our next task is to create the .mo files.

Creating .mo files is a three step process. First, we need to run a tool that extracts all of the strings to be translated into a usable text file that a translator can edit. The tool to do this is called xgettext. The command line to run looks like this:

xgettext --language=Python --keyword=_ --output=awn-weather-applet.pot *.py

This command generates an output file named awn-weather-applet.pot which acts as a template for translators to do their translation. The file has a bunch of lines that look like this:

#: weather.py:127
msgid "Forecast"
msgstr ""

So, let’s say we want to make a Spanish translation of the weather applet. First, we’d make a copy of this file, and name it something sensible like awn-weather-applet.po (note, you can also use the msginit tool, which does a few other housekeeping things for you like fill in the e-mail address). We’d find all the lines that start with msgid, translate it, and put the result in the following line’s msgstr, like this:

#: weather.py:127
msgid "Forecast"
msgstr "PronĂ³stico"

Next, we need to “compile” the awn-weather-applet.po file into an awn-weather-applet.mo file, so that it’s usable by gettext at runtime. This is done using the msgfmt command, like this:

msgfmt awn-weather-applet.po -o awn-weather-applet.mo

That’s it! Now all you need to do is ensure that the awn-weather-applet.mo file ends up in the (base of weather applet)/locale/es/LC_MESSAGES directory, and Spanish translations work!

Developing Applets for AWN: Drawing the Icon

Monday, October 8th, 2007

(Continued from previous two posts)
If you’re familiar with the Avant Window Navigator, then you know that a fair amount of the project revolves around eye candy and visual effects. Making an applet that is consistent with the user’s expectations with respect to visual effects (e.g., reflection, bouncing, rotating, etc.) is very important. Fortunately, Neil and Co. have made this quite straightforward. By simply sub-classing the awn.AppletSimple class, your applet will inherit all of the special effects that it should.

Unfortunately, there’s one small trick you need to work around. If your applet is so simple that it just needs a single, static icon, then sub-classing awn.AppletSimple, followed by a call to set_icon, works great. It gets more difficult if you need to draw dynamic content in the area usually occupied by the icon.

The problem is that set_icon (as well as its misunderstood cousin, set_temp_icon) takes a Pixbuf as its input parameter. However, the drawing framework for doing just about anything, especially loading PNGs, is cairo. Cairo has no native support for converting a surface to a Pixbuf. I discovered a trick for doing this by looking at the source code for the PyClock and BlingSwitcher applets. Let’s say you have a Cairo image surface that you’ve created from an existing PNG, thusly:

cs = cairo.ImageSurface.create_from_png(iconName)
ct = cairo.Context(cs)
ct.set_source_surface(cs)
ct.paint()

…and you want to get a Pixbuf from that image. The following function takes the surface, writes it to a PNG that is stored in a string, then uses the Pixbuf loader to load it from that PNG string:

# Stolen from diogodivision's "BlingSwitcher"
def get_pixbuf_from_surface(self, surface):
  sio = StringIO()
  surface.write_to_png(sio)
  sio.seek(0)
  loader = gtk.gdk.PixbufLoader()
  loader.write(sio.getvalue())
  loader.close()
  return loader.get_pixbuf()

Neat, huh? This is how I manage to display the temperature on the weather applet. I load up the icon into an ImageSurface, write the text on top of it by calling show_text, call the function above to convert the ImageSurface to a PixBuf, then call set_temp_icon using the new PixBuf.

You can see it in action by downloading the weather applet, and looking in weather.py. The relevant code is in the draw_current_conditions function.

Parsing XML with Python and minidom

Thursday, October 4th, 2007

(Continued from my last post)
So, the first thing I needed to do when creating my weather applet for Avant Window Navigator was actually parse weather data from a weather source. After messing around with Google’s weather API for a while, I decided to use weather.com‘s web service. weather.com has a well-documented, straightforward, predictable XML API. To parse the XML, I chose minidom. Minidom is a “Lightweight DOM Implementation.” Here’s how it works: Let’s say you have an XML document that supplies a pizza menu, at some URL. Here’s the XML:

In the python script that will be parsing this, you’d want to import the minidom package. Let’s assume that the above XML is served by the URL http://menu.pizzaplace.us, so you’ll want to import urllib as well. The python code to read up the XML Document might look like the following:


from xml.dom import minidom
import urllib
import sys
try:
 usock = urllib.urlopen(‘http://menu.pizzaplace.us’)
 xmldoc = minidom.parse(usock)
 usock.close()
except:
 print “Something really bad happened! “, sys.exc_info()[0]

Easy, right? Now we want to get the actual data out of the Pizza Menu. Everything in your DOM tree is a Node. This includes text between element tags. In fact, in minidom, the whitespace between strings of text is a node, too (more on that in a minute!). To fetch nodes, you use the getElementsByTagName function. This function returns a List of nodes with matching element tag names. Another handy function is getAttribute. As you might expect, it returns the value for an attribute on a particular element.

Let’s say we want to iterate through all of the pizzas on the pizza-menu, printing the type of pizza. That code would look like this:


from xml.dom import minidom
import urllib
try:
 usock = urllib.urlopen(‘http://menu.pizzaplace.us’)
 xmldoc = minidom.parse(usock)
 usock.close()
 pizza_list = xmldoc.getElementsByTagName(‘pizza’)
 for pizza_element in pizza_list:
  pizza_type = pizza_element.getAttribute(‘type’)
  print ‘Pizza Type: %s’ % pizza_type
except:
 print “Something really bad happened! “, sys.exc_info()[0]

Next, let’s pretend that “heart-attack-special” pizza sounds really appetizing, and we want to estimate just how much our cholesterol count will spike if we have a slice. We probably want to iterate over the toppings on that pizza to perform that evaluation. To that end, we will hunt for the pizza with the type “heart-attack-special”, grab that node, then iterate over the topping sub-nodes. Here’s how we would do that:


from xml.dom import minidom
import urllib
try:
 usock = urllib.urlopen(‘http://menu.pizzaplace.us’)
 xmldoc = minidom.parse(usock)
 usock.close()
 pizza_list = xmldoc.getElementsByTagName(‘pizza’)
 for pizza_element in pizza_list:
  pizza_type = pizza_element.getAttribute(‘type’)
  print ‘Pizza Type: %s’ % pizza_type
  if pizza_type == ‘heart-attack-special’:
   topping_list = pizza_element.getElementsByName(‘topping’)
   for topping_element in topping_list:
    # (do something here)
except:
 print “Something really bad happened! “, sys.exc_info()[0]

As you can see, the pizza_element is a node like any other node, so you can call getElementsByName on it to get any child nodes of this pizza element. The toppings (pepperoni, sausage, hamburg, canadian bacon, and ham) are themselves child nodes of their respective elements. Each node has a nodeType property which describes the nature of that node. The nodeTypes are TEXT_NODE, ELEMENT_NODE, ATTRIBUTE_NODE, and DOCUMENT_NODE. Thus, the word “pepperoni” is a child node of the first topping node, and is of type TEXT_NODE.

You might be surprised to learn that the fourth topping node on the heart-attack-special is comprised of three child text nodes. The text “canadian bacon” has a child with the value bacon, a child with a single character of whitespace, and a child with the value bacon. This is not usually how we want to access the data in our XML documents; we’d prefer that “canadian bacon” be treated as a single node comprised of one string.

To make the data behave the way we expect it to, we can introduce our own simple utility method called getText. This function concatenates all child nodes of the supplied node list which are of type TEXT_NODE. It looks like this:


def getText(nodelist):
 rc = “”
 for node in nodelist:
  if node.nodeType == node.TEXT_NODE:
   rc = rc + node.data
 return rc

To use it, we’d pass it the parent node of the text we’re interested in. Going back to our original example, we can use the getText function to print out each topping on our heart-attack-special pizza:


from xml.dom import minidom
import urllib
try:
 usock = urllib.urlopen(‘http://menu.pizzaplace.us’)
 xmldoc = minidom.parse(usock)
 usock.close()
 pizza_list = xmldoc.getElementsByTagName(‘pizza’)
 for pizza_element in pizza_list:
  pizza_type = pizza_element.getAttribute(‘type’)
  print ‘Pizza Type: %s’ % pizza_type
  if pizza_type == ‘heart-attack-special’:
   topping_list = pizza_element.getElementsByName(‘topping’)
   for topping_element in topping_list:
    topping_text = getText(topping_element)
    print ” Topping: %s” % topping_text
except:
 print “Something really bad happened! “, sys.exc_info()[0]

The XML-parsing portions of the weather applet that I wrote for the Avant Window Navigator aren’t much more complicated than this. You can download the source code for the weather applet here. The parts which parse weather.com’s data are in the weather.py script, in the get_conditions and get_forecast functions.

Applets for AWN

Wednesday, October 3rd, 2007

I use Linux on the desktop at work. As a Mac owner, I was frustrated by the lack of a usable Dock, until I stumbled across a project called the Avant Window Navigator. Not only is it a cool dock, but it comes with oodles of eye-candy, which I’m a sucker for.

Here’s a screenshot of my dock:

You hear a lot about open source developers who got involved because they needed to “scratch an itch.” I happen to do web development in Java, and I had come to rely on my Gnome CPU Meter applet to tell me when Tomcat had finished deploying (there was always a spike while it was deploying a war file, and then a dropoff when it was finished). I didn’t have that in AWN. Fortunately, you can make applets for it.

So the first applet I created, after a “Hello World” applet and a clock, was a C-based applet called CPU Meter (incidentally, my “Hello World” applet is now part of the awn-extras package). For the CPU Meter, I stole some code from the Gnome System Monitor, mashed it into my Hello World applet, read up on the Cairo Graphics API and GTK+ API, and cobbled together a working CPU load grapher. Later on, another developer came up with an applet called “AwnTop” (a process listing applet), and he merged my stuff in with his stuff. The new applet is now called the AWN System Monitor.

Next, I decided to create a weather applet. All of the other developers had started writing their applets in Python, so I figured it was as good time as any to learn Python! I’ve gotta say I LOVE Python. It’s JavaScript’s simplicity, Shell scripting’s ease-of-use, and Perl’s power… and it’s incredibly easy to read and learn. Here’s a picture of the weather applet in action:

Cool, huh?

Well, I really didn’t intend for this post to be a brag session. I wanted it to be a how-to develop applets for AWN using Python. But now I guess I’ll leave that for the next post!


© 2012 Mike Desjardins. All Rights Reserved.