How to write maemo desktop plugins for maemo

This document has been reviewed for maemo 3.x.

This document is released under the GPL license.

Author:Karoliina Salminen karoliina.t.salminen (at) nokia.com>

Introduction to maemo desktop architecture

desktop-architecture-picture

This tutorial shows how to write maemo desktop's Status Bar, Task Navigator, Control Panel and Home plugins.

Maemo desktop consists of three subcomponents: task navigator, Home and status bar. They support additional plugins that are displayed on each these components and which extend the functionality of the host application. The plugins are opened by the host application with dlopen and the plugin has to implement an API, which the host application (TN, Home or SB) then calls. This document is a tutorial, it explains how to use the plugin API and how to write plugins that work well with the maemo desktop.

Task Navigator Plugins

The task navigator implements a modular plugin architecture that allows binary plugins to be loaded into the application. This extends the functionality of the task navigator and is achieved with task buttons. The license for the task navigator plugin can be an open source or closed source; in this way task navigator is versatile for ideal for use in many kinds of devices and applications.

Task navigator plugin API

Task navigator plugins can have any widget as their representation on the task navigator plugin area. The plugin representation, which is statically visible in the task navigator, can be, for example, GtkButton. Task navigator loads the library and displays the main widget of the plugin in the plugin area of TN.

The plugins are responsible for their own signals, for example, clicked/toggle and related and event and so on, and implementing the callback functions for them. Task navigator only provides a container for the plugins, the plugins are responsible for implementing their own UI.

Desktop file location

Task navigator plugins must provide a desktop file. It is placed in the following directory: usr/share/applications/hildon-navigator/

Desktop file contents:

A description of the .desktop file's required contents follows:

The Task Navigator Configuration Control Panel applet is the only one which is involved with the contents of these .desktop files, so task navigator does not read them and the absence of the file does not prevent the plugin from working if the plugin configuration file (described later) is in correct condition.

The following is an example .desktop file for the task navigator applet:

[Desktop Entry]
Name=tn_gps_plugin
Icon=qgn_grid_tasknavigator_gps
X-task-navigator-plugin=libtn_gps_plugin.so
    

The Encoding, Version and Comment fields are optional fields, the others mandatory.

hildon_navigator_lib_create:

You must create/init applet's data in the hildon_navigator_lib_create function. Returning it as a pointer to the task navigator, which then passes it back to you, means you avoid using global variables if you so wish.

The following is an example implementation:

/* Functions called by Task Navigator */

/* This is called for TN to create the plugin. Use this place to allocate your data */
void* hildon_navigator_lib_create ()
{
  plugin_private_data_t *privdata;
  privdata = g_new0(plugin_private_data_t, 1);
  return privdata;
}
    

hildon_navigator_lib_get_button_widget:

The function creates the widget, which is displayed on the task navigator. As mentioned earlier, this is typically a GtkButton. The task navigator automatically sets the button to the correct size.

If you want to have the button skinned properly, set the name for the button as "hildon-navigator-button-one". (gtk_widget_set_name-function).

The following is an example implementation:

GtkWidget *
hildon_navigator_lib_get_button_widget ( void *data )
{
    GtkWidget *img;
    GtkWidget *button;
    
    img = gtk_image_new_from_icon_name ("my_own_icon_name_without_extension", GTK_ICON_SIZE_DIALOG); /* GTK_ICON_SIZE_DIALOG happens to be proper size, it is used for that reason here  */
    
    button = gtk_button_new_with_label("");
    
    data->button0 = button;

    gtk_button_set_image(GTK_BUTTON(button), img);
    
    return button; /* your widget is returned to Task Navigator */
}
    

You might want to put a pointer to the button to the data that you return, since it is used later on (data->button0).

The hildon_navigator_lib_initialize_menu

The task navigator calls this to initialize the menu. This is called when all plugins are loaded to the task navigator. This is the place to initialise your plugin's UI, which is not visible when your button is not pressed. The reason for this being a separate function from other initilisation functions is that the TN UI is shown as ready before all UI's are initialised and therefore speeds the startup time (because it already takes some time before the end user taps the first time some of the task buttons).

The following is an example implementation:

