Maemo Bora 3.x Tutorial

Introduction

This document is released under GNU Free Documentation license.

This tutorial has been reviewed for maemo 3.x

Overview of the Tutorial

This document is a basic tutorial for the maemo platform and teaches the reader for example about the following subjects:

About maemo

Maemo is an open source development platform for Linux-based handhelds such as Internet Tablets. It is built from widely used open source components with additional tweaks to integrate well in handheld devices.

You can create own maemo GUI applications which can be tested and debugged inside maemo SDK with normal Linux PC. Software development for embedded devices with maemo SDK is just as easy as for desktop PC's.

Overview of the SDK

With maemo SDK, users are able to:

  • Run own and ready-made applications on the maemo platform, on the Linux PC. Maemo SDK is not an emulator, for the most parts the same platform is used in development PC and in target device.
  • Create own maemo GUI applications which can be tested and debugged inside maemo SDK with normal Linux PC. Software development for embedded devices with maemo SDK is just as easy as for desktop PC's.
  • Port your favorite open source Linux project as a maemo application. Existing GTK+ applications are relatively easy to convert, the ones which are using some other toolkit need more UI changes.
  • Build application packages which can be installed to maemo compatible devices. After testing the application in SDK it can be easily packaged as ARMEL version too.

The maemo SDK main components include Platform libraries, such as:

  • Binaries and libraries compiled for target architecture (i386 or ARMEL)
  • D-BUS
  • GnomeVFS
  • GConf

And maemo Application Framework, including for example:

  • Hildon and GTK+
  • libOSSO
  • Task navigator

Quick Start

For those who are in a hurry to learn, following quick steps should provide a good start:

Please note, that you should always read the INSTALL.txt file that is maemo sdk release spesific. If the instructions in the INSTALL.txt file differ from the instructions in this tutorial then follow the instructions in the INSTALL.txt file (this is -for example- the case in maemo 3.1).

  1. Download Scratchbox 1.0.7 and cs2005q3.2-glibc toolchains (http://www.scratchbox.org/download/scratchbox-apophis/). Download SDK rootstrap, too (Downloads).
  2. Install them with the instructions available in here.
  3. Login into Scratchbox and select the i386 SDK rootstrap.
  4. Create file “maemo_hello.c” with the source code below:
    
    #include <hildon-widgets/hildon-program.h>
    #include <gtk/gtkmain.h>
    #include <gtk/gtkbutton.h>
    
    int main(int argc, char *argv[])
    {
        /* Create needed variables */
        HildonProgram *program;
        HildonWindow *window;
        GtkWidget *button;
    
        /* Initialize the GTK. */
        gtk_init(&argc, &argv);
    
        /* Create the hildon program and setup the title */
        program = HILDON_PROGRAM(hildon_program_get_instance());
        g_set_application_name("Hello maemo!");
    
        /* Create HildonWindow and set it to HildonProgram */
        window = HILDON_WINDOW(hildon_window_new());
        hildon_program_add_window(program, window);
    
        /* Create button and add it to main view */
        button = gtk_button_new_with_label("Hello!");
        gtk_container_add(GTK_CONTAINER(window), button);
    
        /* Connect signal to X in the upper corner */
        g_signal_connect(G_OBJECT(window), "delete_event",
          G_CALLBACK(gtk_main_quit), NULL);
    
        /* Begin the main application */
        gtk_widget_show_all(GTK_WIDGET(window));
        gtk_main();
    
        /* Exit */
        return 0;
    }
    	
    
    
  5. Compile the application inside Scratchbox with this command:
    [sbox-SDK_PC: ~] > gcc -o maemo_hello maemo_hello.c `pkg-config --cflags gtk+-2.0 hildon-libs` -ansi -Wall `pkg-config --libs gtk+-2.0 hildon-libs`	
    	
    
  6. Start Xephyr outside Scratchbox:
    
    $ Xephyr :2 -host-cursor -screen 800x480x16 -dpi 96 -ac
    	
    
  7. Start the maemo system:
    
    [sbox-SDK_PC: ~] > af-sb-init.sh start	
    	
    
  8. Run your application with “run-standalone.sh” script to make it get the right environment variables and theme:
    [sbox-SDK_PC: ~] >run-standalone.sh ./maemo_hello	
    	
    
  9. Below is a screenshot of the view you should be seeing now. The application name is shown on the top and the application view is filled with one big button. Clicking the button doesn't do anything as there is no event handling in this application yet. That will be explained in the coming chapters. For now just close the application by pressing “X” in the uppder right corner.

    Graphic2

Interested? You can then proceed with one of these options:

  • If you want to learn what maemo SDK includes, read the whole tutorial starting from next chapter, Overview
  • If you know Linux and GTK+ already and want to start coding directly, see Developing New Application HOWTO about creating a full blown maemo text editor application
  • If you have existing GTK+ application, which you wish to port on maemo platform, proceed to Porting Guide HOWTO about converting an existing open source application for the maemo platform

Overview of the maemo Platform

Software inside maemo

The main components of the maemo platform are presented in the figure below and explained in this chapter. This list in not comprehensive, but it includes the software most important for this tutorial.

Object2

Hildon Application Framework

Hildon application framework introduces a new desktop for handheld devices. It is composed of:

  • Task Navigator for starting programs and switching between applications. Task Navigator has some novel capabilities build into it like its ability to show e-mail headers and browser bookmarks inside the menu.
  • Home provides the idle screen to embed different plugins, e.g news feed reader plugin, clock plugin etc.
  • Status Bar provides a plugin interface used to follow device status changes.
  • Control Panel provides a framework for running applets used to change user settings. These applets are libraries that provide an interface to change/modify or set configurations.
  • Hildon UI provides additional widgets on top of GTK+ ones and also theming modifications which makes the whole GUI to match the maemo style.
  • LibOSSO is an additional library containing required and helpful services for maemo applications to integrate them better with the platform.

GTK+

GTK+ is a multi-platform toolkit for creating graphical user interfaces. Hildon UI is basically modified GTK+ with additional widgets and suitable theming modifications. GTK+ toolkit in maemo is based on GTK+ 2.6.

Modified Hildon GTK+ is more suitable for embedded device, but it's still binary-compatible with normal GTK+. So no changes are needed to make current GTK+ applications run in maemo, although they don't appear as native maemo applications without small changes listed in this document.

For more about Hildon UI see GUI chapter, Hildon UI Style Guide Summary, or the Hildon API documentation.

Matchbox

Matchbox is a base environment for the X Window System running on embedded platforms. It is a lightweight window manager for X11 supporting PDA style windowing. Matchbox in maemo is modified to fit the platform GUI style.

For more information about Matchbox, see: http://projects.o-hand.com/matchbox/

Xserver

Xserver is the part of the platform which handles drawing graphics on the screen. Xserver in maemo is optimized for embedded usage and specific hardware platform.

D-BUS

D-BUS message bus system is used for applications and libraries to deliver messages to each another. D-BUS supplies both system daemon and per-user-login-session daemon (for general IPC needs among user applications). Also, the message bus is built on the top of a general one-to-one message passing framework, which can be used by any two applications to communicate directly.

Maemo platform uses D-BUS for these things:

  • System notifications (e.g. “Battery low”). Every application can receive these events and proceed accordingly.
  • For separating application UI and engine, for example the email engine can be used from different applications.
  • For launching the applications from task navigator. D-BUS is able to launch each application only once and application can receive different messages from the task navigator to e.g. save its state.

D-BUS is mostly used with assistance functions of LibOSSO library, check the LibOSSO chapter or read more about D-BUS from: http://www.freedesktop.org/Software/dbus.

GnomeVFS

GnomeVFS (GNOME Virtual File System) is a library which makes accessing various kinds of file systems transparent to the user and developer. With GnomeVFS various protocols, like HTTP, FTP, and local files, are all used in the exact same way. The API for accessing files is according the POSIX standard with some minor exceptions.

In maemo GnomeVFS is used to access files in the user space, which abstracts file tree structure. Access to external memory is also done over GnomeVFS.

Read more about GnomeVFS from: http://developer.gnome.org/doc/API/gnome-vfs/

GConf

GConf is a system for storing application preferences, widely used in Gnome desktop. GConf provides a preferences database, which is like a simple file system. The file system contains organized hierarchy of keys and values.

All application settings in maemo are stored in Gconf, which makes handling them easy.

Read more about GConf: http://www.gnome.org/projects/gconf/

Main Differences between GNOME and Hildon Application Framework

Hildon application framework is based on GNOME open source platform components. From developer point of view this means certain differences and additions compared to mainstream GNOME development:

  • Maemo does not contain all GNOME libraries. Only such components that match the requirements of a handheld device are included. For example, Bonobo was considered too heavy although it is an essential element of GNOME.
  • Several new widgets are available to help building applications faster and making them behave similarly. Most important new widgets, which each maemo application should use, are HildonProgram and HildonWindow.
  • Menus and toolbars use GTK+ methods with small additions to make them merge better to the platform. See more about these in the GUI chapter
  • Many GTK+ widgets have been slightly modified so that they can be themed for maemo GUI style. GTK+ resource files (RC) are very powerful but still don't include enough properties for the whole maemo UI and layout style. Even with these changes Hildon UI is binary compatible with GTK+ 2.6.
  • Better support for the touchscreen usage with tap-and-hold functionality is implemented in the GtkWidget. In this way it can be used with any widget, usually to open context-sensitive menus.

There are plenty of other small differences, some of which are explained in this tutorial.

Supported Hardware and Device UI Form Factors

Currently maemo is developed for Internet Tablet devices. The minimum constraints for supported hardware and form-factors are following:

  • Screen resolution of 800x480 pixels
  • Includes a pointer device (e.g. touchscreen)
  • ARM or x86 CPU
  • Minimum 32 MB ROM and 64 MB RAM

Additionally maemo supports a set of predefined hardware keys.

Things Not Yet Part of maemo SDK

These things are still missing from maemo SDK and this tutorial:

  • emulating portable media card (MMC) access.

Portable media card (MMC) access emulation is sill missing in the maemo SDK. Some documentation is provided about the mainstream open source software used in maemo platform (D-BUS, GnomeVFS, GTK+). However information about these is best to be learned from their own dedicated websites.

Setting Up and Testing Development Environment

Pre-requisites

When setting up maemo SDK, the hardware and software requirements are as follows:

  • Intel compatible x86 processor, 500 MHz or faster
  • 256 MB RAM or more
  • 2 GB free hard disk space
  • Linux OS (Debian or Ubuntu are recommended, but other fairly recent distributions should also work)

When starting up the installation, these software are needed to be downloaded:

Installation

IMPORTANT ! : Please always refer to the INSTALL.txt file that is delivered with maemo 'bora' release and follow instructions in it. The INSTALL.txt file always contains the most up-to-date information how to install the release. For this reason the installation instructions have been removed from this Tutorial. For maemo 'bora' release you can find the INSTALL.txt file from here.

After you have installed Scratchbox and maemo development environment as explained in the INSTALL.txt file your environment is ready for use and you can start compiling maemo applications! To be able to also see and test them, proceed to the next chapter, which guides you to install a graphical X server for running applications.

Installing Xephyr

To run the maemo environment in a Linux PC you need some graphical window where to start it. For this purpose we recommend Xephyr, but there are also alternative options.

Use the following startup-parameters for Xephyr outside Scratchbox:

      
$ Xephyr :2 -host-cursor -screen 800x480x16 -dpi 96 -ac
			

After running Xephyr, the following empty Xephyr window should open.

Xephyr

Proceed to the next chapter to learn how to launch maemo GUI inside this window!

Launching Application Framework Environment

If you started Xephyr or some other graphical window using display 2, like instructed earlier, you should set DISPLAY variable inside Scratchbox to direct graphical applications to the display 2.

[sbox-SDK_PC: ~] > export DISPLAY=:2
		

For future sessions it's good to set this up automatically. Add the following line to the end of the .bash_profile file inside Scratchbox:

export DISPLAY=:2
		

Now you can finally start maemo GUI in the X window with the command:

[sbox-SDK_PC: ~] > af-sb-init.sh start
		

Following user interface will appear.

Graphic5

Now you are ready to start testing provided applications! If you want to stop the GUI, run:

[sbox-SDK_PC: ~] > af-sb-init.sh stop
		

Using Development Environment

Running Applications

Running applications inside the development environment is easy, and most importantly they are run in the real Linux environment instead of an emulator. For running GUI and applications, i386 SDK must be used as the QEMU emulation is not well enough to run ARMEL binaries (See more about ARMEL/QEMU). When maemo GUI has been started with 'af-sb-init.sh start', you can start to experience the pre-installed applications.

If you just want to see, how your application works on the environment, you can run it with the “run-standalone.sh” script. This will set right environment variables and theme.

	
[sbox-SDK_PC: ~] >run-standalone.sh ./maemo_hello	
	

To build the whole application from source and add a menu entry you should keep on reading the tutorial.

On the left side you see the task navigator area with buttons, of which the bottommost is the task launcher menu. That includes all installed applications, so clicking it and selecting application launches it. Let's start Control panel.

Graphic6

The control panel window opens with icons to a few control panel plugins.

Graphic7

Every maemo application can have an application-specific menu, which is located on the left top corner of the application area.

Graphic8

To switch between applications, use Application switcher on bottom left. You have only Control panel and Home to switch between.

Graphic8_2

Then switch back to Control panel and close it either from the menu or by pressing “X” in the upper right corner.

Building Applications

This section explains how to build and install maemopad application from source files. Applications for the maemo platform are distributed as Application Manager .deb packages. Packaging is described more closely in Creating maemo Packages chapter. The maemopad text editor application, which is built from scratch in the Developing New Application HOWTO, is used as an example:

  1. First get the maemopad source package with command:
    [sbox-SDK_PC: ~] > apt-get source maemopad
    
    This should get you the maemopad source package from the maemo repositories.
  2. You should now have maemopad-1.5 directory under the directory where you were operating. The maemopad-1.5 directory contains the source code for maemopad. In addition you should have the file maemopad_1.5.tar.gz file in your directory. In these examples we just refer to the directory as maemopad ie without the 1.5 version number.
  3. Cd to the maemopad source directory:
    [sbox-SDK_PC: ~] > cd maemopad
    [sbox-SDK_PC: ~/maemopad] >
    		
    
  4. To make Debian package of the application run 'dpkg-buildpackage' in the application directory.
    [sbox-SDK_PC: ~/maemopad] > dpkg-buildpackage -rfakeroot -b
    		
    
    
  5. If everything went well, the parent directory should contain the Debian package.
    [sbox-SDK_PC: ~/maemopad] > cd ..
    [sbox-SDK_PC: ~] > ls
    maemopad
    maemopad_1.5_i386.deb
    		
    
  6. The maemopad package is compatible with Application Manager but it can be installed using debian package manager dpkg, too:
    
    [sbox-SDK_PC: ~] > fakeroot dpkg -i maemopad_1.5_i386.deb
    Selecting previously deselected package maemopad.
    (Reading database ... 20 files and directories currently installed.)
    Unpacking maemopad (from maemopad_1.5_i386.deb) ...
    Setting up maemopad (1.5) ...
    		
    
  7. Now you should see the application in the task launcher menu. Select the application to launch it.
Graphic9

Write some text, test menu options like Copy/Paste, changing font etc. In the case you decide to jump to the full screen mode, it's good to know that “F6” is used to emulate full screen hardware key in maemo so by pressing that you can get back to the normal view.

Graphic10

Debian packaging is a large subject, which is not gone through here with details. For more information about it see “Debian New Maintainers' Guide” at http://www.debian.org/doc/manuals/maint-guide/.

Building for ARMEL

Building ARMEL packages for the maemo devices is just as easy as building packages for i386.

If you have installed maemo bora sdk with the installer script that is provided with the release you already have a working ARMEL target inside your scratchbox. Doing the installation with the maemo-sdk-install script is strongly recommended.

Basically there is two ways to get required ARMEL compilation support in rootstrap:

  • To use QEMU to emulate an ARM processor. QEMU is a generic and open source processor emulator which achieves a good emulation speed by using dynamic translation. For more information about QEMU, see: http://fabrice.bellard.free.fr/qemu/
  • To use a real ARMEL device as cross-compilation device. This is done in Scratchbox with sbrsh, which runs the configure script test programs on a networked device with the same CPU as the cross-compilation target device, in a way that is transparent to the configuration system. Running programs on actual target device is more reliable than emulating specific target device, because QEMU may not support all required features. For more information about using CPU-transparency device see http://scratchbox.org.

QEMU is suitable for normal cross-compilation but an actual target device should be used for more advanced purposes. In this tutorial we use QEMU, though, as it is much easier to setup and no extra hardware is required. QEMU emulation is getting more and more complete all the time, although it can't be used to run applications 100 percent accurately yet.

Below are instructions how to configure the ARMEL target in scratchbox but note that you DO NOT NEED TO SETUP AN ARMEL TARGET IF YOU HAVE INSTALLED YOUR SDK WITH THE PROVIDED MAEMO INSTALLER SCRIPT.

  1. Download ARMEL SDK rootstrap at Downloads
  2. Copy rootstrap to “/scratchbox/packages/” directory:
    # cp <ROOTSTRAP>.tar.gz /scratchbox/packages/
    		
    
    
  3. Startup the scratchbox (if it's not running already).
    # /scratchbox/sbin/sbox_ctl start
    		
    
  4. Then log in as a normal user:
    $ /scratchbox/login
    		
    
    
  5. Create new rootstrap target with either “sb-menu” (interactive) or straight from the command line using the instructions presented here. “sb-conf setup SDK_ARMEL --compiler cs2005q3.2-glibc-arm --devkits=debian:perl:doctools:cputransp --cputransp=/scratchbox/devkits/cputransp/bin/qemu-arm-0.8.1-sb2” creates the target. SDK_ARMEL is the name of your new target. If a target with that name already exists, select another name.
    [sbox-SDK_PC: ~] > sb-conf setup SDK_ARMEL --compiler cs2005q3.2-glibc-arm \
    --devkits=debian:perl:doctools:cputransp \
    --cputransp=/scratchbox/devkits/cputransp/bin/qemu-arm-0.8.1-sb2
    		
    
  6. Select the newly created ARMEL target:
    
    [sbox-SDK_PC: ~] > sb-conf select SDK_ARMEL
    Restarting Scratchbox shell...
    Hangup
    Shell restarting...
    [sbox-SDK_ARMEL: ~] >
    		
    

    When you have both PC and ARMEL targets, you can toggle between them with: sb-conf select [target]

  7. Then extract the downloaded rootstrap. This may take few minutes.
    [sbox-SDK_ARMEL: ~] > sb-conf rootstrap /scratchbox/packages/<ROOTSTRAP>.tar.gz
    Unpacking rootstrap...
    [sbox-SDK_ARMEL: ~] >
    
    		
    
  8. Setup devkits, libfakeroot environment and /etc directory with following command:
    [sbox-SDK_ARMEL: ~] > sb-conf install SDK_ARMEL --devkits --fakeroot --etc
    Installing fakeroot version 1.4.2...
    [sbox-SDK_ARMEL: ~] >
    		
    
  9. Do NOT install the ARMEL C-library files from Scratchbox toolchains. Do NOT select the C-library in sb-menu or use the options -c or --clibrary with "sb-conf install" command. If you accidently chose to install armel C-libraries from Scratchbox you can reset the problem by running a command:
    [sbox-SDK_ARMEL: ~] > sb-reinstall /scratchbox/packages/<ROOTSTRAP>.tar.gz
    Resetting SDK_ARMEL...
    Extracting rootstrap...
    Unpacking rootstrap...
    Installing system files...
    Installing fakeroot version 1.4.2...
    [sbox-SDK_ARMEL: ~] >
    		
    
  10. Now you have also an ARMEL SDK installed and you can build Debian packages with it just like earlier with the PC rootstrap. NOTE: Inside Scratchbox ARMEL target (which uses QEMU CPU emulation) everything may not work as in the device.

Writing maemo GUI Applications

Overview of maemo GUI Applications

Hildon is a graphical user interface designed for small mobile devices. This chapter aims to assist developers in getting started with the development of Hildon compatible applications.

Most GTK+ widgets and methods work unaltered in the Hildon environment, but to make applications fit in the maemo GUI environment some source code changes for plain GTK+ applications are required.

Next sections introduce most important widgets in Hildon, all GTK+ widgets can be used in addition of these. For more information see the GTK+ Reference Manual at http://developer.gnome.org/doc/API/2.0/gtk/

Most of the source code snippets in the whole tutorial can also be found in the maemo-examples package that can be downloaded with the apt-get source maemo-examples command inside the scratchbox.

Basic Hildon Layouts

This chapter shows basic layout modes of the Hildon user interface. Applications can use all of these and switch dynamically between different views. In all the views application area can, of course, be shared in any possible ways using GTK+ containers (e.g. GtkHBox, GtkVBox and GtkTable).

Normal View

Figures below describe the basic layout of the Hildon user interface in the normal view. This view consists of Task Navigator area, statusbar/titlebar area, application area and three areas of inactive skin graphics.

  • Task Navigator area on the left is 80 pixels wide
  • Statusbar/titlebar area is 720x60 pixels
  • Application area of 672x396 pixels is in the middle
Graphic16

Normal View with Toolbar

This basic layout contains all types of functional areas. The layout is basically the same as Normal View, but there is a toolbar area at the bottom replacing the inactive skin graphic area. The toolbar area width is 720 pixels and the height varies depending on the toolbar type:

  • 360 pixels with a single toolbar
  • 310 pixels with both application and Find toolbar
Graphic18

Full Screen View

In this view the whole screen (800x480 px) is reserved for the application area. The full screen mode can be activated and deactivated by a hardware button or in the application code. All applications should provide full screen mode to maximize screen usage when needed.

Graphic13

Full Screen View with Toolbar

This is a variation of the full screen view. There is a toolbar area at the bottom of the screen reducing the available space for the application area. S toolbar can be visible in both normal and full screen modes so it should be made scalable.

  • With a single toolbar the application area height is 420 pixels.
  • Multiple toolbars can be used (e.g. Find toolbar) simultaneously. All of them appear at the bottom of screen, on top of each other reducing application area. With two toolbars application area height is 370 pixels.
Graphic14

Windows

HildonProgram

#include <hildon-widgets/hildon-program.h>

			

A HildonProgram is the base of any Hildon application. It's inherited from GObject and represents an application running in the Hildon framework. The HildonProgram tracks the top-most status of an application and informs Task Navigator when the application can be hibernated. It also provides tools to get/set common menus and toolbars, which are common for all application windows.

The HildonProgram widget is created by calling the function hildon_program_get_instance(), which doesn't take any parameters and returns newly created HildonProgram. For this tutorial, the following API functions are of interest:

HildonProgram *hildon_program_get_instance (void)

- Creates new HildonProgram widget.

void hildon_program_add_window (HildonProgram *self, HildonWindow *window)

- Adds HildonWindow to HildonProgram.

hildon_program_set_can_hibernate (HildonProgram *self, gboolean killable)

- Informs Task Navigator that it can hibernate the HildonProgram.

void hildon_program_set_common_menu (HildonProgram *self, GtkMenu *menu)

- Sets common menu for all application windows.

void hildon_program_set_common_toolbar (HildonProgram *self, GtkToolbar *toolbar)

- Sets common toolbar for all application windows.

This is an example (example_hildonprogram.c) application which creates new HildonProgram and HildonWindow. It sets a title for the application and creates a sample label.

/* Includes */
#include <hildon-widgets/hildon-program.h>

#include <gtk/gtkmain.h>

int main(int argc, char *argv[])
{
    /* Create needed variables */
    HildonProgram *program;
    HildonWindow *window;

    /* Initialize the GTK. */
    gtk_init(&argc, &argv);

    /* Create the hildon program and setup the title */
    program = HILDON_PROGRAM(hildon_program_get_instance());
    g_set_application_name("App Title");

    /* Create HildonWindow and set it to HildonProgram */
    window = HILDON_WINDOW(hildon_window_new());
    hildon_program_add_window(program, window);

    /* Add example label to window */
    gtk_container_add(GTK_CONTAINER(window),
                      GTK_WIDGET(gtk_label_new("HildonProgram Example")));

    /* Begin the main application */
    gtk_widget_show_all(GTK_WIDGET(window));

    /* Connect signal to X in the upper corner */
    g_signal_connect(G_OBJECT(window), "delete_event",
      G_CALLBACK(gtk_main_quit), NULL);

    gtk_main();

    /* Exit */
    return 0;
}
			
Graphic11

HildonWindow

#include <hildon-widgets/hildon-window.h>
			

The HildonWindow is inherited from the GtkWindow. It represents a top-level window of an application running in the Hildon framework. It provides facilities to manage window menus and toolbars. GtkWindow API applies to it as well (gtk_window_fullscreen, gtk_window_set_urgency_hint, ...).

Each HildonWindow can have its own menu and toolbars. Application can also have common menu and toolbar, which are shared among all application windows. To get more information check HildonProgram, Menusc and Toolbars chapters.

Following HildonWindow functions are the most important ones:

GtkWidget *hildon_window_new (void)

- Creates a new HildonWindow widget.

void hildon_window_set_menu (HildonWindow *self, GtkMenu *menu)

- Sets a menu for the HildonWindow.

void hildon_window_add_toolbar (HildonWindow *self, GtkToolbar *toolbar)

- Adds a toolbar for the HildonWindow.

See previous chapter for a sample code.

Menus

Menus in maemo use GTK+ menu functions with the exception that the menu is attached to the HildonProgram/Window and appears in the title bar. A GtkMenu can be created and attached to the HildonProgram to be used as a common menu for all HildonWindows that don't have their own menu. Another way is to use a GtkMenu in a HildonWindow. In this way every application window can have different menu.

GTK+ menus can be made both manually and using GtkUIManager. The GtkUIManager is a new action-based method to create menus and toolbars using an XML description. This tutorial introduces only manual creation, you can read more about GtkUIManager at http://developer.gnome.org/doc/API/2.0/gtk/GtkUIManager.html.

The next example (example_menu.c) creates a menu with a submenu. This submenu contains radio menu items and check menu item, all of which can be toggled. The last item in the menu (Close) is attached to a callback fuctions so that the application can be closed also from the menu. Let's start with the function which creates this menu. In this example the menu is attached to the one and only HildonWindow.

/* Create the menu items needed for the main view */
static void create_menu(HildonWindow * main_window) 
{
    /* Create needed variables */
    GtkWidget *main_menu;
    GtkWidget *menu_others;
    GtkWidget *item_others;
    GtkWidget *item_radio1;
    GtkWidget *item_radio2;
    GtkWidget *item_check;
    GtkWidget *item_close;
    GtkWidget *item_separator;

    /* Create new main menu */
    main_menu = gtk_menu_new();

    /* Create new submenu for "Others" */
    menu_others = gtk_menu_new();

    /* Create menu items */
    item_others = gtk_menu_item_new_with_label("Others");
    item_radio1 = gtk_radio_menu_item_new_with_label(NULL, "Radio1");
    item_radio2 =
        gtk_radio_menu_item_new_with_label_from_widget(GTK_RADIO_MENU_ITEM
                                                       (item_radio1),
                                                       "Radio2");
    item_check = gtk_check_menu_item_new_with_label("Check");
    item_close = gtk_menu_item_new_with_label("Close");
    item_separator = gtk_separator_menu_item_new();

    /* Add menu items to right menus */
    gtk_menu_append(main_menu, item_others);
    gtk_menu_append(menu_others, item_radio1);
    gtk_menu_append(menu_others, item_radio2);
    gtk_menu_append(menu_others, item_separator);
    gtk_menu_append(menu_others, item_check);
    gtk_menu_append(main_menu, item_close);

    /* Add others submenu to the "Others" item */
    hildon_window_set_menu(HILDON_WINDOW(main_window), GTK_MENU(main_menu));
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(item_others), menu_others);

    /* Attach the callback functions to the activate signal */
    g_signal_connect(G_OBJECT(item_close), "activate",
                     GTK_SIGNAL_FUNC(item_close_cb), NULL);

    /* Make all menu widgets visible */
    gtk_widget_show_all(GTK_WIDGET(main_menu));
}
			

The menu entry “Close“ is attached with the function “g_signal_connect “ to the callback function “item_close_cb”. This function, which is presented next, ends the application by executing “gtk_main_quit()”.

/* Callback for "Close" menu entry */
void item_close_cb()
{
    g_print("Closing application...\n");
    gtk_main_quit();
}
			

The main function in this application is similar to others presented earlier. The only new thing here is executing the function “create_menu()” HildonWindow as its parameter. This function creates the menu and attach it to the window.

/* Main application */
int main(int argc, char *argv[])
{
    /* Create needed variables */
    HildonProgram *program;
    HildonWindow *window;

    /* Initialize the GTK. */
    gtk_init(&argc, &argv);

    /* Create the hildon program and setup the title */
    program = HILDON_PROGRAM(hildon_program_get_instance());
    g_set_application_name("Menu Example");

    /* Create HildonWindow and set it to HildonProgram */
    window = HILDON_WINDOW(hildon_window_new());
    hildon_program_add_window(program, window);

    /* Add example label to HildonWindow */
    gtk_container_add(GTK_CONTAINER(window),
                      gtk_label_new("Menu Example"));

    /* Create menu for HildonWindow */
    create_menu(window);

    /* Connect signal to X in the upper corner */
    g_signal_connect(G_OBJECT(window), "delete_event",
      G_CALLBACK(item_close_cb), NULL);

    /* Begin the main application */
    gtk_widget_show_all(GTK_WIDGET(window));
    gtk_main();

    /* Exit */
    return 0;
}
			

Screenshot of the application is presented below, the submenu opened.

Graphic20

As the maemo menu are GTK+ menus, they are not explained here in more details. For full API documentation regarding GTK+ menu widgets, see: http://developer.gnome.org/doc/API/2.0/gtk/GtkMenu.html

Toolbars

To create an application that has a toolbar, create a normal HildonWindow and then a normal GtkToolbar. The toolbar is used as any GtkToolbar is used and it can contain normal GtkToolItems, like:

  • GtkToolButton – A normal toolbar button which can be clicked.
  • GtkToggleToolButton – A toolbar button which can be toggled between two different states. An example of this kind of usage is 'Bold' button in text editors, it can be either enabled of disabled.
  • GtkRadioToolButton – A toolbar button which can be toggled between two different states, with the addition that only one of the buttons in a group can be selected at the same time. Example of this kind on usage are tool buttons (line, box, fill) in drawing applications: only one tool can be selected at the same time.
  • GtkSeparatorToolItem – A toolbar item which is used as a separator between real items. It can be visible separation line or just empty space.
  • GtkToolItem – The base class of widgets that can be added to GtkToolbar. By using this as a parent widget, all kind of widgets can be inserted inside a GtkToolbar. An example of this kind of usage can be seen in the following sample code, where the GtkComboBox is attached to a toolbar.

The main difference compared to normal GTK+ usage is that toolbars can be common to all HildonWindows in an application or they can be used only in a particular HildonWindow.

The function below creates a toolbar and adds it to the HildonWindow. In this example (example_toolbar.c) the defined function is called in the main function in the same way as in the previous menu example. The callback function for the 'Close' button (tb_close_cb) is similar to the earlier example, too.


/* Create the toolbar needed for the main view */
static void create_toolbar(HildonWindow * main_window)
{
    /* Create needed variables */
    GtkWidget *main_toolbar;
    GtkToolItem *tb_new;
    GtkToolItem *tb_open;
    GtkToolItem *tb_save;
    GtkToolItem *tb_close;
    GtkToolItem *tb_separator;
    GtkToolItem *tb_comboitem;
    GtkComboBox *tb_combo;

    /* Create toolbar */
    main_toolbar = gtk_toolbar_new();

    /* Create toolbar button items */
    tb_new = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
    tb_open = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN);
    tb_save = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE);
    tb_close = gtk_tool_button_new_from_stock(GTK_STOCK_CLOSE);

    /* Create toolbar combobox item */
    tb_comboitem = gtk_tool_item_new();
    tb_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
    gtk_combo_box_append_text(tb_combo, "Entry 1");
    gtk_combo_box_append_text(tb_combo, "Entry 2");
    gtk_combo_box_append_text(tb_combo, "Entry 3");
    /* Select second item as default */
    gtk_combo_box_set_active(GTK_COMBO_BOX(tb_combo), 1);
    /* Make combobox to use all available toolbar space */
    gtk_tool_item_set_expand(tb_comboitem, TRUE);
    /* Add combobox inside toolitem */
    gtk_container_add(GTK_CONTAINER(tb_comboitem), GTK_WIDGET(tb_combo));

    /* Create separator */
    tb_separator = gtk_separator_tool_item_new();

    /* Add all items to toolbar */
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_new, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_separator, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_open, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_save, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_comboitem, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_close, -1);

    /* Add signal lister to "Close" button */
    g_signal_connect(G_OBJECT(tb_close), "clicked",
                     G_CALLBACK(tb_close_cb), NULL);

    /* Add toolbar HildonWindow */
    hildon_window_add_toolbar(main_window, GTK_TOOLBAR(main_toolbar));
}
			

