Developing applets that run on the Moblin platform is easy if you are familiar with Linux, GTK+ development, and you have already created a target device’s build-environment with Moblin Image Creator.
The Control Panel and Its Applets
The Moblin Control Panel is a part of the desktop reserved for configuration utilities. It can be accessed by selecting "Settings" from the marquee menu and it displays a collection of shortcut icons, just like the other areas of the desktop. The difference is that the control panel launches applets instead of applications. This document describes how to build a control panel applet and install it for use in the control panel.
A control panel applet is comprised of a dynamic library, whose "execute" function is called as the entry point. It is loaded by the desktop environment and runs asynchronously in the same execution space. This is in contrast to applications, which get called on their own, with the main function as their entry point.
Get Started
This document describes how to create control panel applets in Moblin. To create a control panel applet for the Moblin platform, there are a few things that you must do:
- Create a GTK dialog which handles the functionality you want
- Enable this dialog to be created/handled by an execute function
- Build your code into a library and install it on the target
- Create a .desktop file which points to your library and an icon file
- Install the .desktop on the target to show the applet's icon in the Settings Area of the home screen
Before continuing, it is strongly suggested that readers be at least basically familiar with the following:
- Running the Moblin platform - Understanding Hildon
- Using Moblin Image Creator
- The Hildon Plugin Standard - http://maemo.org/development/documentation/how-tos/3-x/tutorial_desktop_plugins_bora.html
- GTK - http://library.gnome.org/devel/gtk-tutorial/stable
The only thing essential to building applets is a Linux system with administrative access, preferably Ubuntu*. The standard libraries need to be installed, such as binutils and coreutils, so that you have access to gcc, make, ld, and all the source libraries. This is most easily accomplished by working within an Image Creator project. The moblin-image-creator tool creates a project environment which is intended to be used for development. The idea is that a developer can chroot to this environment and build Moblin code so that they don't have to clutter up their workstation with packages they wouldn't otherwise need. The moblin target environments are organized to be easily accessible from the project's chroot by placing them in its /targets folder.
Once you have created a target, you should make sure you have the right libraries and tools installed so that you can easily develop applets. First, make sure to add the developers-tools
functional set (fset) to your project with Image Creator. You should also have libosso-dev package installed. For more information about necessary libraries, and adding fsets, click
here.
Now you can begin developing control panel applets that use a variant of the Hildon Control Panel Applet Framework.
This guide will start by creating a simple applet which displays a “Hello World” message as a basic dialog. Then we will continue by explaining some more advanced techniques for sizing dialogs for small screens, using Moko finger scrollers for easier dialog access, and using gconf to store and retrieve configuration data.
Start a terminal and enter the chroot for your project with this command (we'll call your project "myproject"):
Install the osso and gtk packages that are needed to create the applet library:
Create a directory where you will be developing. Let's call this path "expath":
# cd /expath
Create a file called example.c and add the following code:
#include <gtk/gtk.h>
#include <hildon-cp-plugin/hildon-cp-plugin-interface.h>
static int dialog_open = 0;
static gboolean blocking = FALSE;
static void
dialog_response (GtkWidget *widget, gint response_id)
{
switch (response_id) {
case GTK_RESPONSE_CLOSE:
case GTK_RESPONSE_CANCEL:
case GTK_RESPONSE_DELETE_EVENT:
gtk_widget_destroy(widget);
dialog_open = 0;
if(blocking) gtk_main_quit();
break;
}
}
osso_return_t
execute(osso_context_t* osso, gpointer data, gboolean desktop_activated)
{
GtkWidget *dialog, *textview;
if(dialog_open) return OSSO_OK;
dialog_open = 1;
blocking = !desktop_activated;
dialog=gtk_dialog_new_with_buttons("Example Applet",
NULL,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CLOSE,
GTK_RESPONSE_CLOSE,
NULL);
textview = gtk_label_new(“Hello World”);
gtk_widget_show_all(textview);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
textview, TRUE, TRUE, 0);
g_signal_connect (GTK_WIDGET(dialog), "response",
G_CALLBACK (dialog_response), NULL);
gtk_widget_show(GTK_WIDGET(dialog));
if(blocking) gtk_main();
dialog_open = 0;
return OSSO_OK;
}
A Minimal "execute" Function
Unlike the "main" function of regular applications, the point of entry to our applet is the "execute" function:execute(osso_context_t* osso, gpointer data, gboolean desktop_activated)
{
The control panel API for Moblin, including the execute function, was inherited from the Hildon Control Panel API. There is a minimum handling of the arguments one must do to support a control panel applet, which is described below. If you need any more in-depth information, refer to the Maemo* tutorial, http://maemo.org/development/documentation/how-tos/3-x/tutorial_desktop_plugins_bora.html.
The execute function is passed three arguments:| (osso_context_t*)osso | This argument is the OSSO context of the calling application. It can be ignored in most cases, but if your applet supports the use of OSSO context information, it is contained in the members of this data structure. Note: In a purely hildon environment, this information would be retrieved from the controlpanel process, but in Moblin it comes from the desktop. Hildon-desktop loads in the UI environment, mobile-basic-flash, which in turn loads all of the control panel applets directly. Therefore the OSSO context will be inherited from hildon-desktop. |
| (gpointer)data | This argument is the desktop's GTK widget, which can be used as the parent to any GTK-based dialogs created by the applet. However, if you are creating an advanced GUI that is highly customized for the desktop, you can use it to gain access to the caller's GTK infrastructure.
It can also be ignored in most cases. |
| (gboolean)desktop_activated | This argument is a flag which tells the applet whether or not it was called by the desktop. During typical operation, moblin control panel applets should only be instantiated by the desktop, which will always set this argument to TRUE. For testing purposes, the moblin-applets package installs a command line utility which can spawn control panel applets independently. It's located at /usr/bin/moblin-applets. When this utility calls the execute function, it sets the desktop_activated flag to FALSE. This argument should always be used. |
We use two global variables to control the execute function's calling behavior:
Preventing Multiple Execute Calls
|
|
| dialog_open | This variable is used to track whether or not the dialog is already being displayed. It is possible for the execute function to be called multiple times and this variable can be used to make sure that we only have one instance running. |
Blocking vs Non-Blocking Execute Behavior
|
|
| blocking | This variable is used to determine whether the execute function should return immediately (non-blocking) or wait until the dialog has been closed by the user (blocking). When running inside the desktop (desktop_activated is true), we don't want to block because we don’t want to interfere with the desktop's own message handling. However, if we were called by a separate process (desktop_activated is false), we need to block because the caller doesn't know when we're finished. |
Creating and Managing a Basic Dialog
This subsection describes how to create a minimal dialog inside an applet. This is accomplished by assembling the dialog in memory as a collection of GTK Widgets and displaying the final product to the user. For a more in depth tutorial on GTK, go here.
NULL,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CLOSE,
GTK_RESPONSE_CLOSE,
NULL);
Buttons are added to the bottom of the dialog (a.k.a the dialog's "action area") by adding arguments to the gtk_dialog_new call. The GTK_STOCK_CLOSE flag adds a Close button, and the GTK_RESPONSE_CLOSE flag establishes that the default response message will be sent to the dialog's message handler on a buttonpress.
The following are the most common button/response pairs for dialogs, any or all of which can be added to the dialog:| GTK_STOCK_APPLY | GTK_RESPONSE_APPLY |
| GTK_STOCK_CANCEL | GTK_RESPONSE_CANCEL |
| GTK_STOCK_CLOSE | GTK_RESPONSE_CLOSE |
| GTK_STOCK_HELP | GTK_RESPONSE_HELP |
| GTK_STOCK_NO | GTK_RESPONSE_NO |
| GTK_STOCK_OK | GTK_RESPONSE_OK |
| GTK_STOCK_YES | GTK_RESPONSE_YES |
There are two ways to call a dialog:
- Synchronously using the gtk_dialog_run function
- Asynchronoulsy using gtk_widget_show and a message handler
We used gtk_widget_show:
Note: Gtk_dialog_run implicitly calls gtk_show_widget to display the dialog, then waits until the dialog is destroyed or one of the action area buttons is pressed, then returns the response. For control panel applets, using gtk_dialog_run is usually bad form, since we need to display the GUI and return as soon as possible, so as not to conflict the desktop's own message handling.
It is a better choice to create the dialog, display it asynchronously, and return immediately, but we need a message handler to handle the destruction of the dialog. We did this by creating a message handler and attaching it to the dialog's "response" signal to redirect messages to our callback function:G_CALLBACK (dialog_response), NULL);
The dialog_response function is an example of a dialog callback. It is registered with the dialog by passing its pointer to g_signal_connect. It gets called whenever one of the buttons in the action area of the dialog is pressed:
dialog_response (GtkWidget *widget, gint response_id)
Now is when we need to destroy all the memory created for the dialog, handle any operations the user requested, and close the GUI.
Even if a dialog doesn't include a Cancel button, its handler can still receive the GTK_RESPONSE_CANCEL message if the window manager has any shortcuts defined for this purpose. For example, in moblin, pressing ESC sends the cancel message to the currently selected window. Therefore, it is important to handle any messages which may destroy the window without your knowledge: CANCEL, CLOSE, and DELETE_EVENT are intended to kill the dialog.
We do that here:case GTK_RESPONSE_CLOSE:
case GTK_RESPONSE_CANCEL:
case GTK_RESPONSE_DELETE_EVENT:
gtk_widget_destroy(widget);
dialog_open = 0;
if(blocking) gtk_main_quit();
break;
}
Differences between Applets and Applications
There are some basic differences between applets and applications. Understanding them can help you decide whether your functionality would best be implemented as an applet or an application.
First, when an applet is called by the desktop, the library stays loaded from the time of instantiation until the desktop is either restarted or shutdown. As a result, any static or global memory you have will be preserved from one instantiation of your applet to the next. It's extremely important that you either keep track of all the memory you're not freeing when your dialog closes, or free everything completely to avoid memory leaks. Also, if your applet enables any function callbacks such as timeouts or Gconf key monitors, they need to be disabled before its GUI is destroyed.
There are many libraries which have initialization functions which need to be called at the start of the process. Applets shouldn't be designed to depend on any libraries that hildon-desktop itself doesn't initialize. For example, gtk_init needs to be called prior to using any GTK functions. GTK is supported and initialized by hildon-desktop, so you can include gtk_init with no worries. On the other hand, the package gstreamer requires that gst_init be called first thing inside the main function but it is not supported by hildon-desktop. Therefore, you should not include it in your applet's functionality. You would have to create a separate executable that your applet library spawns and monitors.
Compiling and Installing the Applet
You can compile the library with the following command:The library is installed on the target in the /usr/lib/hildon-control-panel folder. This can be accessed from the project's chroot as such (we will call you target "mytarget"):
Now, unlike applications, the only way to launch an applet is by double-clicking its icon on the screen. So before we can test our code, we first have to tell the desktop how to display our applet's icon, so that we can launch it when the icon is clicked. This is accomplished in the next section.
Name=Example
Comment=an example of a hildon control panel applet
Type=HildonControlPanelPlugin
Icon=example
X-control-panel-plugin=example.so
You can copy it to the target like this:
The example icon needs to be placed somewhere in the default search path for images. You can take the icon found in the example source, and copy it to /usr/share/icons/gnome/48x48/apps like this:
The desktop reads in the Type key and checks that it's set to HildonControlPanelPlugin. It reads in the X-control-panel-plug-in key to determine the name of the applet library in /usr/lib/hildon-control-panel. It reads in the Icon key to determine what icon image to display as the shortcut in "Settings" (either as a full path or as an icon name to find in the default search path). It then reads in the Name key to determine what text to display beneath the icon in the shortcut. The Comment field isn't currently used but can be useful for other developers.
For more information about desktop entry file, refer to the "Desktop Entry Specification" found at http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html.
Start the Xephyr window:
Figure 3-1: Moblin displaying a hello world app.
You should now see the Example Applet shortcut on the home screen in the Settings area. If you click it, it should pop up a “Hello World” message. Notice that the dialog is sized only as large as is needed to display the text. But what if this screen is only 4 inches across? It might be nice to be able to size our dialogs to fit snugly within some fraction of the screen. This document will show you how to do that.
This section describes how to build the applet library by creating a Makefile. Most packages are designed to use automake, which reads in a simpler Makefile.am file and produces the Makefile for building. But because the format of the automake file is package-dependent, we will focus only on the Makefile in this doc.
The code snippets shown are from the example applet's Makefile and are shown in order, from top to bottom:APPDSKFILE = $(NAME).desktop
APPLIBFILE = $(NAME).so
APPICOFILE = $(NAME).png
APPICODIR = $(DESTDIR)/usr/share/icons/gnome/48x48/apps
APPLIBDIR = $(DESTDIR)/usr/lib/hildon-control-panel
APPDSKDIR = $(DESTDIR)/usr/share/applications/hildon-control-panel
To begin, we define all the variables we will be using later in the file. In this example, we're installing our own custom icon into the gnome icon path, so APPICODIR defines that location. The APPLIBDIR and APPDSKDIR variables define the locations of the applet's library and desktop files respectively.
When running make from within a project, the developer can copy the output files directly to the target by passing, in a destination directory, to make. The example Makefile demonstrates how to support this feature by reading in a DESTDIR variable and prepending it to any paths we will be installing to. It is used as such: "make DESTDIR=/targets/<mytarget>/fs install".$(APPLIBFILE): $(NAME).c
gcc -shared \
`pkg-config --libs --cflags libosso` \
`pkg-config --libs --cflags gtk+-2.0` \
`pkg-config --libs --cflags gconf-2.0` \
`pkg-config --libs --cflags moko` \
-Wl,"-export-dynamic -module" $(NAME).c \
-o $(APPLIBFILE)
The source code needs to be built using the LibOSSO library and header files as this is what hildon-desktop is based on. It can be installed using apt-get install libosso. It's compile and link flags should be passed to gcc using pkg-config, which queries the packages to get the proper values for the current version. The -export-dynamic and -module flags should be used in the link stage so that the applet library doesn't have to follow the lib<name>.so naming convention (that is, the library can just be <name>.so).
mkdir -p $(APPLIBDIR)
mkdir -p $(APPDSKDIR)
mkdir -p $(APPICODIR)
cp ./$(APPLIBFILE) $(APPLIBDIR)
cp ./$(APPDSKFILE) $(APPDSKDIR)
cp ./$(APPICOFILE) $(APPICODIR)
uninstall:
rm -f $(APPLIBDIR)/$(APPLIBFILE)
rm -f $(APPDSKDIR)/$(APPDSKFILE)
rm -f $(APPICODIR)/$(APPICOFILE)
clean:
rm -f ./$(APPLIBFILE)
Lastly, it's always useful to have install, uninstall, and clean functionality. If you're running a virtual GUI inside the target just remember that the GUI will need a restart to pull in any changes to the applet library. This is because hildon-desktop only searches for and loads applet files once at startup.