/* This is called by Task Navigator to initialize the menu */
void hildon_navigator_lib_initialize_menu (void *data)
{
    GtkWidget *menuitem0 = NULL;
    GtkWidget *menuitem1 = NULL;
    plugin_private_data_t *privptr;

    privptr = (plugin_private_data_t *) data;
    privptr->menu0 = gtk_menu_new();
    
    menuitem0 = gtk_image_menu_item_new_with_label("Hello world");
    gtk_menu_shell_append(GTK_MENU_SHELL(privptr->menu0), menuitem0);
    gtk_widget_show(menuitem0);

    menuitem1 = gtk_image_menu_item_new_with_label("Perform some magic");
    gtk_menu_shell_append(GTK_MENU_SHELL(privptr->menu0), menuitem1);
    gtk_widget_show(menuitem1);

    /* You can use either button-press-event or toggled or clicked depending 
        on which suits your use the best, the framework doesn't limit your choices */

    g_signal_connect (G_OBJECT(privptr->button0), "toggled",
                    G_CALLBACK(my_task_button_toggled), data);
    
    /* Listen to the menu going away */

    g_signal_connect (G_OBJECT(privptr->menu0), "hide",
                    G_CALLBACK(my_task_menu_hidden), data);  
}

/* Signal listener callback functions, see hildon_navigator_lib_initialize_menu implementation */

gboolean my_task_button_toggled(GtkToggleButton *widget,
                                   gpointer data)
{
   plugin_private_data_t *privptr = (plugin_private_data_t *) data;

   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(privptr->button0)))
   {
        gtk_menu_shell_deactivate(GTK_MENU_SHELL(privptr->menu0));
   } else {
        gtk_menu_shell_select_first(GTK_MENU_SHELL(privptr->menu0), TRUE);
   }

   return TRUE;
}


/* Since the button doesn't get an release event, we'll tell it to
* unactivate itself explicitly when the menu is hidden
*/
void my_task_menu_hidden (GtkWidget *widget, gpointer data)
{
    plugin_private_data_t *privptr;   
    privptr = (plugin_private_data_t *) data;
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(privptr->button0), FALSE);

}
    

hildon_navigator_lib_destroy:

You must free your data on the hildon_navigator_lib_destroy -function. If you have chosen to not to implement this as global variable, the task navigator passes the pointer back at you. You should also destroy your own widgets here, particularly the menu.

The following is an example implementation:

/* This is called for TN to remove the plugin. Use this place to free your data. */
void hildon_navigator_lib_destroy (void *data)
{
    plugin_private_data_t *privptr;
    privptr = (plugin_private_data_t *)data;

    gtk_widget_destroy(GTK_BUTTON(privptr->button0));

    gtk_widget_destroy(GTK_MENU(privprt->menu0));
    g_free(data);
}
    

Task Navigator Configuration Control Panel Applet Configuration:

The system configuration file is: /etc/hildon-navigator/plugins.conf

This file has factory plugin configuration. It can contain mandatory flags, which prevent the removal of the plugin by Task Navigator Configuration Control Panel Applet.

The user configuration file: /home/user/.osso/hildon-navigator/plugins.conf

The file format specification is as follows:

[mytnplugin1]
Library = filename_of_so_library
Position = 0
Desktop-file=mytnplugin1.desktop
Mandatory = false

[mytnplugin2]
Library = filename_of_your_so_library
Position = 1
Mandatory = false
Desktop-file=mytnplugin2.desktop


[mytnpluginN]
Library = filename_of_your_so_library
Position = 1
Mandatory = false
Desktop-file=mytnplugin2.desktop

The amount of plugins is not limited in the configuration file. However, in the form factor of Nokia Internet Tablet there are places for only two plugins to be displayed at a time. If the screen in the maemo-device was longer, then more places could be used at the same time, therefore justifying the possibility of having more than two plugins configured in the configuration file.

Task Navigator Configuration Control Panel applet reads the .desktop files of the plugins. Task Navigator Control Panel configuration applet reads the applet information from the .desktop files.

The task navigator does not use these files, so they are made solely for the purpose of the Task Navigator Configuration Applet. If you want to make your plugin interact correctly with the Task Navigator Configuration Applet, the desktop file needs to be in place. It can be implemented in that way, so that your postinstall script etc. could change the contents of the plugin configuration file automatically so then the applet usage would not be required to make your plugin visible. If however, you are doing applet for a maemo-system that is not a built on top of a Nokia commercial rootstrap but rather on a development rootstrap, there is no concern over the Task Navigator Configuration Control Panel applet compatibility, you need only edit the config files mentioned directly.

Home plugins

As a new feature for IT2007, home plugins can be now resized if the resizability flag is mentioned on the plugin's .desktop-file.

Each home applet needs to provide a .desktop file. The .desktop files must be placed in the /usr/share/applications/hildon-home directory.

You need to provide a .desktop file for each plugin. The applets must not implement hildon_home_applet_lib_properties, but implement instead hildon_home_applet_lib_settings (described later in this tutorial).