The screenshot presents the created toolbar with several buttons and combobox.

Graphic21

HildonFindToolbar

#include <hildon-widgets/hildon-program.h>
#include <hildon-widgets/hildon-find-toolbar.h>

#include <gtk/gtk.h>

A HildonFindToolbar is a special toolbar for a find feature. Maemo applications may have several toolbars, which are then attached on top of each other. The find toolbar is generally placed on the top of the main toolbar, as in this example.

Now we go through a bit more complete application (example_findtoolbar.c), for which the Find toolbar needs to be opened and hidden. We create a structure to contain the main UI widget pointers. This is the way a real maemo application should be built to make accessing generated widgets easier. Let's go through the code piece by piece, at first the data structure which holds important data needed to access by other functions.

/* Application UI data struct */
typedef struct _AppData AppData;
struct _AppData {

    /* View items */
    HildonProgram *program;
    HildonWindow *window;

    /* Toolbar */
    GtkWidget *main_toolbar;

    /* Find toolbar */
    HildonFindToolbar *find_toolbar;

    /* Is Find toolbar visible or not */
    gboolean find_visible;
};
			

The next part presents all the callback functions required: “Close” and “Find” for the main toolbar and “Close” and “Search” for the find toolbar. The actual search isn't implemented but it should be included in the find_tb_search() function.


/* Callback for "Close" toolbar button */
void tb_close_cb(GtkToolButton * widget, AppData * view)
{
    g_print("Closing application...\n");
    gtk_main_quit();
}

/* Callback for "Find" toolbar button */
void tb_find_cb(GtkToolButton * widget, AppData * view)
{
    /* Show or hide find toolbar */
    if (view->find_visible) {
        gtk_widget_hide_all(GTK_WIDGET(view->find_toolbar));
        view->find_visible = FALSE;
    } else {
        gtk_widget_show_all(GTK_WIDGET(view->find_toolbar));
        view->find_visible = TRUE;
    }
}

/* Callback for "Close" find toolbar button */
void find_tb_close(GtkWidget * widget, AppData * view)
{
    gtk_widget_hide_all(GTK_WIDGET(view->find_toolbar));
    view->find_visible = FALSE;
}

/* Callback for "Search" find toolbar button */
void find_tb_search(GtkWidget * widget, AppData * view)
{
    gchar *find_text = NULL;
    g_object_get(G_OBJECT(widget), "prefix", &find_text, NULL);
    /* Implement the searching here... */
    g_print("Search for: %s\n", find_text);
}
			

