Writing Gnome Applets in Gnome2

Andrew Burton

Revision History
Revision 1.020 December 2005ADB
Added link to download applet.
Revision 0.0215 April 2004ADB
Revisions based upon feedback from gnome-doc-list mailing list. Use Docbook 4.2, improve section on compilation, use appropriate markup for Q and A section, amend code.
Revision 0.0113 April 2004ADB
Initial development

Abstract

Gnome panel applets are cool little apps which sit in the Gnome panel and provide simple yet useful functionality to the user. There are a few little differences from writing regular Gnome applications; this tutorial aims to explain how to create a small panel applet.


Table of Contents

What's An Applet?
Building The Infrastructure
Making It Interesting
Creating A Context Menu
Common Questions
Conclusion
More Information

What's An Applet?

In Gnome, an applet is a small application, designed to sit in the Gnome panel, providing quick and easy access to a control, such as a volume control, a network status display, or even a weather gauge.

Applets require the libpanel-applet library to run and if you want to develop them, you'll need to install the development package (usually marked as -dev or -devel, depending on the distribution). Due to their small nature, they're often less complex, and so easier to master for the developer new to the Gnome environment.

A fresh install of a Gnome desktop will have the date and a volume applet in the top right corner. Laptop users find it useful to have an applet displaying the battery life:

Building The Infrastructure

Technically, applets are Bonobo controls embedded in the Gnome panel. This means that there are a few slight differences to stand-alone Gnome programs. The first difference is that each applet requires a 'server' file, which contains a description of the Bonobo capabilities. If this doesn't make much sense, don't worry. The only thing most developers need to do is edit a file, replacing some fields with the specifics of their applet.

Let's have a look at a sample .server file.

Figure 1. Sample .server File

<oaf_info> <oaf_server iid="OAFIID:ExampleApplet_Factory" type="exe" 
            location="/usr/lib/gnome-panel/myexample">

        <oaf_attribute name="repo_ids" type="stringv">
                <item value="IDL:Bonobo/GenericFactory:1.0"/>
                <item value="IDL:Bonobo/Unknown:1.0"/>
        </oaf_attribute>
        <oaf_attribute name="name" type="string" value="Example Applet Factory"/>
        <oaf_attribute name="description" type="string" value="Factory to create the example applet"/>
</oaf_server>

<oaf_server iid="OAFIID:ExampleApplet" type="factory"
            location="OAFIID:ExampleApplet_Factory">

        <oaf_attribute name="repo_ids" type="stringv">
                <item value="IDL:GNOME/Vertigo/PanelAppletShell:1.0"/>
                <item value="IDL:Bonobo/Control:1.0"/>
                <item value="IDL:Bonobo/Unknown:1.0"/>
        </oaf_attribute>
        <oaf_attribute name="name" type="string" value="Example Applet"/>
        <oaf_attribute name="description" type="string" value="An example applet"/>
        <oaf_attribute name="panel:category" type="string" value="Amusements"/>
        <oaf_attribute name="panel:icon" type="string" value="myicon.png"/>
</oaf_server>
</oaf_info>

A few things to note. One is the location of our executable file, defined by the oaf_server tag in the location value, where the type is "exe". In this example, our executable file is called myexample and is placed in /usr/lib/gnome-panel/. Secondly, we define the name of our applet 'factory', ExampleApplet_Factory. This is the name of the .server file, and is usually placed in /usr/lib/bonobo/servers/.

Once we have the .server file written, we can start writing the code for our applet. Let's start with a simple example: a "Hello World" applet.

Figure 2. Hello World example

#include <string.h>

#include <panel-applet.h>
#include <gtk/gtklabel.h>

static gboolean
myexample_applet_fill (PanelApplet *applet,
   const gchar *iid,
   gpointer data)
{
	GtkWidget *label;

	if (strcmp (iid, "OAFIID:ExampleApplet") != 0)
		return FALSE;

	label = gtk_label_new ("Hello World");
	gtk_container_add (GTK_CONTAINER (applet), label);

	gtk_widget_show_all (GTK_WIDGET (applet));

	return TRUE;
}
PANEL_APPLET_BONOBO_FACTORY ("OAFIID:ExampleApplet_Factory",
                             PANEL_TYPE_APPLET,
                             "The Hello World Applet",
                             "0",
                             myexample_applet_fill,
                             NULL);