The Home applet .desktop file is named as home<yourappletsname>.desktop and resides under /usr/share/applications/hildon-home.

The contents of the .desktop file are:

[Desktop Entry]
Name=your_logical_name_here
Comment=engineering english name of applet
Type=HildonHomeApplet
X-home-applet=lib_some_applet_name_.so
X-home-applet-resizable=YES
X-home-applet-minwidth=400
X-home-applet-maxheight=200

The following is an example metar-applet.desktop

[Desktop Entry]
Name=metar-applet
Comment=Aviation Weather Home Applet
Type=HildonHomeApplet
X-home-applet=libmetar_applet.so

API required to implement for a simple applet with no information to state save A simple applet must implement the following functions:

hildon_home_applet_lib_initialize
  void hildon_home_applet_lib_deinitialize

Other API functions can remain empty and return NULL when a pointer return value is required. The required #include statement is:

#include <hildon-home-plugin/hildon-home-plugin-interface.h>

hildon_home_applet_lib_initialize:

/**
* @hildon_home_applet_lib_initialize
*
* @param state_data Not in use
*
* @param state_size Not in use
* @param widget Return parameter the Applet should fill in with
*                   it's main widget.
* @returns A pointer to applet specific data
*   
* Applet initialization. 
* This is called when Home loads the applet. It may load it self
* in initial state or in state given. It creates a GtkWidget that
* Home will use to display the applet. 
*/

void *hildon_home_applet_lib_initialize(void *state_data, 
int *state_size, 
GtkWidget **widget);

The following is an example implementation:


void* 
hildon_home_applet_lib_initialize (void *state_data,
int *state_size, 
GtkWidget **widget)
{ 
    osso_context_t *osso;
    
    /* Initialize libosso */
    osso = osso_initialize ("METAR_APP", "0.1", FALSE, NULL);
    if (!osso) {
        g_debug ("Error initializing the osso" 
        "maemo metar applet");
        return NULL;
    }
    
    /* 
    *  Call private function metar_ui_init which does the initialization
    *  for this applet. It returns the GtkWidget which is then returned
    *  to Home. The function content could be implemented here too, but for
    *  maintainability reasons it has been moved to different function in different
    *  source file.
    */ 
    (*widget) = metar_ui_init (osso);
    
    g_debug ("Initialized maemo metar applet");
    /* Libosso context is returned to Home */
    return (void*)osso;
}

Related functions in the accompanying metar-ui.c in this example applet are as follows:

/* Test button */
static void test_button_clicked (GtkButton* button, gpointer data)
{
    printf("Hello world. You clicked the test button!\n");
}

/* Create the widget for the applet that is returned to the caller (maemo desktop) */
static void 
metar_ui_create_home_applet (void)
{

    /* Create the main widget of the applet */
    app->frame1 = gtk_frame_new(NULL);

    gtk_widget_set_size_request ( GTK_WIDGET (app->frame1), APPLET_X_SIZE, APPLET_Y_SIZE );

    /* Create one vbox */
    app->vbox0  = gtk_vbox_new (TRUE, 2);

    /* Hello world label */
    app->label0 =  gtk_label_new ("Hello world");

    /* Test button */
    app->button0 = gtk_button_new_with_label (("Test button"));
      gtk_signal_connect (GTK_OBJECT (app->button0), "clicked",
              GTK_SIGNAL_FUNC (test_button_clicked), (gpointer) 1);

    /* Set the main widget frame1 to contain vbox0 */
    gtk_container_add (GTK_CONTAINER (app->frame1), app->vbox0);
    
    /* Add first the label */
    gtk_box_pack_start(GTK_BOX(app->vbox0), app->label0, FALSE, FALSE, 0);
    /* The button comes below the label */
    gtk_box_pack_start(GTK_BOX(app->vbox0), app->button0, FALSE, FALSE, 0);
    /* Hack needed to get the applet themed correctly. This
    string can be found from gtkrc.Hildon Desktop -file. Please look
    at it for more information. With string osso-rss-feed-reader it is possible
    to achieve propably best results for a simple applet */

    /* NOTE: FIXME: The skin is drawn inside the applet area so you can't use 
    the border area to draw widgets on it, otherwise your widgets will appear on
    top of the skin graphics like in this quick'n'dirty example */
    gtk_widget_set_name (GTK_WIDGET (app->frame1), "osso-rss-feed-reader");
    /* Finally show the thing */
    
    gtk_widget_show_all (GTK_WIDGET (app->frame1));
}


/* Init UI */