Next we create the HildonFindToolbar in a separate function and connect it to the callback listeners presented before. Notice that there is no gtk_widget_show() function for the toolbar here, as it's wanted to be kept hidden when application starts.

/* Create the find toolbar */
void ui_create_find_toolbar(AppData * view)
{
    HildonFindToolbar *find_toolbar;
    find_toolbar = HILDON_FIND_TOOLBAR
        (hildon_find_toolbar_new("Find String: "));

    /* Add signal listers to "Search" and "Close" buttons */
    g_signal_connect(G_OBJECT(find_toolbar), "search",
                     G_CALLBACK(find_tb_search), view);
    g_signal_connect(G_OBJECT(find_toolbar), "close",
                     G_CALLBACK(find_tb_close), view);
    hildon_window_add_toolbar(view->window, GTK_TOOLBAR(find_toolbar));

    /* Set variables to AppData */
    view->find_toolbar = find_toolbar;
    view->find_visible = FALSE;
}
			

The function create_toolbar() is very similar to the earlier example, only a callback is added for the find toolbar button.

/* Create the toolbar for the main view */
static void create_toolbar(AppData * appdata)
{
    /* Create needed variables */
    GtkWidget *main_toolbar;
    GtkToolItem *tb_new;
    GtkToolItem *tb_open;
    GtkToolItem *tb_save;
    GtkToolItem *tb_find;
    GtkToolItem *tb_close;
    GtkToolItem *tb_separator;
    GtkToolItem *tb_comboitem;
    GtkComboBox *tb_combo;

    /* Create toolbar */
    main_toolbar = gtk_toolbar_new();

    /* Create toolbar button items */
    tb_new = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
    tb_open = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN);
    tb_save = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE);
    tb_find = gtk_tool_button_new_from_stock(GTK_STOCK_FIND);
    tb_close = gtk_tool_button_new_from_stock(GTK_STOCK_CLOSE);

    /* Create toolbar combobox item */
    tb_comboitem = gtk_tool_item_new();
    tb_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
    gtk_combo_box_append_text(tb_combo, "Entry 1");
    gtk_combo_box_append_text(tb_combo, "Entry 2");
    gtk_combo_box_append_text(tb_combo, "Entry 3");
    /* Select second item as default */
    gtk_combo_box_set_active(GTK_COMBO_BOX(tb_combo), 1);
    /* Make combobox to use all available toolbar space */
    gtk_tool_item_set_expand(tb_comboitem, TRUE);
    /* Add combobox inside toolitem */
    gtk_container_add(GTK_CONTAINER(tb_comboitem), GTK_WIDGET(tb_combo));

    /* Create separator */
    tb_separator = gtk_separator_tool_item_new();

    /* Add all items to toolbar */
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_new, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_separator, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_open, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_save, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_comboitem, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_find, -1);
    gtk_toolbar_insert(GTK_TOOLBAR(main_toolbar), tb_close, -1);

    /* Add signal lister to "Close" button */
    g_signal_connect(G_OBJECT(tb_close), "clicked",
                     G_CALLBACK(tb_close_cb), NULL);

    /* Add signal lister to "Find" button */
    g_signal_connect(G_OBJECT(tb_find), "clicked",
                     G_CALLBACK(tb_find_cb), appdata);

    /* Add toolbar HildonWindow */
    hildon_window_add_toolbar(appdata->window, GTK_TOOLBAR(main_toolbar));

    gtk_widget_show_all(main_toolbar);
    appdata->main_toolbar = main_toolbar;
}
			

Then the main application loop, which creates a new AppData structure and stores newly created HildonProgram and HildonWindow into it. Both toolbars are created but only the main toolbar is set visible.

/* Main application */
int main(int argc, char *argv[])
{
    /* Create needed variables */
    HildonProgram *program;
    HildonWindow *window;

    /* Initialize the GTK. */
    gtk_init(&argc, &argv);

    /* Create the hildon program and setup the title */
    program = HILDON_PROGRAM(hildon_program_get_instance());
    g_set_application_name("HildonFindToolbar Example");

    /* Create HildonWindow and set it to HildonProgram */
    window = HILDON_WINDOW(hildon_window_new());
    hildon_program_add_window(program, window);

    /* Add example label to window */
    gtk_container_add(GTK_CONTAINER(window),
                      gtk_label_new("HildonFindToolbar Example"));

    /* Create AppData */
    AppData *appdata;
    appdata = g_new0(AppData, 1);
    appdata->program = program;
    appdata->window = window;

    /* Create toolbar for view */
    create_toolbar(appdata);

    /* Create find toolbar, but keep it hidden */
    ui_create_find_toolbar(appdata);

    /* Connect signal to X in the upper corner */
    g_signal_connect(G_OBJECT(appdata->window), "delete_event",
      G_CALLBACK(item_close_cb), NULL);

    /* Show all other widgets except the find toolbar */
    gtk_widget_show_all(GTK_WIDGET(appdata->window));
    gtk_widget_hide_all(GTK_WIDGET(appdata->find_toolbar));

    /* Begin the main application */
    gtk_main();

    /* Exit */
    return 0;
}
			

Now the application is ready and the screenshot below presents the result.The find toolbar can be opened and closed pressing the 'Find' toolbar button (second from right). As we didn't create any real functionality for the search, pressing “Find” only prints the search string into the command line.

Graphic22

Other Hildon Widgets

This chapter introduces general Hildon widgets, which can and should be used in different maemo applications. Only most common ones are introduced, please see Hildon API documentation for full list of available widgets. Remember that all normal GTK+ widgets can be used, too, see GTK+ Reference Manual at http://developer.gnome.org/doc/API/2.0/gtk/.

HildonFileChooserDialog

	
#include <hildon-widgets/hildon-program.h>
#include <hildon-widgets/hildon-file-chooser-dialog.h>
#include <gtk/gtk.h>

/* Application UI data struct */
typedef struct _AppData AppData;
struct _AppData {
	HildonProgram *program;
	HildonWindow *window;
	
	GtkWidget * main_vbox;
	GtkWidget * label;

	/* Menu stuff */
	GtkWidget * main_menu;
	GtkWidget * menu_item_file_open;
	GtkWidget * menu_item_file_save;
	GtkWidget * menu_item_quit;
};

			

HildonFileChooserDialog is a dialog used to save and open user files. It is based on the GtkFileChooser so that the API is similar to the one used in GTK+.

HildonFileChooserDialog is usually opened by event callback of “Open” or “Save” menu entries or toolbar buttons. To use the file dialog these callbacks should include similar code than below (example_file_chooser.c).

void cb_example_file_open (GtkWidget *w, AppData *data)
{
	gchar *filename = NULL;
	    
	filename = interface_file_chooser (data, GTK_FILE_CHOOSER_ACTION_OPEN);

	if (filename == NULL) {
		filename = "NULL";
	}

	example_show_test_result (data, "Open File", filename);
}

void cb_example_file_save (GtkWidget *w, AppData *data)
{
	gchar *filename = NULL;
	filename = interface_file_chooser (data, GTK_FILE_CHOOSER_ACTION_SAVE);

	if (filename == NULL) {
		filename = "NULL";
	}
	else {
		FILE *f = fopen (filename, "w");
		fprintf (f, "This file was generated by Hildon File Chooser example.");
		fclose (f);
	}

	example_show_test_result (data, "File saved as", filename);	
}
			

Definition of the interface_file_chooser() function:

gchar *interface_file_chooser (AppData * appdata, GtkFileChooserAction action)
{
	GtkWidget *dialog;
	gchar *filename = NULL;
	
	dialog = hildon_file_chooser_dialog_new (GTK_WINDOW (appdata->window), action);
	gtk_widget_show_all (GTK_WIDGET (dialog));

	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
	}

	gtk_widget_destroy(dialog);
	return filename;
}
			

To make compiler and linker find these headers, give additional "pkg-config --cflags hildon-fm" and "pkg-config --libs hildon-fm" parameters for gcc or to Makefile.

Below are screenshots of both Save and Open dialogs.

Graphic26

Graphic25

For more information about HildonFileChooserDialog see Hildon API documentation or Developing New Application HOWTO.

HildonColorSelector

#include <hildon-widgets/hildon-color-selector.h>
#include <hildon-widgets/hildon-color-button.h>

#include <hildon-widgets/hildon-program.h>
#include <gtk/gtk.h>

HildonColorSelector is a dialog for selecting colors. It's quite similar to GtkColorSelectionDialog but more suitable for embedded devices.

There are two different ways to use HildonColorSelector: either by using HildonColorButton which shows the selected color in the button area and launches HildonColorSelector dialog automatically when clicked or by creating the dialog manually.

Let's learn to use HildonColorButton first (example_color_selector.c). It can be created by hildon_color_button_new() and attached to where ever wanted in the UI.

...
color_button = HILDON_COLOR_BUTTON(hildon_color_button_new());
g_signal_connect(G_OBJECT(color_button), "clicked",
    G_CALLBACK(color_button_clicked), NULL);
...
			

In the color_button_clicked callback function the selected color is retrieved from the HildonColorButton using the “color” property of the widget.

void color_button_clicked(GtkWidget * widget, gpointer data)
{
    GdkColor *new_color = NULL;
    g_object_get(widget, "color", &new_color, NULL);
}
			

That's all that is needed! If HildonColorButton isn't needed, HildonColorSelector can be created manually:.

void ui_show_color_selector(GtkWidget * widget, AppData * appdata)
{
    GdkColor *current_color;
    const GdkColor *new_color;
    GtkWidget *selector;
    gint result;

    /* Create new color selector (needs access to HildonWindow!) */
    selector = hildon_color_selector_new(GTK_WINDOW(appdata->window));

    /* Set the current selected color to selector */
    hildon_color_selector_set_color(HILDON_COLOR_SELECTOR(selector),
                                    current_color);

    /* Show dialog */
    result = gtk_dialog_run(GTK_DIALOG(selector));

    /* Wait for user to select OK or Cancel */
    switch (result) {
    case GTK_RESPONSE_OK:

        /* Get the current selected color from selector */
        new_color = hildon_color_selector_get_color
            (HILDON_COLOR_SELECTOR(selector));

        /* Now the new color is in 'new_color' variable */
        /* Use it however suitable for the application */

        gtk_widget_destroy(selector);
        break;

    default:

        /* If dialog didn't return OK then it was canceled */
        gtk_widget_destroy(selector);
        break;
    }

}
			

In HildonColorSelector a predefined color can be selecter, or a new color can be generated. Both are presented in screenshots below.

Graphic23

Graphic24

HildonFontSelectionDialog

	  
#include <hildon-widgets/hildon-program.h>

#include <hildon-widgets/hildon-font-selection-dialog.h>
#include <gtk/gtk.h>

			

Where HildonColorSelector provides method to query color from the user, HildonFontSelectionDialog is used to define font. It contains three tabs with the different font formatting information and is ideally used in text editors and other applications where font style can be selected (example_font_selector.c).

To be able to use HildonFontSelectionDialog some knowledge of Pango text formating is required, as the returned variable is of type PangoAttrList. For more information about Pango, see http://developer.gnome.org/doc/API/2.0/pango/

HildonFontSelectionDialog *dialog;
gint result;
PangoAttrList *list = NULL;
GSList *attrs = NULL;
PangoAttrIterator *iter;
AppData *appdata = (AppData *) data;

/* Create dialog */
dialog = HILDON_FONT_SELECTION_DIALOG
        (hildon_font_selection_dialog_new(GTK_WINDOW(appdata->window), "Font selector"));

/* Show the dialog */
gtk_widget_show_all(GTK_WIDGET(dialog));

/* Wait for user to select OK or Cancel */
result = gtk_dialog_run(GTK_DIALOG(dialog));

if (result == GTK_RESPONSE_OK) {
    /* Get selected font from dialog */
    list = hildon_font_selection_dialog_get_font(dialog);
    iter = pango_attr_list_get_iterator(list);
    attrs = pango_attr_iterator_get_attrs(iter);
 
    /* Now the new font setting are in 'attrs' variable */
    /* Use it however suitable for the application */
}

