HildonDesktopPluginHowto

  1. Prerequisites
  2. Task Navigator plugin
  3. Status Bar plugin
  4. Home plugin
  5. The Desktop file
  6. Building the plugins
  7. Installing and testing
  8. Conclusions

Hildon Desktop is a major rewrite of maemo-af-desktop, the main UI component of Maemo Platform. It features a new plugin system which enables developers to write plugin in several ways. In this tutorial, we'll show the steps to write a plugins for Task Navigator, Status Bar and Home using the new GTypeModule-based API. We'll focus on the source code related information here. Therefore, build system and debian packaging will not discussed.

MoinMoinWiki Macro: TableOfContents()

Prerequisites

To fully understand this tutorial, you're supposed to know:

  • C programming
  • GLib dynamic type system and GObject
  • GTK+

Also, you will need to have a fully working SDK with Sardine to test the plugins presented here.

Task Navigator plugin

Task Navigator (TN) plugins should inherit from libhildondesktop's TaskNavigatorItem. Suppose you want to write a TN plugin called MyNavigatorPlugin. The header file (my-navigator-plugin.h) should look like an usual GObject-based class/object declation.

#!cplusplus
#ifndef MY_NAVIGATOR_PLUGIN_H
#define MY_NAVIGATOR_PLUGIN_H

#include <glib-object.h>

/* For Task Navigator plugins */
#include <libhildondesktop/tasknavigator-item.h>

G_BEGIN_DECLS

/* Common struct types declarations */
typedef struct _MyNavigatorPlugin MyNavigatorPlugin;
typedef struct _MyNavigatorPluginClass MyNavigatorPluginClass;
typedef struct _MyNavigatorPluginPrivate MyNavigatorPluginPrivate;

/* Common macros */
#define MY_TYPE_NAVIGATOR_PLUGIN            (my_navigator_plugin_get_type ())
#define MY_NAVIGATOR_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MY_TYPE_NAVIGATOR_PLUGIN, MyNavigatorPlugin))
#define MY_NAVIGATOR_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MY_TYPE_NAVIGATOR_PLUGIN, MyNavigatorPluginClass))
#define MY_IS_NAVIGATOR_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MY_TYPE_NAVIGATOR_PLUGIN))
#define MY_IS_NAVIGATOR_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MY_TYPE_NAVIGATOR_PLUGIN))
#define MY_NAVIGATOR_PLUGIN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MY_TYPE_NAVIGATOR_PLUGIN, MyNavigatorPluginClass))

/* Instance struct */
struct _MyNavigatorPlugin
{
  TaskNavigatorItem tnitem;

  MyNavigatorPluginPrivate *priv;
};

/* Class struct */
struct _MyNavigatorPluginClass
{
  TaskNavigatorItemClass parent_class;
};

GType  my_navigator_plugin_get_type  (void);

G_END_DECLS

#endif /* MY_NAVIGATOR_PLUGIN_H */

And source code file (my-navigator-plugin.c) should look like this:

#!cplusplus
#include <gtk/gtk.h>
#include <glib-object.h>

#include <libhildondesktop/libhildondesktop.h>

#include "my-navigator-plugin.h"

/* Utility macro which defines the plugin dynamic type bits */
HD_DEFINE_PLUGIN (MyNavigatorPlugin, my_navigator_plugin, TASKNAVIGATOR_TYPE_ITEM);

static void
my_navigator_plugin_init (MyNavigatorPlugin *navigator_plugin)
{
  GtkWidget *button;

  button = gtk_button_new ();

  gtk_button_set_image (GTK_BUTTON (button), 
                        gtk_image_new_from_stock (GTK_STOCK_CONVERT,
                                                  GTK_ICON_SIZE_LARGE_TOOLBAR));

  gtk_widget_set_size_request (button, 80, 80);

  gtk_widget_show_all (button);

  gtk_container_add (GTK_CONTAINER (navigator_plugin), button);
}

static void
my_navigator_plugin_class_init (MyNavigatorPluginClass *class)
{
}

Important notes about the source code:

  • You must use the HD_PLUGIN_DEFINE macro so that the exported module symbols are correctly exposed;
  • It's mandatory to write at least the class init and instance init functions: my_navigator_plugin_class_init() and my_navigator_plugin_init() respectively in this case;
  • In this TN plugin example, we just created a plugin which simply shows a button. For doing so, we creates a GtkButton instance and then pack it inside our plugin (TaskNavigatorItem) which is a GtkBin.