GtkWidget*
metar_ui_init (osso_context_t* osso) 
{
    if (!app) {
        /* Create the struct for storing the globals nicely inside a namespace */
        app = g_new0 (MetarApp, 1);
        app->osso = osso;

        /* The UI initialization could be inside this function, but I
           did choose to put it to another function */
        metar_ui_create_home_applet ();

    }
    /* IMPORTANT! This returns the main widget of the applet (frame1) to maemo desktop */
    return app->frame1;
}

hildon_home_applet_lib_deinitialize:

     
/**
* @hildon_home_applet_lib_deinitialize
*
* @param applet_data Applet data as returned by applet_initialize.
*
* Called when Home unloads the applet from memory.
* Applet should deallocate all the resources needed.
*   
*/
void hildon_home_applet_lib_deinitialize(void *applet_data);

The following is an example implementation:

void 
hildon_home_applet_lib_deinitialize (void *applet_data)
{
    osso_context_t *osso = (osso_context_t*)applet_data;

    /* Call private function which deinitializes your UI. Not necessary required to be
       separate, but for maintainability reasons it may be cleaner to place it to different file
       than this API implementation */
    metar_ui_quit ();
    /* Deinitialize libosso */
    osso_deinitialize (osso);
}

Accompanying functions in this example in metar-ui.c are:

void
metar_ui_quit (void) {
    /* This is for freeing the stuff allocated earlier */
    if (app) {
        g_free (app);
        app = NULL;
    }
}

hildon_home_applet_lib_background:

This function is called when your applet is backgrounded. In other words, the clock applet, for example, does not need to update its display when it is not shown. Home calls hildon_home_applet_lib_foreground again when it is necessary to continue updating the display of the applet again periodically.

     
/**
* @hildon_home_applet_lib_background
*   
* @param applet_data Applet data as returned by applet_initialize.
*
* Called when Home goes to background. 
* Applet should stop all timers when this is called.
*/
void hildon_home_applet_lib_background(void *applet_data);

You do not need to implement this for a simple applet, so it can be left empty as follows:

     
void
hildon_home_applet_lib_background (void *applet_data)
{
    return;
}

hildon_home_applet_lib_foreground:

This function can be used to start, for example, timers in the applet if it has been backgrounded and is then brought up. For example, a clock applet that updates itself periodically does not need updates when it is on background - to do so in an embedded system wastes resources and the battery. Restart your periodic updates when Home calls this function.

The prototype of hildon_home_applet_lib_foreground is:

 
/**
* @hildon_home_applet_lib_foreground
*
* @param applet_data Applet data as returned by applet_initialize.
*
* Called when Home goes to foreground. 
* Applet should start periodic UI updates again if needed.
*/
void hildon_home_applet_lib_foreground(void *applet_data);

For a simple applet this can be also left empty:

  
void 
hildon_home_applet_lib_foreground (void *applet_data)
{
    return;
}

hildon_home_applet_lib_save_state

Prototype of hildon_home_applet_lib_save_state

  

/**
* @hildon_home_applet_lib_save_state
*
* @param applet_data Applet data as returned by applet_initialize.
*   
* @param state_data Applet allocates memory for state data and
*                   stores pointer here. 
*                   Must be freed by the calling application
*                   
* @param state_size Applet stores the size of the state data
*                   allocated here.  The size is measured in
*                   sizeof(void) units.
*
* @returns 0 if successful.
*
* Method called to save the UI state of the applet 
*/

int 
hildon_home_applet_lib_save_state (void *applet_data,
void **state_data, 
int *state_size);

If you do not need the saving state, leave it empty with the following implementation (just set the state data to NULL and state size 0).

     
int 
hildon_home_applet_lib_save_state (void *applet_data,
void **state_data, 
int *state_size)
{
    /* we don't want to save state in a simple weather applet */
    
    (*state_data) = NULL;
    
    if (state_size) {
        (*state_size) = 0; 
    } else {
        g_debug ("State_size pointer was not pointing to right place");
    }
    
    return 1;
}

If your applet needs the saving state, just fill up the **state_data with your data. Home stores the data and initialises the applet next time with the saved data in hildon_home_applet_lib_initialize. Assign state data size in *state_size return value. The return value (int) is an error code. Return 0 if successful.

hildon_home_applet_lib_settings:

The following is the prototype of hildon_home_applet_lib_settings

  

/**
* @hildon_home_applet_lib_settings
*
* @param applet_data Applet data as returned by applet_initialize.
* @param parent The toplevel applet GTK window
*
* Called when the applet needs to open a settings dialog
* This allows applet to provide Home e.g. a menu item widget
* that is connected to an action specified by the applet
*/
GtkWidget *hildon_home_applet_lib_settings(void *applet_data,
GtkWindow *parent);