/* Close the dialog */
gtk_widget_destroy(GTK_WIDGET(dialog));
			

The screenshot below shows the HildonFontSelectionDialog with the Style tab open.

Graphic27

HildonFileDetailsDialog

#include <hildon-widgets/hildon-program.h>
#include <hildon-widgets/hildon-file-details-dialog.h>
#include <gtk/gtk.h>

/* Application UI data struct */
typedef struct _AppData AppData;
struct _AppData {
    HildonProgram *program;
    HildonWindow *window;
};
			

HildonFileDetailsDialog is a dialog to show file information. It has two tabs, first one containing file name, size, last modification date, and some general information and second one being empty for the use of applications. This kind of dialogs are used not only in a file manager application but also in all kind of applications with user files, like editors and viewers.

The following source code describes how to create HildonFileDetailsDialog with an additional application specific tab. In this example (example_file_details.c) the extra tab only shows the file name again, but in a real application it could contain line count, preview of a document, length of the sound etc. The type of the object “additional tab” is GTK_LABEL, so all kind of widgets can be inserted inside it.


HildonFileDetailsDialog *dialog;
GtkWidget *label = NULL;
gint result;

/* Create dialog */
dialog = HILDON_FILE_DETAILS_DIALOG
  (hildon_file_details_dialog_new(GTK_WINDOW(data->window), TEXT_FILE));

/* Set the dialog to show tabs */
g_object_set(dialog, "show-tabs", TRUE, NULL);

/* Create some content to additional tab */
label = gtk_label_new(g_strdup_printf
  ("Name of this \nfile really is:\n%s", TEXT_FILE));

/* Add content to additional tab label */
g_object_set(dialog, "additional-tab", label, NULL);

/* Change tab label */
g_object_set(dialog, "additional-tab-label", "More Info", NULL);

/* Show the dialog */
gtk_widget_show_all(GTK_WIDGET(dialog));

/* Wait for user to select OK or CANCEL */
result = gtk_dialog_run(GTK_DIALOG(dialog));

/* Close the dialog */
gtk_widget_destroy(GTK_WIDGET(dialog));
			

Here's an example screenshot of File Details dialog.

File Details dialog

HildonBanner

HildonBanners are used in many places in the maemo applications. They show a small information banner in the top-right corner of the application view for a few seconds and then disappear automatically. They are used for all kind of notifications like “File Saved”, “Unable to make connection” or “Please choose only one option”.

There are multiple functions to choose, depending on the information we want to show. The banner can show plain text, custom icon, or progress bar for the user. The next example (example_banner.c) presents all these alternatives. First the function which shows the different HildonBanners one by one.

/* Includes */
#include <hildon-widgets/hildon-program.h>
#include <hildon-widgets/hildon-banner.h>
#include <gtk/gtk.h>

static gint banner_type = 1;
GtkWidget *banner = NULL;

/* Callback to show information banners */
void show_banner(GtkButton * widget, HildonWindow * window)
{
    switch (banner_type) {
    case 1:
        /* Show normal information banner and this automatically goes away */
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Hi there!");
        break;

    case 2:
        /* Informaton banner with animation icon. This banner does not automatically disapear. */
        banner = hildon_banner_show_animation(GTK_WIDGET(window), NULL, "This is animation icon");
        break;

    case 3:
        /* Remove current information banner */
        gtk_widget_destroy(GTK_WIDGET(banner));
        break;

    case 4:
        /* Information banner with progressbar */
        banner = hildon_banner_show_progress(GTK_WIDGET(window), NULL, "Info with progress bar");
        /* Set bar to be 20% full */
        hildon_banner_set_fraction(HILDON_BANNER(banner), 0.2);
        break;

    case 5:
        /* Set bar to be 80% full */
        hildon_banner_set_fraction(HILDON_BANNER(banner), 0.8);
        break;

    case 6:
        /* With sixth click, end the application */
        gtk_main_quit();
    }

    /* Increase the counter */
    banner_type++;
}
			

The main function introduces how to pack widgets inside the HildonWindow using GtkVBox, which is a vertical box container in GTK+. Inside the vbox it places one button, which is connected to the show_banner callback function.

/* Main application */
int main(int argc, char *argv[])
{
    /* Create needed variables */
    HildonProgram *program;
    HildonWindow *window;
    GtkWidget *main_vbox;
    GtkWidget *button1;

    /* Initialize the GTK. */
    gtk_init(&argc, &argv);

    /* Create the hildon program and setup the title */
    program = HILDON_PROGRAM(hildon_program_get_instance());
    g_set_application_name("App Title");

    /* Create HildonWindow and set it to HildonProgram */
    window = HILDON_WINDOW(hildon_window_new());
    hildon_program_add_window(program, window);

    /* Add vbox to hildon window */
    main_vbox = gtk_vbox_new(FALSE, 0);
    gtk_container_add(GTK_CONTAINER(window), main_vbox);

    /* Add button to vbox */
    button1 = gtk_button_new_with_label("Show Info");
    gtk_box_pack_start(GTK_BOX(main_vbox), button1, FALSE, TRUE, 0);

    /* Add signal listener to button */
    g_signal_connect(G_OBJECT(button1), "clicked",
                     G_CALLBACK(show_banner), window);

    /* Connect signal to X in the upper corner */
    g_signal_connect(G_OBJECT(window), "delete_event",
      G_CALLBACK(gtk_main_quit), NULL);

    /* Begin the main application */
    gtk_widget_show_all(GTK_WIDGET(window));
    gtk_main();

    /* Exit */
    return 0;
}
			

Using HildonBanner widgets is very simple as the HildonWindow takes care of showing them. The output of the application can be seen in the screenshots below. When clicking the button, a normal banner appears first, then one with the custom icon, and at last theprogress bar. The last click closes the application.

Graphic28

Graphic29_2

Graphic29

There are a lot more Hildon widgets available, to get more information about them see the Hildon API documentation

Using maemo Input Mechanism

Touchscreen (Mouse)

The maemo platform is designed to be used with a touchscreen, and the SDK with a mouse to emulate the touchscreen functionality. Some aspects to remember:

  • The main functional difference between the use of mouse and touchscreen needs to be remembered: It is not possible to move cursor position on the touchscreen without “pressing” the button all the time. This difference may be noticed e.g. in drawing application or game, where the listening events need to be designed so that user can click left and right side of the screen without ever “moving” the mouse pointer from left to right.
  • Only left mouse button should be used and remember not to add any functionality under the right mouse button. Touchscreen can only recognice one kind of 'clicks'.
  • When menus, usually got pressing the right mouse button, are wanted to be created, it is possible to implement them using a little bit longer click of the left mouse button. This kind on context-sensitive menus are supported by the platform, see gtk widget tap and hold setup function in the Hildon API documentation.
  • GtkWidget in maemo has been modified to pass a special insensitive_press signal when the users presses on an insensitive widget. This was added because of the need for actions such as showing a banner when disabled widgets are pressed e.g. in menu.

With this information it is possible to fully emulate touchscreen behavior with the mouse in the maemo SDK so that there shouldn't be any problems when using the application in the actual device.

Context Sensitive Menu

Context sensitive menus are implemented in the GtkWidget class. An application may register these menus for it's widgets which are accessed by tap-and-hold over the widget.

You can find an example on how to add this functionality in the maemo-examples package in the file example_context.c .

Hardware Keys

The maemo device has hardware keys which are also supported in the SDK using different keyboard events. The actual number and type of keys depends on the device, but the common ones are presented in the table below.

Button Description Keyboard Key Event
Graphic43 Move up Arrow key up GDK_Up
Graphic47 Move down Arrow key down GDK_Down
Graphic47 Move left Arrow key left GDK_Left
Graphic47 Move right Arrow key right GDK_Right
Graphic47 Select, Confirm Return GDK_Return
Graphic48 Cancel, Close Esc GDK_Escape
Graphic49 Open menu F4 GDK_F4
Graphic50 Show Home F5 GDK_F5
Graphic51 Full screen F6 GDK_F6
Graphic52 Increase / Zoom in / Volume up F7 GDK_F7
Graphic53 Decrease / Zoom out / Volume down F8 GDK_F8

Adding support for wider number of keys is easy as the actual key pressing signals can be implemented with GDK key events. An example (example_hard_keys.c) of this mapping is presented below, first the callback function which is called whenever keys are pressed.

#include <hildon-widgets/hildon-program.h>

#include <hildon-widgets/hildon-banner.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

/* Callback for hardware keys */
gboolean key_press_cb(GtkWidget * widget, GdkEventKey * event,
                      HildonWindow * window)
{
    switch (event->keyval) {
    case GDK_Up:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Navigation Key Up");
        return TRUE;

    case GDK_Down:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Navigation Key Down");
        return TRUE;

    case GDK_Left:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Navigation Key Left");
        return TRUE;

    case GDK_Right:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Navigation Key Right");
        return TRUE;

    case GDK_Return:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Navigation Key select");
        return TRUE;

    case GDK_F6:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Full screen");
        return TRUE;

    case GDK_F7:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Increase (zoom in)");
        return TRUE;

    case GDK_F8:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Decrease (zoom out)");
        return TRUE;

    case GDK_Escape:
        hildon_banner_show_information(GTK_WIDGET(window), NULL, "Cancel/Close");
        return TRUE;
    }

    return FALSE;
}
	

In the main function the callback function is connected with g_signal_connect to listen “key_press_event” signal. Ehenever some hardware key is pressed, key_press_cb function is called to process the event.

int main( int argc, char* argv[] )
{
    ...
    /* Add hardware button listener to application */
    g_signal_connect(G_OBJECT(window), 
        "key_press_event", G_CALLBACK(key_press_cb), window);
    ...
}
	

The output of the hardware key example application is shown below in the case zoom in (F7) is pressed.

Graphic30

Maemo Text Input Methods

The maemo platform includes text input methods used with touch screen. There are two different input types: virtual keyboard and hand writing recognition. Only the virtual keyboard is provided in maemo SDK but as the input is transparent for the application, all applications will automatically support handwriting recognition, too. When using the device, it's possible to select between two keaboard layouts. When you touch the screen using a finger, a thumb keyboard is provided.

The input methods work automatically so that whenever text widgets (entry, text view) are pressed or text inside them selected, keyboard pops up. In the same way the keyboard is hidden automatically when use selects some other widget. A screenshot of the maemo virtual keyboard is visible below.

Graphic31

For the development use it is also possible to use PC keyboard to input text. It should work without any changes but if experienced some problems it can be also enabled in SDK by pressing the right mouse button on top of some text widget and then selecting “Input Methods -> X Input Method” from the menu.

Application Layout Considerations

The layout of maemo applications can be described as wide and not very tall because of the screen dimensions (800x480 pixels). This wideness is emphasized when a text input method is popped up, so there are some rules, which should be followed, when application UI is designed.

  • Make the application UI area scalable by using the GTK+ layout containers like GtkVBox and don't use any fixed layout like GtkFixed. When the application area doesn't fit into the screen once the keyboard pops up, it should be scrollable. This can be achieved by adding application area widgets inside GtkScrolledWindow.
  • Use only one visible toolbar at the same time in one application. An exception for this is the HildonFindToolbar, which should pop on top of the main toolbar when search is done. It is also a good design principle to allow the user to hide toolbars through the menu to maximize the application area.
  • Enable full screen mode in the application. The mode can abe toggled with full screen hardware button. This is easy to implement with gtk_window_fullscreen() and gtk_window_unfullscreen() functions.
  • Avoid larger dialogs, which take a big part of the application area height. If the dialog needs text input, this is especially important, because the virtual keyboard is then needed to be visible at the same time.

More information you can find in the Hildon UI Style Guide Summary.

Integrating Applications to maemo Framework

This chapter explains how to integrate applications to maemo Application Framework, including adding application to Task Navigator menu and different features provided by the platform.

Getting Application to Task Navigator Menu

Maemo .desktop File

