excuse my caps…
Today is August 21st — the Summer of Code deadline.
It’s been a pretty fun ride. I’ve been getting up close and personal with the gnome-panel code in ways that I wouldn’t previously have imagined to be possible.
I’ve been working on an API for applets. In addition to this API, I’ve written an implementation of a client-side library for this API and in-panel code for this library to communicate with.
The communication is done by way of DBUS and XEmbed. The API has been constructed in such a way, however, that other than GTK itself, all underlying implementation detail is invisible. If DBUS should fall out of fashion, it will be possible to replace the back-end implementation without an API shift. This is not the case for the current applet system which is very much tied to Bonobo.
Note: the API is UNSTABLE. It will change. I promise.
From the standpoint of the user (applet writer) a class is provided named GnomeApplet. You subclass this class to create your own types of applets. To give a feel for this API, I will present an example of a very simple applet.
The applet in question is a simple clock. An icon has been added for demonstration purposes.
I’ll assume for the sake of simplicity that the following function has been provided:
const char *format_current_time (gboolean seconds, gboolean _24hour);
This function returns a string representing the current time either in 24 or 12 hour format and with or without seconds.
The code of the applet is included here:
#include <libgnome-applet.h>
GNOME_APPLET_DEFINE (ClockApplet, clock_applet,
gboolean _24hour;
gboolean show_seconds;
);
static gboolean
update_time (ClockApplet *clock)
{
gnome_applet_set_text (GNOME_APPLET (clock),
format_current_time (clock->show_seconds, clock->_24hour));
return TRUE;
}
static void
update_boolean (GnomeApplet *applet, const char *key,
gboolean value, gpointer user_data)
{
*(gboolean *) user_data = value;
update_time ((ClockApplet *) applet);
}
static void
clock_applet_initialise (ClockApplet *clock)
{
GnomeApplet *applet = GNOME_APPLET (clock);
gnome_applet_set_name (applet, "Clock Applet");
gnome_applet_set_persist (applet, TRUE);
gnome_applet_add_dropdown_widget (applet, gtk_calendar_new ());
gnome_applet_set_image_from_stock (applet, GTK_STOCK_YES);
gtk_timeout_add (1000, (GtkFunction) update_time, clock);
gnome_applet_config_watch_boolean (applet, "24hour", update_boolean,
&clock->_24hour, NULL);
gnome_applet_config_watch_boolean (applet, "24hour", update_boolean,
&clock->show_seconds, NULL);
}
The first thing to notice is the GNOME_APPLET_DEFINE line. In its simple form, this line is like a G_DEFINE_TYPE instantiation minus the parent class (since the parent class is always GnomeApplet).
For example:
GNOME_APPLET_DEFINE (ClockApplet, clock_applet);
However, this macro aims to reduce your typing for you. As such, you do not need to define structures for your class. If you want additional items in these structures (other than the parent class) then you give them using the syntax seen in the example code.
The update_time function is uninteresting. It shows that the fields defined in the extended syntax of GNOME_APPLET_DEFINE
are accessed as you’d expect them to be. It also shows use of the gnome_applet_set_text
function.
The new applet API is a hybrid API. As a subclass of GtkContainer
, you can gtk_container_add
a widget to the applet and it will be displayed on the panel. You are much more likely, however, to want to use the set_text and set_image functions.
The first time you use a set_text or set_image function the applet will internally create a GnomeAppletLayoutBox
and add it to itself. It will then request the creation of the image or label inside of this box.
The GnomeAppletLayoutBox
system allows you to easily create applets that feature 0 or 1 icons and 0 or 1 text labels. In addition, it gracefully handles horizontal and vertical panels and drawers of varying thickness.
The label that is created is not a GtkLabel
, but rather a GnomeAppletLabel
. GnomeAppletLabel
has some functions that make it easier to use than a simple GtkLabel
for purposes of putting in a GnomeAppletLayoutBox
. The most noticeable feature of a GnomeAppletLabel
, however, is that when placed on a coloured, pixmap or transparent panel, it shows a drop shadow like Nautilus shows on its desktop contents.
update_boolean
is not a very interesting function either. It simply updates a boolean value pointed at by the user_data parameter
and calls update_clock
. The need for functions like this may disappear as the API becomes more developed.
clock_applet_initialise
is the most important function here. When you use GNOME_APPLET_DEFINE
the 2nd parameter has _initialise
added to it and this is the name of the only function that you must implement.
This function is NOT a normal GObject
init function. It would be called clock_applet_init
if that were the case. This function is, rather, called after the object has been entirely constructed and connected to the panel. This means that you can perform function calls from it which query the state of the panel.
We perform a number of calls here:
gnome_applet_set_name
tells the panel the name of the applet. This is used in UI that the user sees concerning the applet. For example, of the applet were to unexpectedly exit, the message dialog shown would be customised with the applet’s name.
gnome_applet_set_persist
registers the applet as a persistent applet. When an applet is first created it is non-persistent. This means that the applet can come and go as it pleases. The panel will make no attempt to start the applet on login and won’t complain if the applet just vanishes. This behaviour is useful for programs like Gossip or Muine, where the life of the applet is tied to the life of the application, rather than the life of the session. It is also useful for applets that wish to come and go as hardware is added or removed.
If an applet is marked persistent then its current path is noted down in the panel’s GConf tree. On login the applet will be invoked and asked to join the panel at its previous location.
gnome_applet_add_dropdown_widget
is a new feature. When passed a GtkWidget
, this function will add the widget to a dropdown display. Many applets would or currently do benefit from this functionality. For the clock, it makes sense to have a dropdown calendar. For the weather applet you could have a dropdown detailed forecast. For the mixer, a dropdown slider. If a dropdown is registered then it is displayed when the left mouse button is clicked. If the user moves their mouse from applet to applet while a dropdown is displayed then the dropdowns of the different applets are displayed.
gnome_applet_set_image_from_stock sets the image shown with an applet. There are functions for setting from stock, from file, from icon theme, from pixmaps and from pixbufs. See the discussion above about GnomeAppletLayoutBox.
gtk_timeout_add
is gtk_timeout_add
.
gnome_applet_config_watch_boolean
is showing a very small part of the configuration system that exists. It was decided, for a number of reasons, that applets would not link against GConf. It is still possible to do this for yourself and use GConf functions with your applet, but this behaviour is not recommended. All configuration requests are, instead, sent to the panel via DBUS.
There are a number of reasons for this:
- The configuration information associated with an applet should be tied to the panel (as it is now). The panel might be using a different configuration system then the applet. (think: mixing KDE, xfce, GNOME).
- If GNOME itself shifts configuration systems, the GConf API is completely hidden. (think: you won’t need to rewrite your applet).
- There is a memory savings associated with not linking to GConf in every applet, not firing up the machinery in every applet and not having a server-side connection for every applet.
- There is an additional benefit interms of load time. Since the panel can preload all of the information for all of the applets in a single roundtrip to the GConf daemon, the number of roundtrips is reduced. This means less contention for a heavily contended resource.
There are a few unfortunate effects:
- You can not store pairs or lists. Only values which fit in a GValue are supported. I could add more API, but the point is to not expose GConf-specifics.
- I had to do a lot more typing. More typing introduces the possibility of more bugs and certainly, the interface won’t share the exact semantics of GConf.
In general, there are 5 flavours of each function. One for each of GValue, string, int, boolean, double. I’ll introduce the integer variants:
-
gnome_applet_config_get_int
– gets a key of a given name and returns it in a pass-by-reference variable. This function returns TRUE if the key existed or FALSE if it did not.
-
gnome_applet_config_get_int_default
– gets a key of a given name and returns it. If the key did not exist then a user-provided default value is returned.
-
gnome_applet_config_set_int
– sets a key of a given name to a given value. Returns TRUE if successful.
-
gnome_applet_config_watch_int
– registers a watch function on a given key. A user_data (with corresponding destroy notify) field is also given. When this function is first called, a synthetic notify event is immediately generated. You can, therefore, use this function to initialise variables without having to manually call one of the ‘get’ functions.
There is more API than shown here, but this should give a good overview of the general flavour. This project is not yet done! The following features are planned to be completed before GNOME 2.18 when, if all goes well, this code will ship as part of gnome-panel.
Immediate TODOs:
- gtk-doc for the client-side API with the documentation posted on developer.gnome.org
- integration of Vytautas’ work to make multiple instances of the same applet use the same process
- support for dynamic loading of applets into the panel to further reduce memory use
- improved support for finding and loading persistent applets (the path for invoking applets on startup is currently hard-coded to my working directory)
Other TODOs:
- gather feedback from early adopters of the API. What needs to change? Can there be some more convenience functions?
- look into language bindings (python and C# are likely early candidates)
- bug fixes! I can’t even imagine how many bugs are in the code. It will take time to reveal them all and get the code to be stable enough for general use.
A recent version of the code is usually available at http://desrt.mcmaster.ca/random/applets.tar.gz. I’m going to wait until after the GNOME CVS branches 2.16 stable until I commit the code to CVS HEAD.