An example implementation for a simple applet (can be left empty):

     
GtkWidget*
hildon_home_applet_lib_settings (void *applet_data,
GtkWindow *parent)
{
    return NULL;
}

Writing status bar plugins

The maemo status bar can contain user-defined items. Plugin places etc. properties can be set with the Task Navigator and Status bar configuration applet found from Control Panel ("Navigation applet").

To make your own status bar plugin, you need to make a shared object with certain functions in it.

NOTE: maemo desktop was changed to dynamically link to the libhildonstatusbar.so library as statically linking it made it impossible to use the statusbaritem API from the plugins (the type system was confused by having two instances of the get_type() function in memory).

Status Bar plugins are divided into three categories, namely: permanent, conditional and temporal. Permanent plugins are shown all the time. Conditional and temporal plugins are shown only when the condition is fullfilled.

Desktop file

Each plugin have to offer a desktop-file:

  [Desktop Entry]
  Name=<logical name identifier>
  Icon=<logical icon identifier>
  Category=<permanent/conditional/temporal>, 
            the default value is permanent if the key value read fails.
  X-status-bar-plugin=lib<plugin name>.so
  Mandatory=<true/false>,
            if not set, the default value is false

The .desktop file shall be placed in ${datadir}/applications/hildon-status-bar/

Status bar functions

The most important function is the entry function, which is called when the library is opened. This function defines the other functions needed for the plugin operation. The entry function's name depends on the name of your library. It is [your lib name]_entry, for example for a plugin called hello, the entry function is called hello_entry. The following example illustrates the entry function:

/* Definition of HildonStatusBarPluginFn_st */
#include <hildon-status-bar-lib/hildon-status-bar-item.h>
#include <gtk/gtk.h>

void *hello_initialize(HildonStatusBarItem *item, GtkWidget **button);
void hello_destroy(void *data);
void hello_update(void *data, gint value1, gint value2, const gchar *str);
int hello_get_priority(void *data);

/* You have to also include prototype of your entry function */
void hello_entry(HildonStatusBarPluginFn_st *fn);

void hello_entry(HildonStatusBarPluginFn_st *fn)
{
  /* Sanity check */
  if (fn == NULL) {
    return;
  }
  fn->initialize = hello_initialize;
  fn->destroy = hello_destroy;
  fn->update = hello_update;
  fn->get_priority = hello_get_priority;
}

The entry tells the status bar which functions are called at certain events. On the first event, the initialize function is called. The return value of initialize is passed to destroy, update and get_priority functions.

To make it possible to destroy an item, the first parameter of initialize must be stored for later use. The second parameter must be initialised to a new button that is the graphical representation of the plugin. The size of the plugin button should be 40x40 pixels. If any g_timeouts or such are used, the IDs of these must be stored for removal (if not removed, the entire status bar probably crashes). The following example illustrates the initialize function:

struct HelloPlugin {
  HildonStatusBarItem *item;
  GtkWidget *button;
  /* Add here any data for your plugin. */
};

void *hello_initialize(HildonStatusBarItem *item, GtkWidget **button)
{
  GtkWidget *image = NULL;
  struct HelloPlugin *hello = g_new0(struct HelloPlugin, 1);

  hello->item = item;
  *button = hello->button = gtk_button_new();

  image = gtk_image_new_from_file("hello.png"); /* Should be 40x40 */

  gtk_container_add(
    GTK_CONTAINER(*button),
    GTK_WIDGET(image));

  /* Here could add some gconf_notify's, g_timeouts or such. */

  gtk_widget_show_all(*button);

  return (void *)hello;
}

The plugin is now initialized. The following example illustrates the update function, which is called whenever the plugin receives events. The plugin interface allows three parameters to be given to the plugin: two integers and one string. The plugin can use the parameters as it wants. In the example, the first integer value is used to define whether to show the plugin.

When the item is destroyed, the destroy function is called. It should free all the memory and release all the notifys that can lead to the plugin code. The widget must not be destroyed here, the status bar takes care of that. The following example illustrates the destroy function:

void hello_destroy(void *data)
{
  if (!data) {
    return;
  }

  /* You should do g_source_removes and gconf_client_notify_removes here. */

  g_free(data);
}

void [plugin_name]_set_conditional( void *data, gboolean conditional_status);

this function purposes is to provide an interface to conditional and temporal plugins for setting a plugin. Boolean value describes if a plugin is shown(TRUE) and respectively, is not shown(FALSE). Function is an optional e.g. permanent plugins do not need to implement this.