To make application visible in maemo Task Navigator it needs a Desktop file for the application. This file contains all the essential information needed to show the application entry in the menu, like name, binary and D-BUS service name. Name of the file should be [application].desktop and location in filesystem “/usr/share/applications/hildon/”.

An (example_libosso.desktop) file for our application looks following:

[Desktop Entry]
Encoding=UTF-8
Version=1.0
Type=Application
Name=Example libOSSO
Exec=/usr/bin/example_libosso
X-Osso-Service=org.maemo.example_libosso
Icon=qgn_list_gene_default_app
MimeType=application/x-example;
		

The fields in this file are:

Encoding – The character encoding of the file, should be UTF-8.

Version – Version of the application desktop file.

Type – Type of the entry, should be Application.

Name – The name of the application which will come visible to Task Navigator menu.

Exec – The application binary which will be started from the menu entry. Although D-BUS allows launching of applications by service name, this is useful in order to be able to launch also applications that are not registered as D-BUS services.

X-Osso-Service – D-BUS service name of the application. This will be defined in the [application].service file, see next section.

Icon – Name of icon that represents this application. The icon will be visible in Task Navigator before application name. Application can install it's own icon or use one of the predefined icons available in platform. Format of the file should be PNG and the prefix (.png) should not be defined.

MimeType – The MIME type(s) supported by this entry.

Other commonly used fields:

StartupWMClass – Name of the application binary name. This field is optional and only needed when binary name is different than D-BUS service name. In this case, if this property is not set, application will not be shown in the Task Navigator's Application Switcher.

X-Window-Icon – Usually the same as Icon field.

X-Window-Icon-Dimmed – If the dimmed icon should have different look than normal icon.

X-Osso-Type – Type for the exec, usually application/x-executable.

Terminal – Whether the program runs in a terminal window.

Note: it is not allowed to have whitespace after these fields.

See the Desktop Entry Specification for more details.

Desktop file itself doesn't include the location of the application in the Task Navigator menu. So to make application visible in there, also a symbolic link do desktop file is needed in “/etc/others-menu/”. This link assigns the order of the entries as well as the possibility that it's not in the root menu but instead inside some folder. Application should do this link when package is installed.

The format of the link is such that it begins with four numbers which define the order of the entry (smallest come on top of the menu) and then underscore (_) following the application name. To create this link for our test application:


[sbox-PC_SDK: /etc/others-menu] > ln -s /usr/share/applications/hildon/example_libosso.desktop \
  /etc/others-menu/0010_example_libosso.desktop
		

To see the new entry now in Task Navigator, launch the GUI again with “af-sb-init.sh restart”. Example LibOSSO entry should come visible to menu like shown below.

Graphic33

Now to launch the application D-BUS service file is still needed. Next section describes how to create it.

D-BUS Service File

D-BUS service file is needed to be able to launch the maemo application and connect it to D-BUS services. One special feature of D-BUS is that if application is not running already when D-BUS message is sent to it, D-BUS will start the application automatically. Also only one instance of a D-BUS service is running at the same time which guarantees that each application is running only once.

The format of the service file is simpler than desktop file, below is what (example_libosso.service) looks like.


[D-BUS Service]
Name=org.maemo.example_libosso
Exec=/usr/bin/example_libosso
			

The fields in this file are:

Name – The D-BUS service name. This needs to be unique and same which is used in application source codes when initializing application. Usually name is build with application provider URL following the application name to minimize the possibility for conflicts with other service files.

Exec – The full path of application binary to launch when service name gets called.

D-BUS service file is installed in “/usr/share/dbus-1/services/” and maemo GUI needs to be restarted with “af-sb-init.sh restart” before D-BUS daemon recognizes it.

LibOSSO Library

LibOSSO is the basic library containing required and helpful functions for maemo applications. The full API documentation of LibOSSO is available in Doxygen format.

Maemo Initialization

All maemo applications need to be initialized correctly or they will not work as expected. One symptom of missing initialization is that application starts from Task Navigator but closes automatically after few seconds.

Initializing application is done with osso_initialize() function. With this function application connects to D-BUS session bus and system bus. osso_initialize() function should only be called once in the application, and the structure of type osso_context_t type it returns should be stored for later usage. First parameter of the function is the application D.BUS name, used also in application D-BUS service file. Second parameter is application version as a string. Third is the activation type, with TRUE the library assumes that the application binary has been launched by the D-BUS daemon, and thus will connect to the D-BUS activation bus. Fourth parameter is the GLib main-loop context to connect to, NULL should be used for the default context.

osso_context_t * osso_initialize(const gchar *application, const gchar* version, gboolean activation, GMainContext *context)

When application is closing, osso_deinitialize function should be called to close the message bus connection and free all memory allocated by the library.

void osso_deinitialize(osso_context_t *osso)

Below is an example (example_libosso.c) of this, if the initialization doesn't succeed function returns NULL.

osso_context_t *osso_context;

/* Initialize maemo application */
osso_context = osso_initialize(
    "example_libosso", "0.0.1", TRUE, NULL);
       
/* Check that initialization was ok */
if (osso_context == NULL)
{
    return OSSO_ERROR;
}
		

Remote Process Messages

System wide messages in maemo platform are handled with D-BUS system messaging which is a Remote Process Communication (RPC) method. LibOSSO has own wrappers to normal D-BUS functions to make usage simpler and to maintain backward compatibility. By using D-BUS applications can send messages from one process to another.

Callback function which receives the messages can be like this (example_libosso.c), where AppData structure contains the initialized osso_context:

	  
#include <hildon-widgets/hildon-program.h>
#include <hildon-widgets/hildon-banner.h>
#include <gtk/gtk.h>
#include <libosso.h>

#define OSSO_EXAMPLE_NAME    "example_libosso"

#define OSSO_EXAMPLE_SERVICE "org.maemo."OSSO_EXAMPLE_NAME
#define OSSO_EXAMPLE_OBJECT  "/org/maemo/"OSSO_EXAMPLE_NAME
#define OSSO_EXAMPLE_IFACE   "org.maemo."OSSO_EXAMPLE_NAME
#define OSSO_EXAMPLE_MESSAGE "HelloWorld"


/* Application UI data struct */
typedef struct _AppData AppData;
struct _AppData {
    HildonProgram *program;
    HildonWindow *window;
    osso_context_t *osso_context;
};

...

/* Callback for normal D-BUS messages */
gint dbus_req_handler(const gchar * interface, const gchar * method,
                      GArray * arguments, gpointer data,
                      osso_rpc_t * retval)
{
    AppData *appdata;
    appdata = (AppData *) data;

    osso_system_note_infoprint(appdata->osso_context, method, retval);
    osso_rpc_free_val(retval);

    return OSSO_OK;
}
		

To attach this callback function to receive all the normal D-BUS messages which will come to application, osso_rpc_set_default_cb_f function shall be used:

int main( int argc, char* argv[] )
{
    osso_return_t result;
    ...
    /* Add handler for session bus D-BUS messages */
    result = osso_rpc_set_cb_f(appdata->osso_context, 
                               OSSO_EXAMPLE_SERVICE, 
                               OSSO_EXAMPLE_OBJECT, 
                               OSSO_EXAMPLE_IFACE,
                               dbus_req_handler, appdata);

    if (result != OSSO_OK) {
        g_print("Error setting D-BUS callback (%d)\n", result);
        return OSSO_ERROR;
    }
    ...
 /* Deinitialize OSSO */
    osso_deinitialize(osso_context);    
}
		

Now our application is ready to receive D-BUS messages and whenever it receives them, dbus_req_handler function is called to process the message. From another test application (example_message.c) we can now send the “HelloWorld” which our application was designed to handle, like this:

...
#define OSSO_EXAMPLE_NAME    "example_libosso"

#define OSSO_EXAMPLE_SERVICE "org.maemo."OSSO_EXAMPLE_NAME
#define OSSO_EXAMPLE_OBJECT  "/org/maemo/"OSSO_EXAMPLE_NAME
#define OSSO_EXAMPLE_IFACE   "org.maemo."OSSO_EXAMPLE_NAME
#define OSSO_EXAMPLE_MESSAGE "HelloWorld"

...

ret = osso_rpc_run(osso_context, 
		   OSSO_EXAMPLE_SERVICE, 
		   OSSO_EXAMPLE_OBJECT, 
		   OSSO_EXAMPLE_IFACE, 
		   OSSO_EXAMPLE_MESSAGE, &retval, DBUS_TYPE_INVALID);
...
		

When example_libosso_test is started it sends a “example_message” D-BUS message to org.maemo.example_libosso service which is attached to our example_libosso application (See more about D-BUS service files from earlier chapter). Now when example_libosso receives the message it shows an banner.

Graphic32

One nice thing about D-BUS is that receiving application doesn't need to be even started: D-BUS can automatically start the application based on it's service file and then pass the message to it!

Hardware State Messages

Maemo applications can connect to listen the system D-BUS messages like “battery low” and “shutdown”. When these messages are received application may want to ask user to save files which are open or react to them however wanted.

A callback function is defined like below, taking osso_hw_state_t and gpointer as the parameters. The changed state can be gotten from state variable.

/* Callback for hardware D-BUS events */
void hw_event_handler(osso_hw_state_t *state, gpointer data)
{
    AppData *appdata;
    appdata = (AppData *) data;

    if (state->shutdown_ind)
    {
        hildon_banner_show_information(GTK_WIDGET(appdata->window), NULL, "Shutdown event!");
    }
    if (state->memory_low_ind)
    {
        hildon_banner_show_information(GTK_WIDGET(appdata->window), NULL, "Memory low event!");
    }
    if (state->save_unsaved_data_ind)
    {
        hildon_banner_show_information(GTK_WIDGET(appdata->window), NULL, "Must save unsaved data event!");
    }
    if (state->system_inactivity_ind)
    {
        hildon_banner_show_information(GTK_WIDGET(appdata->window), NULL, "Minimize application inactivity event!");
    }
}
		

To attach this handler e.g. in application main(), use osso_hw_set_event_cb().

 
    ...
    /* Add handler for hardware D-BUS messages */
    result = osso_hw_set_event_cb( appdata->osso_context,
        NULL, hw_event_handler, (gpointer) appdata );
    
    if (result != OSSO_OK)
    {
        g_print("Error setting HW state callback (%d)\n", result);
        return OSSO_ERROR;
    }  
    ...
		

Note that these hardware events are not sent in SDK so testing them is only possible in maemo device.

System Exit Message

There is a separate system message apart from the hardware state messages presented earlier which comes when the applications are required to close themselves. Callback for it is like this :


/* Callback for exit D-BUS event */
void exit_event_handler(gboolean die_now, gpointer data)
{
    AppData *appdata;
    appdata = (AppData *) data;
    g_print("exit_event_handler called\n");
    /* Do whatever application needs to do before exiting */
    hildon_banner_show_information(GTK_WIDGET(appdata->window), NULL,
                                   "Exiting...");
}       
		

And the callback is set e.g. in application main like this.

int main( int argc, char* argv[] )
{
...
    /* Add handler for Exit D-BUS messages */
    result = osso_application_set_exit_cb(appdata->osso_context,
                                          exit_event_handler,
                                          (gpointer) appdata);
    if (result != OSSO_OK) {
        g_print("Error setting exit callback (%d)\n", result);
        return OSSO_ERROR;
    }
...
}  
		

Now whenever system needs to close application for any reason, it will be done gracefully.

Application State-saving and Auto-save

State-saving is a special feature of maemo platform. It means that applications save their running state from RAM to permanent memory such as flash memory, and then end the running application process. This state saving happens when switching out from the application and device memory is running low. When user then returns to backgrounded application, it is started again and application state is read from the flash memory, so that user doesn't even know that application was not running all the time.

Auto-saving is addition to state-saving for the editor-like applications which contain user data. This data/document is automatically saved at the same time with state save. Main difference is that application state is usually just small amount of variables which describe the state where application was, where the auto-saved file can be what ever file user was editing. Auto-saving guarantees that user files are not lost when e.g. battery runs out.