Status Bar plugin

Status Bar (SB plugins should inherit from libhildondesktop's StatusbarItem. Suppose you want to write a SB plugin called MyStatusbarPlugin. The header file (my-statusbar-plugin.h) should look like an usual GObject-based class/object declation.

#!cplusplus
#ifndef MY_STATUSBAR_PLUGIN_H
#define MY_STATUSBAR_PLUGIN_H

#include <glib-object.h>

/* For Status Bar plugins */
#include <libhildondesktop/statusbar-item.h>

G_BEGIN_DECLS

/* Common struct types declarations */
typedef struct _MyStatusbarPlugin MyStatusbarPlugin;
typedef struct _MyStatusbarPluginClass MyStatusbarPluginClass;
typedef struct _MyStatusbarPluginPrivate MyStatusbarPluginPrivate;

/* Common macros */
#define MY_TYPE_STATUSBAR_PLUGIN            (my_statusbar_plugin_get_type ())
#define MY_STATUSBAR_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MY_TYPE_STATUSBAR_PLUGIN, MyStatusbarPlugin))
#define MY_STATUSBAR_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MY_TYPE_STATUSBAR_PLUGIN, MyStatusbarPluginClass))
#define MY_IS_STATUSBAR_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MY_TYPE_STATUSBAR_PLUGIN))
#define MY_IS_STATUSBAR_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MY_TYPE_STATUSBAR_PLUGIN))
#define MY_STATUSBAR_PLUGIN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MY_TYPE_STATUSBAR_PLUGIN, MyStatusbarPluginClass))

/* Instance struct */
struct _MyStatusbarPlugin
{
  StatusbarItem sbitem;

  MyStatusbarPluginPrivate *priv;
};

/* Class struct */
struct _MyStatusbarPluginClass
{
  StatusbarItemClass parent_class;
};

GType  my_statusbar_plugin_get_type  (void);

G_END_DECLS

#endif /* MY_STATUSBAR_PLUGIN_H */

And source code file (my-statusbar-plugin.c) should look like this:

#!cplusplus
#include <gtk/gtk.h>
#include <glib-object.h>

#include <libhildondesktop/libhildondesktop.h>

#include "my-statusbar-plugin.h"

/* Utility macro which defines the plugin dynamic type bits */
HD_DEFINE_PLUGIN (MyStatusbarPlugin, my_statusbar_plugin, STATUSBAR_TYPE_ITEM);

static void
my_statusbar_plugin_init (MyStatusbarPlugin *statusbar_plugin)
{
  GtkWidget *button;

  button = gtk_button_new ();

  gtk_button_set_image (GTK_BUTTON (button), 
                        gtk_image_new_from_stock (GTK_STOCK_CONVERT,
                                                  GTK_ICON_SIZE_LARGE_TOOLBAR));

  gtk_widget_show_all (button);

  gtk_container_add (GTK_CONTAINER (statusbar_plugin), button);
}

static void
my_statusbar_plugin_class_init (MyStatusbarPluginClass *class)
{
}

Important notes about the source code:

  • You must use the HD_PLUGIN_DEFINE macro so that the exported module symbols are correctly exposed;
  • It's mandatory to write at least the class init and instance init functions: my_statusbar_plugin_class_init() and my_statusbar_plugin_init() respectively in this case;
  • In this SB plugin example, we just created a plugin which simply shows a button. For doing so, we creates a GtkButton instance and then pack it inside our plugin (StatusbarItem) which is a GtkBin child.
  • SB plugins can be conditional. This means you can manage when the plugin should be shown or hidden depending on certain conditions. You can define this by setting the "condition" property to TRUE (show) or FALSE (hidde). By default, the condition is set to TRUE meaning that the plugin will be shown after being loaded.

Home plugin

Home plugins should inherit from libhildondesktop's HildonDesktopHomeItem. Suppose you want to write a Home plugin called MyHomePlugin. The header file (my-home-plugin.h) should look like an usual GObject-based class/object declation.

#!cplusplus
#ifndef MY_HOME_PLUGIN_H
#define MY_HOME_PLUGIN_H

#include <glib-object.h>

/* For Home plugins */
#include <libhildondesktop/hildon-desktop-home-item.h>

G_BEGIN_DECLS

/* Common struct types declarations */
typedef struct _MyHomePlugin MyHomePlugin;
typedef struct _MyHomePluginClass MyHomePluginClass;
typedef struct _MyHomePluginPrivate MyHomePluginPrivate;

/* Common macros */
#define MY_TYPE_HOME_PLUGIN            (my_statusbar_plugin_get_type ())
#define MY_HOME_PLUGIN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MY_TYPE_HOME_PLUGIN, MyHomePlugin))
#define MY_HOME_PLUGIN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MY_TYPE_HOME_PLUGIN, MyHomePluginClass))
#define MY_IS_HOME_PLUGIN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MY_TYPE_HOME_PLUGIN))
#define MY_IS_HOME_PLUGIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MY_TYPE_HOME_PLUGIN))
#define MY_HOME_PLUGIN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MY_TYPE_HOME_PLUGIN, MyHomePluginClass))

/* Instance struct */
struct _MyHomePlugin
{
  HildonDesktopHomeItem hitem;

  MyHomePluginPrivate *priv;
};

/* Class struct */
struct _MyHomePluginClass
{
  HildonDesktopHomeItemClass parent_class;
};

GType  my_home_plugin_get_type  (void);

G_END_DECLS

#endif /* MY_HOME_PLUGIN_H */

And source code file (my-home-plugin.c) should look like this:

#!cplusplus
#include <gtk/gtk.h>
#include <glib-object.h>

#include <libhildondesktop/libhildondesktop.h>

#include "my-home-plugin.h"

/* Utility macro which defines the plugin dynamic type bits */
HD_DEFINE_PLUGIN (MyHomePlugin, my_home_plugin, HILDON_DESKTOP_TYPE_HOME_ITEM);

static void
my_home_plugin_init (MyHomePlugin *home_plugin)
{
  GtkWidget *button;

  button = gtk_button_new ();

  gtk_button_set_image (GTK_BUTTON (button), 
                        gtk_image_new_from_stock (GTK_STOCK_CONVERT,
                                                  GTK_ICON_SIZE_LARGE_TOOLBAR));

  gtk_widget_show_all (button);

  /* Set the resizing behavior */
  hildon_desktop_home_item_set_resize_type (HILDON_DESKTOP_HOME_ITEM (home_plugin),
                                            HILDON_DESKTOP_HOME_ITEM_RESIZE_BOTH);

  gtk_container_add (GTK_CONTAINER (home_plugin), button);
}

static void
my_home_plugin_class_init (MyHomePluginClass *class)
{
}

Important notes about the source code:

  • You must use the HD_PLUGIN_DEFINE macro so that the exported module symbols are correctly exposed;
  • It's mandatory to write at least the class init and instance init functions: my_home_plugin_class_init() and my_home_plugin_init() respectively in this case;
  • In this Home plugin example, we just created a plugin which simply shows a button. For doing so, we creates a GtkButton instance and then pack it inside our plugin (HildonDesktopHomeItem) which is a GtkBin child;
  • Home plugins can define their resizing behavior with the hildon_desktop_home_item_set_resize_type() method. It can be HILDON_DESKTOP_HOME_ITEM_RESIZE_NONE (allows no resizing), HILDON_DESKTOP_HOME_ITEM_RESIZE_VERTICAL (allows vertical resizing), HILDON_DESKTOP_HOME_ITEM_RESIZE_HORIZONTAL (allows horizontal resizing), or HILDON_DESKTOP_HOME_ITEM_RESIZE_BOTH (allows vertical and horizontal resizing);
  • Home plugins can define their minimum width and height (in the case their resizable) by setting the "minimum-width" and "minimum-height" properties.
  • Home plugins can detect when the home goes background by connecting to the "background" and "foreground" signals or overriding the associated handlers.
  • Home plugins can provide a submenu item for the Applet Settings menu by connecting to the "settings" signal or overriding the associated handler.

The Desktop file

Once you have the plugin code ready, you need to create a desktop file which provides important information about the plugin which will be used by the plugin loader to load the plugin in Hildon Desktop. The desktop file is a composed by a set of key/value pairs inside a Desktop Entry group. Here's one example of a plugin desktop file:

[Desktop Entry]
Name=Your Plugin Name
Type=default
X-Path=myplugin.so

So, for our TN plugin (MyNavigatorPlugin), the desktop file (my-navigator-plugin.desktop) would look like this:

[Desktop Entry]
Name=My Task Navigator Plugin
Type=default
X-Path=libmynavigatorplugin.so

For our SB plugin (MyStatusbarPlugin), the desktop file (my-statusbar-plugin.desktop) would look like this:

[Desktop Entry]
Name=My Status Bar Plugin
Type=default
X-Path=libmystatusbarplugin.so

So, for our Home plugin (MyHomePlugin), the desktop file (my-home-plugin.desktop) would look like this:

[Desktop Entry]
Name=My Home Plugin
Type=default
X-Path=libmyhomeplugin.so

The Name key should contain a intuitive name for the plugin. The Type key should have default as value because we're developing a plugin with the default GTypeModule-based API. The X-Path should have the filename for the plugin shared object file or an absolute path to this file. If only the filename is provided, Hildon Desktop will look for this file in ${PREFIX}/lib/hildon-desktop.

Notice that for any of the plugin types (Task Navigator, Status Bar or Home), the desktop file have quite the same keys. The desktop file must be installed in the desktop files directory of the target desktop containers. If you want the plugin to be used in Home area, you should install the desktop file in ${PREFIX}/share/applications/hildon-home. For Task Navigator plugins, in ${PREFIX}/share/applications/hildon-navigator. For Status Bar plugins, in ${PREFIX}/share/applications/hildon-statusbar.

To make sure you're always installing the plugin files in the correct location, you can always use the pkg-config tool to get the wanted information from the osso-af-settings package.

To get the lib directory for Hildon Desktop plugins:

pkg-config osso-af-settings --variable=hildondesktoplibdir

To get the desktop files directory for Task Navigator plugins:

pkg-config osso-af-settings --variable=tasknavigatordesktopentrydir

To get the desktop files directory for Status Bar plugins:

pkg-config osso-af-settings --variable=statusbardesktopentrydir

To get the desktop files directory for Home plugins:

pkg-config osso-af-settings --variable=homedesktopentrydir

Building the plugins

Well, of course you want to create a complete build infrastructure with autotools but as this out of the scope of this tutorial. So, you can manually build the source code like this:

Building the TN plugin:

gcc -shared -o libmynavigatorplugin.so my-navigator-plugin.c `pkg-config <del>libs </del>cflags gtk+-2.0 glib-2.0 libhildondesktop`

Building the SB plugin:

gcc -shared -o libmystatusbarplugin.so my-statusbar-plugin.c `pkg-config <del>libs </del>cflags gtk+-2.0 glib-2.0 libhildondesktop`

Building the Home plugin:

gcc -shared -o libmyhomeplugin.so my-home-plugin.c `pkg-config <del>libs </del>cflags gtk+-2.0 glib-2.0 libhildondesktop`

Installing and testing

Again, considering we don't have the build infrastructure here, you can just manually copy the files to the correct locations for now.

For the TN plugin:

cp libmynavigatorplugin.so `pkg-config osso-af-settings --variable=hildondesktoplibdir`
cp my-navigator-plugin.desktop `pkg-config osso-af-settings --variable=tasknavigatordesktopentrydir`

For the SB plugin:

cp libmystatusbarplugin.so `pkg-config osso-af-settings --variable=hildondesktoplibdir`
cp my-statusbar-plugin.desktop `pkg-config osso-af-settings --variable=statusbardesktopentrydir`

For the Home plugin:

cp libmyhomeplugin.so `pkg-config osso-af-settings --variable=hildondesktoplibdir`
cp my-home-plugin.desktop `pkg-config osso-af-settings --variable=homedesktopentrydir`

Now, to test the Home plugin, you can simply select "Home -> Select applets..." and select our plugin in the list. To test the the SB and TN plugins, you can use the Navigation applet in Control Panel to load them.

Conclusions

As you could see, now it's quite simple to write plugins for Hildon Desktop in C. We've created a set of example plugins with the complete build infrastructure and the debian packaging bits. Grab it from our repository:

https://stage.maemo.org/svn/maemo/projects/haf/branches/maemo-af-desktop/example-plugins

You can use it as a starting point for your next awesome Hildon Desktop plugin! Go Go Go!