Example:

/* Called by the desktop process whenever the
 * hildon_status_bar_update_conditional is emitted on the
 * SB item 
 */
static void
hello_set_conditional (HelloSb *data, gboolean visible)
{
  if (!data)
    return;

  if (visible)
    {
      if (!data->button)
        {
          data->button = gtk_button_new_with_label ("H");
          g_signal_connect_swapped (data->button, "clicked",
                                    G_CALLBACK (hello_clicked),
                                    data);

          gtk_widget_show_all (data->button);

        }
    }

  else
    {
      gtk_widget_destroy (data->button);
      data->button = NULL;
    }

  hildon_status_bar_item_set_button (data->item, data->button);

}
  

The API provides now, also a new signal for notifying StatusBar? a conditional plugin status changes:

  void (*hildon_status_bar_update_conditional)
            (HildonStatusBarItem * self, gboolean conditional_status);

in which, the boolean parameter's purposes is to notify Status Bar whether a plugin need to be shown or then need to be hide.

Conditional plugins do not need to create the button in the initialize API call, but can leave the button parameter as NULL. To specify the button that will be inserted in the Status Bar, the plugin will need to call

   void hildon_status_bar_item_set_button (HildonStatusBarItem *item, GtkWidget *button);

in the set_conditional() implementation of the plugin.

Changing the conditional status

If a plugin is permanent e.g is shown all the time, it doesn't need to implement set_conditional function. But, otherwise if it is a conditional or a temporal one, it implements set_conditional function. At StatusBar? initialization the default is that those plugins are not shown.

When a plugin needs to change its status it emits "hildon_status_bar_update_conditional" signal:

 gboolean is_shown = FALSE;
   g_signal_emit_by_name(item,
                         "hildon_status_bar_update_conditional",
                         !is_shown);

After emitting the signal, StatusBarItem itself sets conditional function and StatusBar shows plugin at the position where a user has configured it.

Destroying the plugin

There is now need for more frequent reloading and as well removing of plugins. Hence, because at least some existing permanent plugins haven't included yet in their _destroy function dbus deinitialization (osso_deinitialize), not later than now all plugins should include it.

There exists the possibility of avoiding reloading of plugins by setting the plugin as mandatory in the .desktop file, so that it is never unloaded under any circumstances. If so, the plugin can not be unselected in control panel navigator applet.

Statusbar plugins .desktop files

Building the shared object

The shared object can be built like any normal binary, by only giving the "-shared" flag to gcc:

        
[sbox-SDK_PC: ~] > gcc -shared `pkg-config gtk+-2.0 libosso --libs --cflags` hello.c -o libhello.so

The binary produced by gcc must be installed to the path specified in the "hildon-status-bar-lib pkg-config" entry. This path can be obtained with pkg-config hildon-status-bar-lib --variable=pluginlibdir. By default, it is /usr/lib/hildon-status-bar.

Writing Hildon Control Panel plugins

Hildon Control Panel follows the same approach with its plugins than the other components in the maemo desktop environment. Control panel is a standardized place for put settings changeable by the end users for applications, servers etc. in the system.

The Control Panel is divided to categories: General, Connectivity, Personalisation, Extras.

.desktop file for Control Panel plugins

Each Control Panel applet has to provide a .desktop file of the following format:

  [Desktop Entry]
  Name=<logical applet name identifier>
  Comment=Task Navigator Control Panel Applet
  Type=HildonControlPanelPlugin
  Icon=<logical icon name identifier>
  Categories=<general/connectivity/personalisation/extras>, from which 
              the extras is the default value, in case the key value 
              read fails. 
  X-control-panel-plugin=lib<applet name>.so

Execute Hildon Control Panel calls this function to run the plugin.

Prototype

osso_return_t execute(osso_context_t * osso, gpointer data);

Parameter explanations

*osso - The osso context of the application that executes the plugin.

data - The GTK toplevel widget. It is needed so that the widgets created by the plugin can be made a child of the main application that utilizes the plugin. Type is gpointer so that the plugin does not need to depend on GTK (in which case it should ignore the parameter).

Save_state - This method is called to tell the plugin to do state saving. Called by the host application, e.g. Hildon Control Panel.

Prototype:

osso_return_t save_state(osso_context_t *osso, osso_state_t *state);

Parameters:

osso Osso context of the application running the plugin.

state_data The save_state should allocate memory for state save data here. This must be freed by the calling function.

How to create a makefiles and package for your applet

You now have enough information to create a simple Home applet. It contains just an eventbox with savable state. In the build process of applets, autotools are used. autogen.sh, configure.ac and Makefile.am are created in addition to the actual applet source code.