Information of how to implement state-saving is in LibOSSO API document.

Application Settings

Maemo uses GConf for maintaining application settings. GConf is widely used in Gnome desktop environment for this same purpose. Read more about GConf from: http://www.gnome.org/projects/gconf/

To use GConf in application, these headers need to be included:

 
#include <gconf/gconf.h>
#include <gconf/gconf-client.h>
		

To make compiler and linker find these headers, give additional "pkg-config --cflags gconf-2.0" and "pkg-config --libs gconf-2.0" parameters for gcc or to Makefile.

GConf needs to be initialized first with gconf_client_get_default() function which returns GconfClient type of variable. After this setting of different type can be stored with gconf_client_set_ functions and restored with gconf_client_get_ functions.

To keep GConf keys unique in maemo, they should be type “/apps/maemo/[application]/[key]” so for example “/apps/maemo/hello_maemo/default_font_size”.

Example of basic usage for setting and loading GConf values is following:

 
...
GConfClient *gc_client;

/* Init type system */
g_type_init();

/* Load default GConf path */
gc_client = gconf_client_get_default();

if (gc_client == NULL) {
    return FALSE;
}

...

/* Set integer variable */
gconf_client_set_int(gc_client, key, my_number, NULL);

/* Set string variable */
gconf_client_set_string(gc_client, key, my_string, NULL);

...

/* Get integer variable */
gint my_number = gconf_client_get_int(gc_client, key, NULL);

/* Get string variable */
gchar *my_string = gconf_client_get_string(gc_client, key, NULL);

...

/* Free gconf settings object */
g_object_unref(gc_client);
		

The maemo version of GConf is unaltered so all tips and tricks available from Internet are usable in maemo too.

Usage of Backup Application

The backup application saves and restores user data stored in ~/MyDocs (by default) and setting directories/files /etc/osso-af-init/gconf-dir (a link to GConf database /var/lib/gconf), /etc/osso-af-init/locale, and /etc/bluetooth/name. It can be configured to back up other locations and/or files as well by custom configuration files.

The backup application must not to be distrupted by other applications writing or reading during a backup or restore operation.

For restore process, Backup therefore will, if the user approves, ask the application killer to close all application, and then wait until it has been done.

For backing up, the backup_start and backup_finish D-BUS signals will be emitted on the session bus, which will indicate to applications that they should not write to disk.

D-BUS description and methods of Backup Application

Service      com.nokia.backup
Interfaces   com.nokia.backup
Object Paths /com/nokia/backup

Method: cancel

Name            cancel
Parameters      none
Returns         Empty reply
Description     Cancels any ongoing backup or restore operation

Method: activate

Name            activate
Parameters      none
Returns         Empty reply
Description     Used to activate the application with auto-activation
      

Custom Backup Locations

For other data not normally backed up the so-called locations configuration is used. It is important that the locations configuration paths MUST NOT overlap with the documents path.

The locations configuration lets applications install a configuration file with a list of files and directories that should be included in the backup. The files should be installed into /etc/osso-backup/applications, named <application>.conf and consist of simple XML format. For our application the example_libosso.conf looks the following way:

<backup-configuration>
  <locations>
    <location type="file"
              category="settings" auto="true">/etc/example.ini</location>

    <location type="dir"
              category="documents">/home/user/foo</location>
    <exclusion type="file" 
              category="settings">/home/user/bigfile</exclusion>

    <exclusion type="file" 
              category="settings">/tmp/*.jpg</exclusion>

  </locations>
</backup-configuration>
          

With the <location> tag we can give different locations that should be backed up, note that the path must be absolute. <exclusion> tag can be used if some files in certain directories are not wanted to be backed up, for example, in the case of some setting file changing from one software version to another. In this way the new setting file of updated software won't be destroyed, if the backup is restored. Wild cards '?' and '*' are supported. too.

Both tags have TYPE and CATEGORY arguments and <location> tag additional AUTO argument:

TYPE – Its value can be "file" or "dir" for a file or directory. This argument must be provided.

CATEGORY – It is used for handling selective backup and restore. It may be omitted, in which case the location will only be backed up when backing up or restoring everything. The value can be:

  • emails
  • documents
  • media
  • contacts
  • bookmarks
  • settings

AUTO – Its value can be true or false. It is used to tell the backup application not to request a user response in case of a conflict, but automatically replace the file. Note that the auto argument is only used for files, not for directories. It may be omitted, in which case it defaults to false.

After Restore Run Scripts

The backup application makes it possible to execute scripts after restore operation. There are two kind of scripts. First there can be scripts that are executed after every restore operation. Then there are also scripts that are executed only if the restore is made from a backup created in some earlier product.

For the scripts that are used to transform data between the device software versions the location for applications to install the scripts is: /etc/osso-backup/restore.d/<dir>, where <dir> is a subdirectory for each transition between two different consecutive version of the platform. For transforming between IT­2006 and IT­2007 versions the directory is: /etc/osso­-backup/restore.d/it2007/. For scripts that are executed after every restore the location is /etc/osso-­backup/restore.d/always.

The files installed here should have the executable bit set. Any files ending with "~" or ".bak" are ignored, as directories or files starting with a dot (“.”).

Each script will be executed with a command­line argument that is the path of a file containing the list of all files that have been restored, per category. The format of this file is:

[CATEGORY]
/path/to/file1
/path/to/file2
...
[CATEGORY]
...

CATEGORY is one of the OSSO categories (emails,documents, media, contacts, bookmarks, or settings).This makes it possible for the scripts to check which files they need to upgrade, if any at all. The format is chosen to be easy to parse with a simple script or program.

The scripts will be executed after a successful restoration, or after a restoration has been cancelled. In both cases the scripts will only be executed if any files were actually restored. Scripts should clean up after transforming, so that old files are not left behind. The script or program should use the common convention and return zero on success, and non­zero in case of failure. Application developers should try to make their programs execute and finish quickly.

How to Map MIME Types

To map a MIME-type for an application (that specifies for the platform which application should handle it) we need to define this in its desktop file with adding MimeType field to its desktop file.

An example_libosso.desktop file for our application looks following:

[Desktop Entry]
Encoding=UTF-8
Version=1.0
Type=Application
Name=Example libOSSO
Exec=/usr/bin/example_libosso
X-Osso-Service=org.maemo.example_libosso
Icon=qgn_list_gene_default_app
MimeType=application/x-example;
     

The last line is the important one, and specifies that this application can handle the MIME type "application/x-example".

New MIME Type with OSSO Category Extension

If the application is introducing new MIME-type to the system, we need to provide the mime-info XML (see more at http://standards.freedesktop.org/shared-mime-info-spec/) that defines it, in our case an example-mime.xml file for our application looks following:

<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"
           xmlns:osso="http://nokia.com/osso/mime-categories">
  <mime-type type="application/x-example">

         <comment>Example application file</comment>
         <osso:category name="images"/>
         <magic priority="50">
                <match type="string" value="FOO" offset="0"/>

         </magic>
         <glob pattern="*.foo"/>
  </mime-type>
</mime-info>
        

This entry maps one extension and a "magic" string to a certain MIME type.

NOTE: the glob pattern shall be given in lower-case.

What Is OSSO Category

The platform has a notion of file categories for the user's data files. The available categories are:

  • Bookmarks
  • Contacts
  • Documents
  • Emails
  • Images
  • Audio
  • Video
  • Other

A mapping is setup between categories and MIME types, so that the MIME type of a file decides which category it is in. The MIME type setup is handled by the shared-mime-info infrastructure, and the category information is added to that same framework.

Adding a mapping between a category and a number of MIME types is done much like adding or editing the supported MIME types in the system.

Each application or library that adds a category mapping should add a file in:

/usr/share/mime/packages/
           

The file format is the same XML format used for MIME types, with an added tag "<osso:category>". See the example above, where it sets up a mapping between .foo files and the Images category.

Updating the Platform's Databases

To introduce the newly defined MIME-type(s) to the platform we need to

  1. copy the mime-information XML under /usr/share/mime/packages:
    [sbox-PC_SDK: ~] > cp example-mime.xml /usr/share/mime/packages
                 
    
  2. update the MIME and desktop database:
    [sbox-PC_SDK: ~] > update-mime-database /usr/share/mime
    [sbox-PC_SDK: ~] > update-desktop-database /usr/share/applications
                 
    
  3. and update the osso category database:
    [sbox-PC_SDK: ~] > osso-update-category-database /usr/share/mime
                 
    
    

if we want to remove the MIME-type from the platform, simply delete the XML file under /usr/share/mime/packages/, and update the databases as above.

Registering a MIME-type with Package

Since most of the applications are installed on the platform via precompiled packages, the MIME-type registration has to be done by it as well.

The steps are similar as above.

To have the MIME information XML installed under /usr/share/mime/packages we need to edit the package's rules and install files, in our case it would look like this:

  • in the rules file under install section we need to add the following lines
    
    mkdir -p $(CURDIR)/debian/tmp/usr/share/mime/packages
    cp $(CURDIR)/example-mime.xml $(CURDIR)/debian/tmp/usr/share/mime/packages
                    
    
  • and in example_libosso.install we need to add
    usr/share/mime/packages/example-mime.xml
                    
    

This way we can assure that the mime information XML is being installed under /usr/share/mime/packages.

Both in package's postinst and postrm files we need to add the following two lines:

if [ -x /usr/sbin/update-mime ]; then
 update-mime-database /usr/share/mime
fi

if [ -x /usr/sbin/update-mime ]; then
 update-desktop-database /usr/share/applications
fi

if [ -x /usr/bin/osso-update-category-database ]; then
 osso-update-category-database /usr/share/mime
fi
           

This keeps the platform's mime information and osso category databases up-to-date.

Clipboard Usage

In maemo there are number of clipboard enhanchements to the X clipboard and Gtk+, in order to able to

  • Support retaining the clipboard data when applications that own the clipboard exit.
  • Be able to copy and paste rich text data between Gtk+ text views in different applications.
  • Provide a generally more pleasant user experience; make it easy for application developers to gray out "Paste" menu items when the clipboard data format is not supported by the application.

GtkClipboard API Changes

gboolean gtk_clipboard_set_can_store (GtkClipboard   *clipboard
                                      GtkTargetEntry *targets,
                                      gint            n_targets);
       

This function sets what data targets the current clipboard owner can be transferred to the clipboard manager. NULL can be passed as targets, together with 0 as n_targets to indicate that all targets can be transferred.

When the clipboard owner changes, these values are reset.

void gtk_clipboard_store (GtkClipboard *clipboard);
       

This function tells the clipboard to try to store the contents of the targets specified using gtk_clipboard_set_can_store. If no such call has been made, or if there is no clipboard manager around, this function is simply a no-op.

Applications can call this function when exiting, but it's called automatically when the application is quitting, when we do so with gtk_main_quit(). If the application is not the owner of the clipboard, the function will simply be a no-op.

In addition, adding a convenience function for finding out if a target is supported (in order to be able to gray out "Paste" items if none of the existing clipboard targets are supported)

gboolean gtk_clipboard_wait_is_target_available (GtkClipboard *clipboard,
                                                 GdkAtom       target);
       

GtkTextBuffer API Changes

In order to support rich text copy and paste, some new functions were introduced:

void
gtk_text_buffer_set_enable_paste_rich_text (GtkTextBuffer *buffer,
                                            gboolean       can_paste_rich_text);

gboolean
gtk_text_buffer_get_enable_paste_rich_text (GtkTextBuffer *buffer);
       

The setter function toggles whether it should be possible to paste rich text in a text buffer.

To prevent applications from getting confused when text with unexpected tags is pasted to a buffer, the notion of "rich text format" was added:

void
gtk_text_buffer_set_rich_text_format (GtkTextBuffer *buffer,
                                      const gchar   *format);
G_CONST_RETURN *
gtk_text_buffer_get_rich_text_format (GtkTextBuffer *buffer);
       

When a buffer has a certain text format, it can only paste rich text from buffers that have the same text format. If the formats differ, only the plain text will be pasted. If a buffer has its format set to NULL, it means that it can paste from any format. For example, a format called "html" could include the tags "bold", "italic" etc. Thus we could only paste text from buffers having the same format specified.

Note that the string is just an identifier. It is up to the application developers to make sure that if they specify that an application supports a certain format, then the tags in the buffer need to be specified for that format.

How to Write Control Panel Applets

Control panel is designed to be extendable, and items can be added to it with dynamic libraries with certain functions and desktop files describing the library.

The Functions

There are two functions needed for a control panel applet These functions are defined in hildon-cp-plugin/hildon-cp-plugin-interface.h. The first, more important one, is called execute. It is called when the applet is activated from control panel. Usually this function creates a dialog and waits until it is done. Any dialog created should be modal to the window in parameter done. Do note, that the library might be unloaded when execute returns, so you shouldn't leave any g_timeouts, gconf_notifys or such when done. You do not need to do any gtk or osso initialization, they're already done at this point.

 
#include <hildon-cp-plugin/hildon-cp-plugin-interface.h>
#include <gtk/gtk.h>

osso_return_t execute(osso_context_t *osso, gpointer data, gboolean user_activated)
{
	GtkWidget *dialog;
	gint response;

	/* Create dialog with OK and Cancel buttons. Leave the separator out, as we don't have any content. */
	dialog = gtk_dialog_new_with_buttons(
		"Hello control panel",
		GTK_WINDOW(data),
		GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR,
		GTK_STOCK_OK,
		GTK_RESPONSE_OK,
		GTK_STOCK_CANCEL,
		GTK_RESPONSE_CANCEL,
		NULL);

	/* ... add something to the dialog ... */

	if (!user_activated)
	{
		/* ... load state ... */
	}

	/* Wait until user finishes the dialog. */
	response = gtk_dialog_run(GTK_DIALOG(dialog));

	if (response == GTK_RESPONSE_OK)
	{
		/* ... do something with the dialog stuff ... */
	}

	/* Free the dialog (and it's children) */
	gtk_widget_destroy(GTK_WIDGET(dialog));

	return OSSO_OK;
}             
     