Compile this code with the following:

bash$ gcc $(pkg-config --cflags --libs libpanelapplet-2.0) -o my_applet my_applet.c

Note that the PKG_CONFIG_PATH environment variable must contain the path to libpanelapplet-2.0.pc. If you get the following error:

Package libpanelapplet-2.0 was not found in the pkg-config search path.
Perhaps you should add the directory containing `libpanelapplet-2.0.pc'
to the PKG_CONFIG_PATH environment variable
No package 'libpanelapplet-2.0' found

you need to run the following:

bash$ PKG_CONFIG_PATH=/usr/lib/pkgconfigbash$ export $PKG_CONFIG_PATH

Place the executable in the directory /usr/lib/gnome-panel/ (remember this is what we defined in our .server file). Our applet will be represented by the icon myicon.png, which needs to be placed in /usr/share/pixmaps/.

We add our applet to the panel by right-clicking on the Gnome panel, and choose Add to Panel->Amusements->Example Applet.

How does Gnome find the link between our C code and the .server file? That's resolved by the call to PANEL_APPLET_BONOBO_FACTORY. This call takes a number of important parameters, and the function definition is:

PANEL_APPLET_BONOBO_FACTORY (iid, type, name, version, callback, data)

The first parameter specifies the OAFIID, which is a Bonobo identifier, and should be the name of the factory we define in the .server file, ExampleApplet_Factory. The second parameter specifies that this is a PANEL_TYPE_APPLET, and is required for all panel applets. The third parameter is a name which shows up when we query Bonobo for running interfaces. The fourth is the version. We also specify the callback, the entry method in our source, which is myexample_applet_fill(). Lastly, we specify any data to pass to the callback. In our example, we don't pass any, and so use the keyword NULL.

In our code, the function definition for myexample_applet_fill () is:

myexample_applet_fill (applet, iid, data)

Note that the name of this function must match what we have defined in the factory.

We firstly check to make sure that the iid used in the factory matches what we have been passed. If not, we abort, otherwise we continue with the rest of the method.

	if (strcmp (iid, "OAFIID:ExampleApplet") != 0)
		return FALSE;

Currently, our main function doesn't have a lot of functionality. We make sure that the OAFIID matches what the Bonobo factory is expecting, embed a label in the applet widget, and show the widget. Simple, but not very useful. All we can really do with our applet as it currently exists is what is provided by the Gnome libraries when we right-click on any panel applet - we can remove it from the panel, lock it in place, or move it. Not exactly rivetting.

Making It Interesting

Let's change the widget somewhat by using a small picture instead of the text "Hello World". All we need to do is stick a GtkImage into the applet as follows:

image = gtk_image_new_from_file ("/usr/share/pixmaps/mypicture.png");

Then, because the GtkImage widget doesn't receive events (it's true!) and so won't respond to any mouseclicks, we need to place the GtkImage into a GtkEventBox:

event_box = gtk_event_box_new ();
gtk_container_add (GTK_CONTAINER (event_box), image);

g_signal_connect (G_OBJECT (event_box), 
                  "button_press_event",
                  G_CALLBACK (on_button_press),
                  image);

Don't forget to delete the existing code that creates the Hello World label.

Now, we need to have the applet do something when we click on the icon in the panel. So we need to create a function that handles the mouseclick:

Figure 3. Handling a Mouseclick on our Applet

static gboolean
  on_button_press (GtkWidget      *event_box, 
                         GdkEventButton *event,
                         gpointer        data)
  {
	static int window_shown;
	static GtkWidget *window, *box, *image, *label;
	/* Don't react to anything other than the left mouse button;
	   return FALSE so the event is passed to the default handler */
	if (event->button != 1)
		return FALSE;

	if (!window_shown) {
		window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
		box = GTK_BOX (gtk_vbox_new (TRUE, 12));
		gtk_container_add (GTK_CONTAINER (window), box);

		image = GTK_IMAGE (gtk_image_new_from_file ("/usr/share/pixmaps/mypicture.png"));
		gtk_box_pack_start (GTK_BOX (box), image, TRUE, TRUE, 12);

		label = gtk_label_new ("Hello World");
		gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 12);
		
		gtk_widget_show_all (window);
	}
	else
		gtk_widget_hide (GTK_WIDGET (window));

	window_shown = !window_shown;
	return TRUE;
  }

In the above function on_button_press(), we create a new window, and some text. When we click the button, the window is shown; when clicked again, the window is hidden.

Here is the screenshot of the window displayed when we click on the applet with the left mouse button:

Creating A Context Menu

When we right-click the applet icon on the panel, we get the default menu - with the choice of Remove from Panel, Move or Lock. Let's add a few more options - a Help button, an About window, and a Preferences option.

To create the pop-up menu, we need firstly to define the menu. This can be done in the code, as follows:

static const char Context_menu_xml [] =
   "<popup name=\"button3\">\n"
   "   <menuitem name=\"Properties Item\" "
   "             verb=\"ExampleProperties\" "
   "           _label=\"_Preferences...\"\n"
   "          pixtype=\"stock\" "
   "          pixname=\"gtk-properties\"/>\n"
   "   <menuitem name=\"About Item\" "
   "             verb=\"ExampleAbout\" "
   "           _label=\"_About...\"\n"
   "          pixtype=\"stock\" "
   "          pixname=\"gnome-stock-about\"/>\n"
   "</popup>\n";

or in an XML file, loaded at runtime with the method call panel_applet_setup_menu_from_file (). The values used here should be simple to understand; we are adding two menu items, giving them a name that appears in the menu when the right mouse button is clicked, and using stock icons, defined in GtkStockItem.

Secondly, we need to define Bonobo UI verbs:

static const BonoboUIVerb myexample_menu_verbs [] = {
        BONOBO_UI_VERB ("ExampleProperties", display_properties_dialog),
        BONOBO_UI_VERB ("ExampleAbout", display_about_dialog),
        BONOBO_UI_VERB_END
};

This links the verbs, specified when we define the menu above, to the callbacks we will use. In other words, when the user chooses Preferences... from the pop-up menu, our applet will enter the display_properties_dialog () function.

Lastly, we need to construct the menu, tying the above two steps together:

panel_applet_setup_menu (PANEL_APPLET (myexample->applet),
                         Context_menu_xml,
                         myexample_menu_verbs,
                         myexample);

Note that the last parameter is the user_data, which we can then use within the callbacks.

Our callbacks will have the method signature:

static void
myexample_applet_properties_dialog (BonoboUIComponent *uic,
                                 struct MultiRes *applet) {
	/* Construct the properties dialog and show it here */
	...
}

Common Questions

1. How do I debug my applet? I often use printf () calls to see what my code is doing, and I can't do this with an applet!
2. I don't have an icon for my applet, so I didn't include it in the .server file. I can't find my applet in the list of available applets when trying to add it to the panel.
3. My applet isn't available in the menu Add To Panel.
1.

How do I debug my applet? I often use printf () calls to see what my code is doing, and I can't do this with an applet!

Start your applet from the command line. If you then Add it to the panel, you'll notice that all output goes to the console (a tip from Glynn Foster).

2.

I don't have an icon for my applet, so I didn't include it in the .server file. I can't find my applet in the list of available applets when trying to add it to the panel.

Applets require an icon to be specified before they can be included in the list of available applets.

3.

My applet isn't available in the menu Add To Panel.

When you compile and install and applet by hand, the applet is installed into /usr/local/ by default. Gnome looks for its panel applets in /usr. You can run ./configure --prefix=/usr to install your applet to the preferred location. You will also need to logout and log back in to Gnome for the applet to become available.

Conclusion

Creating a panel applet is not difficult. However, it can be as complex as you wish. At some stage, though, you should consider whether the complexity of your applet justifies making it a full-blown application. As you add more widgets, you may find that using Glade combined with libxml is an easier way to build the GUI.

One important caveat with regard to the example used above. I've compiled the applet from the command line for simplicity; a real applet would likely use a Makefile; this allows for details currently hardcoded (like the location of the icon) to be moved to the Makefile, improving maintainability.

More Information

  • Download the example applet created in this tutorial. The tarball also includes the appropriate Makefiles required to completely configure and install your applets.

  • Gnome applets are available for download in Gnome CVS under the gnome-applets directory.

  • Installing the documentation for libpanel-applet2 gives a great (but incomplete) guide to writing applets.

  • Details on panel applets, including an old tutorial for GTK1.x applets, can be found at the Gnome Developer web site.