The directory structure of the package is:

/metar_applet
/metar_applet/debian
/metar_applet/data
/metar_applet/src

Firstly, the autogen.sh script (this script does not contain anything applet-specific details.):

#!/bin/sh
set -x
libtoolize --automake
aclocal-1.7 || aclocal
autoconf
autoheader
automake-1.7 --add-missing --foreign || automake --add-missing --foreign

The configure.ac file is as follows:

AC_PREREQ(2.59)
AC_INIT(maemo-metar-applet, 0.1, karoliina.t.salminen@nokia.com)

AM_INIT_AUTOMAKE
AM_CONFIG_HEADER(config.h)

dnl ##################
dnl This script generates host names
dnl ##################
AC_CANONICAL_HOST

dnl ##################
dnl Check for installed programs that is needed
dnl ##################
AC_PROG_CC
AM_PROG_CC_STDC
AC_PROG_INSTALL
AC_PROG_RANLIB
AC_PROG_INTLTOOL([0.21])
AC_PROG_LIBTOOL

AM_PATH_GLIB_2_0

AC_CHECK_PROG(HAVE_PKG_CONFIG, pkg-config, yes, no)
if test "x$HAVE_PKG_CONFIG" = "xno"; then
AC_MSG_ERROR([You need to install pkg-config tool])
fi

dnl ##################
dnl Compiler flags, note that the -ansi flag is not used because
dnl in that case the rint fuction is not awailable in math.h
dnl ##################

CFLAGS="$CFLAGS -g -Wall -Werror -ansi -Wmissing-prototypes -Wmissing-declarations"

dnl ##################
dnl Check needed headers, like C standard headers, GLib, GStreamer etc.
dnl ##################
AC_HEADER_STDC

GLIB_REQUIRED=2.6.0
GTK_REQUIRED=2.4.0
LIBOSSO_REQUIRED=0.8.3
HILDON_LGPL_REQUIRED=0.9.14
HILDON_LIBS_REQUIRED=0.9.15
HILDON_HOME=2.2
GMODULE=2.6
LIBXML=2.6
GNOMEVFS_REQUIRED=2.8.3

PKG_CHECK_MODULES(METAR, [
glib-2.0 >= $GLIB_REQUIRED,
gtk+-2.0 >= $GTK_REQUIRED,
libosso >= $LIBOSSO_REQUIRED,
hildon-lgpl >= $HILDON_LGPL_REQUIRED,
hildon-libs >= $HILDON_LIBS_REQUIRED,
hildon-home >= $HILDON_HOME,
gmodule-2.0 >= $GMODULE,
libxml-2.0 >= $LIBXML,
gnome-vfs-2.0 >= $GNOMEVFS_REQUIRED
])

PKG_CHECK_MODULES(GNOME_VFS, gnome-vfs-2.0 >= 2.8.3)
AC_SUBST(GNOME_VFS_CFLAGS)
AC_SUBST(GNOME_VFS_LIBS)

dnl ##################
dnl directories
dnl ##################

localedir=`pkg-config osso-af-settings --variable=localedir`
AC_SUBST(localedir)

metarpluginlibdir="/usr/lib/metar-applet-home"

AC_SUBST(metarpluginlibdir)

AC_DEFINE_UNQUOTED(METAR_PLUGIN_DIR, "${metarpluginlibdir}",[Dir where plugins are stored])

pluginlibdir="/usr/lib/hildon-home"
AC_SUBST(pluginlibdir)

AC_CONFIG_FILES([Makefile
src/Makefile
data/Makefile
])

AC_OUTPUT

The package config file is used to provide information about the installation directory of the compiled plugin by querying the value of pluginlibdir variable. All version numbers for various components above should be interpreted as illustrative only.

The master Makefile.am is:

SUBDIRS = src data

EXTRA_DIST= \
autogen.sh \
intltool-extract.in \
intltool-merge.in \
intltool-update.in \
debian/rules \
debian/control \
debian/copyright \
debian/changelog \
debian/maemo-metar-applet.install 

deb_dir = $(top_builddir)/debian-build

INCLUDES = $(DEPS_CFLAGS)

deb:    dist
-mkdir $(deb_dir)
cd $(deb_dir)    &&   tar xzf ../$(top_builddir)/$(PACKAGE)-$(VERSION).tar.gz
cd $(deb_dir)/$(PACKAGE)-$(VERSION)   &&   dpkg-buildpackage -rfakeroot
-rm -rf $(deb_dir)/$(PACKAGE)-$(VERSION)