The other function is called save_state. It is called when application using the applet is saving state. Usually this does nothing, but if you wan't the application to support state saving, use this.

       
osso_return_t save_state(osso_context_t *osso, gpointer data)
{
        /* ... save state ... */

        return OSSO_OK;
}
     

Building the Applet

To use the applet, it needs to be built into a dynamic library aka shared object. This can be accomplished by giving flag -shared to gcc.

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

The binary produced by gcc should be installed to path specified in hildon-control-panel pkg-config entry. This path can be obtained with pkg-config hildon-control-panel --variable=pluginlibdir. By default this is /usr/lib/hildon-control-panel.

The .desktop File

Any control panel applet needs a desktop file describing it. The file contains metadata like name, icon name and library of the applet. The applet desktop file is much like the desktop file for any other application.

Here's an example desktop file for the applet created above. Maemo 3.x brings new "Categories" field.

[Desktop Entry] 
Encoding=UTF-8
Version=1.0
Name=Control Panel Hello World
Comment=A control panel example applet
Type=HildonControlPanelPlugin
Icon=qgn_list_cp_isetup
X-control-panel-plugin=libapplet.so
Categories=general
    

The desktop file must be installed to directory specified in pkg-config file for hildon-control-panel. This directory can be obtained by pkg-config hildon-control-panel --variable=plugindesktopentrydir. By default this is /usr/share/applications/hildon-control-panel.

How to Write Statusbar Plugins

The maemo statusbar can contain user defined items. Normally there's places for two of these additional items. These two slots are used by usb connection indicator and alarm indicator, but can be used by any plugin. Although plugins can specify a priority the current version of status bar doesn't handle the plugin priorities, so only the two newest plugins are visible.

To make your own statusbar plugin, you need to make a shared object with certain functions in it. The statusbar plugins are activated with certain dbus messages sent to control panel, and the name of the library is gotten from this signal, so no other description for the plugin is needed.

The Functions

The function declarations cannot be found in any header file, mostly because their names may differ. The most important function is the entry function, which is called when the library is opened. This function will tell the other functions needed for the plugin operation. The entry function's name depends on the name of your library. It's [your lib name]_entry, for example for plugin called hello the entry function is called hello_entry.

/* 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);

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 statusbar, which functions will be called at certain events. On first event, the initialize function is called. The return value of initialize will be passed to destroy, update and get_priority. To make it possible to destroy an item, the first parameter of initialize should be stored for later use. The second parameter should be initialized to a new button, that will be the graphical representation of the plugin. The size of a plugin button should be 40 by 40 pixels. If any g_timeouts or such are used, the IDs of these should be stored for removal (if not removed, you will probably crash the whole statusbar).

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;
}
     

Now we have the plugin initialized but deliberately not shown yet. The next function is 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. Use of these is all up to the plugin. We're just deciding whether to show ourselves or not by the first integer value.

        
void hello_update(void *data, gint value1, gint value2, const gchar *str)
{
  if (!data) {
    return;
  }

  struct HelloPlugin *hello = (struct HelloPlugin *)data;

  if (value1 == 0) {
    /* Destroy the applet, we get unloaded by this. Note, destroying the item,
       not the button. */
    gtk_widget_destroy(GTK_WIDGET(hello->item));
  }
  else {
    /* Reveal the plugin. */
    gtk_widget_show_all(hello->button);
  }

  return;
}        
     

Now, when the item gets destroyed, the destroy function will be called. The destroy function should free all memory and release all notifys that might lead to the plugin code. The widget should not be destroyed here, the Statusbar will take care of that.

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

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

  g_free(data);
}
     

Now we only have one function left, the get_priority function. This should be used when deciding which plugins are visible and in which order, but is not supported yet.

         
gint hello_get_priority(void *data)
{
  return 42;
}
     

Building the Shared Object

The shared object can be built just like any normal binary, but using the -shared 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 should be installed to path specified in hildon-status-bar-lib pkg-config entry. This path can be obtained with pkg-config hildon-status-bar-lib --variable=pluginlibdir. By default this is /usr/lib/hildon-status-bar.

Testing Your Plugin

To see your plugin, you need to send an event to it, for it to even be loaded. The status bar listens in system bus. There is a easy to use convenience function in libosso, called osso_statusbar_send_event. The function needs a osso context, name of the plugin and the three arguments for it (two integers and a string). Additionally the plugin can be tested with dbus-send from command line.


         
[sbox-SDK_PC: ~] > dbus-send --session --type=method_call \ 
--dest=com.nokia.statusbar /com/nokia/statusbar com.nokia.statusbar.event string:hello int32:1 int32:0 string:
     

Where the first string (hello) is the name of the plugin. The statusbar will try to load a library called lib[name].so and seeks for a function called [name]_entry from there. If the first int32 is changed to 0, the example will remove itself from the statusbar.

How to Write Internet Connected Applications

The maemo platform is designed to be used with network connection and includes powerful methods to get Internet connection easily for application. Here's some information, but to learn more check the Connectivity Guide.

Maemo IAP Paradigm

There are devices with two different connectivity modes, connected devices like PC's with broadband connections which are always connected to network and disconnected devices which are only connected when network connection is really required. Maemo devices are generally the second type.

When user starts applications which require network connection in maemo device, connectivity wizard is automatically started. This connectivity dialog then handles opening the LAN, WLAN, Bluetooth or whatever connection types automatically and application does not have the knowledge which connection method is used. Applications should not have any need to know how the connection is made, they just need to know whether the connection is available or should they be started in offline mode.

Automatic Connection

The way to get network connectivity for maemo application is to use special environmental variable. With this method connectivity dialog is launched automatically when application tries to open network socket connection.

To acquire a network connection on the target device all we have to do is to set LD_PRELOAD environment variable so that libosso-ic-preload.so is loaded before starting the application. Easiest way to do this is to start the application with a shell script instead of starting the application binary directly. In this shell script we first run connectivity_preload.sh, which will load the libosso-ic-preload.so. After this the script runs the application binary. The script my_application.sh is an example how to do this.

 
#!/bin/sh
source /usr/bin/connectivity_preload.sh
/usr/bin/my_application
		

This my_application.sh is then started from the desktop file where the runnable binary is normally specified. Read more about desktop files from this earlier chapter. For you to be able to do this when developing on the i386 rootstrap you need to do "apt-get install osso-ic-oss" to have the connectivity script included in the target.

Testing Limitations

There are limitations for testing the connectivity with the SDK. It is possible to make and run Internet connected application inside Scratchbox with the PC SDK rootstrap, but it is of course not possible to test the WLAN and Bluetooth connections in it without the real hardware.

How to Deploy Applications

This chapter instructs how to deploy the application packages to maemo devices and for others to be used in SDK.

Creating maemo Packages

Maemo packages are normal Debian packages with one small difference (see also HOWTO: Making a package for the Application Manager):

  • Package's section (in control file) must be declared to be like "user/office". So the section is defined with "user/" prefix. There are predefined sections like office, internet, multimedia, games, utilities, other, etc..

As the maemo packages are Debian packages, you'll find a lot of useful information about building them in the maintainers' guide . Also source codes from New application HOWTO and Porting HOWTO should be checked for real life example.

For a more detailed, step-by-step introduction on debian package creation see the Creating a Debian package page.

The files which specify how packages are build are located in “debian” directory of application:

  • “debian/control” - This file contains various values which are used to manage the package like package name, short description, and the maintainer of the packages. This also lists the package dependencies needed to install the package in maemo devices, like “Depends: package_name”.
  • “debian/rules” - This file is used by dpkg-buildpackage command to actually create the package. It is actually a Makefile for Debian package building, consisting of several rules specifying how to handle the source. Each rule consists of targets, filenames or names of actions that should be carried out (e.g. `build:' or `install:').Unlike other files in debian directory, this one will be marked as executable.
  • “debian/changelog” - This lists the changes what has been made for the application and also the version number of package to be created. The structure is very strict and must be filled exactly by the rules.
  • “debian/[application].link - This file is needed to make the symbolic link of application desktop file to “etc/others-menu”. Maemo desktop file structure is explained in earlier chapter.

Once the application is properly 'Debianized', building the application is done easily with the command:

[sbox-PC_SDK: ~/test_application] > dpkg-buildpackage -rfakeroot	
	

This will create the Debian package to parent directory, together with the source package.

Installing Application Packages

Maemo packages can be installed inside Scratchbox as well as to maemo devices.

Installing to SDK

When installing Application Manager package to maemo SDK, debian package installer, like dpkg, should be used:

[sbox-SDK_PC: ~] > fakeroot dpkg -i application_i386.deb
		

Replace 'application' with the actual name of the package. Packages can be installed in both PC and ARMEL targets. The package architecture must match the Scratchbox target.

Installing to Device

To install application packages in maemo devices, Application Manager should be used. This is a tool which handles package installation process and maintains information of all external packages which have been installed. Usage of the tool is simple, it can be started from Tools menu and application similar to below appears.

Graphic34

Then click “Browse installable applications” to install packages, select the package you want to install and agree to install it. Another way is to tap the package's filename on the File Manager which opens automatically the Application Manager and starts to install the package.

Graphic35

The behavior or Application Manager may differ between maemo devices, so please consult the documentation provided with the device for further instructions.

How to proceed from here?

Now all the basic features of maemo platform should be clear and you can start digging deeper! Good places to start are:

  • The maemo_examples package includes most of the code snippets presented in the tutorial
  • Maemo New application HOWTO which introduces how to build full-featured text editor application for maemo platform and deploy it in package.
  • Maemo Porting HOWTO which demonstrates how to port existing GTK+ application to maemo. Monkey Bubble game is used in this example and all the required modifications to UI are demonstrated.
  • API Documentation of the platform is good place to seek more detailed information of functions, Hildon API and LibOSSO API are both available in API section.

Code examples

You can get code examples by running the command below inside the bora sdk.

    apt-get source maemo-examples
    


Improve this page