Makefile.am of the src subdirectory:

pluginlib_LTLIBRARIES = libmetar_applet.la

common_CFLAGS = \
$(METAR_CFLAGS) \
-DPREFIX=\"$(prefix)\" \
-DLOCALEDIR=\"$(localedir)\"

common_LDADD = \
$(METAR_LIBS) 

libmetar_applet_la_LDFLAGS= -module -avoid-version

libmetar_applet_la_CFLAGS = $(common_CFLAGS)

libmetar_applet_la_LIBADD = $(common_LDADD)

libmetar_applet_la_SOURCES = \
metar-ui.h \
metar-ui.c \
metar-loader.h \
metar-loader.c \
metar-applet.c

Makefile.am of data directory:

homeapplet_desktopdir=/usr/share/applications/hildon-home
homeapplet_desktop_DATA=metar-applet.desktop

EXTRA_DIST=$(homeapplet_desktop_DATA)

The debian/control is as follows:

Source: maemo-metar-applet
Section: misc
Priority: optional
Maintainer: Karoliina Salminen <karoliina.t.salminen@nokia.com>
Build-Depends: libgtk2.0-dev (>=2.4.0-1), pkg-config, libosso-dev, hildon-lgpl-dev, hildon-libs-dev, intltool (>= 0.21), hildon-home-dev (>=2.2.1), libxml2-dev (>=2.6), libosso-gnomevfs2-dev
Standards-Version: 3.6.1

Package: maemo-metar-applet
S ection: user/internet
Architecture: any
Depends: ${shlibs:Depends},${launcher:Depends}
Description: Maemo Metar home panel applet 
Home panel applet for showing aviation weather.    

The debian/changelog is:

maemo-metar-applet (0.1) experimental; urgency=low

* Created package

-- Karoliina Salminen <karoliina.t.salminen@nokia.com>  Tue, 30 May 2006 10:04:45 +0200

The debian/rules are:

#!/usr/bin/make -f

# export DH_VERBOSE=1

# These are used for cross-compiling and for saving the configure script
# from having to guess our platform (since we know it already)
DEB_HOST_GNU_TYPE   ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE)
DEB_BUILD_GNU_TYPE  ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE)

CFLAGS = -Wall -g
PACKAGENAME = maemo-isearch-applet

ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
CFLAGS += -O0
else
CFLAGS += -O2
endif
ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS)))
INSTALL_PROGRAM += -s
endif

config.status: 
dh_testdir
# Add here commands to configure the package.
CFLAGS="$(CFLAGS)" ./configure --host=$(DEB_HOST_GNU_TYPE) --build=$(DEB_BUILD_GNU_TYPE) --prefix=/usr --mandir=\$${prefix}/share/man --infodir=\$${prefix}/share/info

build: build-stamp

build-stamp: config.status
dh_testdir

# Add here commands to compile the package.
$(MAKE)

touch build-stamp

clean:
dh_testdir
dh_testroot
rm -f build-stamp configure-stamp

# Add here commands to clean up after the build process.
-$(MAKE) clean

dh_clean 

install: build
dh_testdir
dh_testroot
dh_clean -k 
dh_installdirs

# Add here commands to install the package
$(MAKE) install DESTDIR=$(CURDIR)/debian/tmp

# Build architecture-independent files here.
binary-indep: build install
# We have nothing to do by default.

# Build architecture-dependent files here.
binary-arch: build install
dh_testdir
dh_testroot
#   dh_installchangelogs 
dh_installdocs
#   dh_installexamples
dh_install -v --sourcedir=debian/build
#   dh_installmenu
#   dh_installdebconf   
#   dh_installlogrotate
#   dh_installemacsen
#   dh_installpam
#   dh_installmime
#   dh_installinit
#   dh_installcron
#   dh_installinfo
#   dh_installman
dh_link
dh_strip
dh_compress
dh_fixperms
#   dh_perl
#   dh_python
dh_makeshlibs
dh_installdeb
dh_shlibdeps -V
dh_gencontrol
dh_md5sums
dh_builddeb

binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install configure

The debian/maemo-metar-applet.install is:

usr/lib/metar-applet-home/*.so
usr/lib/hildon-home/*.so

data/metar-applet.desktop:

 
[Desktop Entry]
Name=metar-applet
Comment=Aviation Weather Home Applet
Type=HildonHomeApplet
X-home-applet=libmetar_applet.so

Code examples

The following tar.gz packages contain C source code examples that demonstrate how to use the maemo desktop Plugins. Enjoy.

NOTE: If you have problems running the ./configure-script, try running ./autogen.sh first



Improve this page