Documentation/Maemo 5 Developer Guide/Porting Software/Porting Existing GTK+ Application to Maemo 5

(reformat images)
(Portrait Mode)
 
Line 373: Line 373:
Second action point is rotation directly. We have used Hildon API for this purpose. maemo-sliders/sliders.c
Second action point is rotation directly. We have used Hildon API for this purpose. maemo-sliders/sliders.c
 +
<source lang="c">
static DBusHandlerResult
static DBusHandlerResult
mce_filter_func (DBusConnection * connection,
mce_filter_func (DBusConnection * connection,

Latest revision as of 10:46, 4 September 2010

This section describes key aspects of the process of porting an application to the Maemo platform. When starting to port an application to Maemo platform, the first step is to set up the development environment. The actual porting after that is described in this section.

Contents

[edit] Introduction

Application that is used as an example for porting is Sliders, a GTK+-based game.

The Sliders interface consists of the main window with board, menu and a couple of dialogs. See source mentioned above for game history and description.

[edit] Autotools Usage

Sliders does not use GNU autotools originally, so we need to add configure.ac, Makefile.am and autogen.sh for simplicity. See GNU Build System for corresponding information.

[edit] User Interface Changes

Sliders game is not designed for usage in mobile devices, so we need to hildonize it in order to build effective touch interface for this simple game.

[edit] Hildonizing Main View

Before using Hildon we need to initialize it. maemo-sliders/sliders.c

/* Initialize the GTK+ and hildon libraries */
hildon_gtk_init (&argc, &argv);

After that we can create a main window with all necessary content. maemo-sliders/sliders.c

static GtkWidget*
create_main_window (void)
{
  GtkWidget *main_window;
  /* Create the main window */
  main_window = hildon_stackable_window_new ();
  gtk_window_set_title (GTK_WINDOW (main_window), "Welcome to Sliders");
  /* Create and set application menu */
  HildonAppMenu *menu = create_menu ();
  hildon_window_set_app_menu (HILDON_WINDOW (main_window), menu);
  /* Create and pack table, that contains sliders */
  appdata.table = create_table ();
  gtk_container_add (GTK_CONTAINER (main_window), appdata.table);
  return main_window;
}

We have replaced the original Sliders menu and toolbar with a Hildon application menu.

Screenshot of menu
Maemo Sliders menu


maemo-sliders/sliders.c

static HildonAppMenu *
create_menu (void)
{
  /*
   * Create menu buttons one by one, connect to appropriate callbacks and
   * add to the menu.
   */
  HildonSizeType button_size = HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH;
  HildonAppMenu *menu = HILDON_APP_MENU (hildon_app_menu_new ());
  GtkWidget *button;
  button = hildon_gtk_button_new (button_size);
  gtk_button_set_label (GTK_BUTTON (button), "New");
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (on_new_clicked), NULL);
  hildon_app_menu_append (menu, GTK_BUTTON (button));
  button = hildon_gtk_button_new (button_size);
  gtk_button_set_label (GTK_BUTTON (button), "Scores");
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (on_scores_clicked), NULL);
  hildon_app_menu_append (menu, GTK_BUTTON (button));
  button = hildon_gtk_button_new (button_size);
  gtk_button_set_label (GTK_BUTTON (button), "Open");
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (on_open_clicked), NULL);
  hildon_app_menu_append (menu, GTK_BUTTON (button));
  button = hildon_gtk_button_new (button_size);
  gtk_button_set_label (GTK_BUTTON (button), "Save");
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (on_save_clicked), NULL);
  hildon_app_menu_append (menu, GTK_BUTTON (button));
  button = hildon_gtk_button_new (button_size);
  gtk_button_set_label (GTK_BUTTON (button), "About");
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (on_about_clicked), NULL);
  hildon_app_menu_append (menu, GTK_BUTTON (button));
  gtk_widget_show_all (GTK_WIDGET (menu));
  return menu;
}

Sliders table buttons (see figure 16.13) are maximized and wrapped with hildon_gtk_button_new (). maemo-sliders/sliders.c

static GtkWidget*
create_table (void)
{
  GtkWidget *table;
  gint x, y, c = 0;
  appdata.vacant_pos = SLIDERS_NUMBER;
  appdata.move_no = 0;
  HildonSizeType button_size = HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH;
  /* Create table for sliders */
  table = gtk_table_new (4, 4, FALSE);
  /*
   * Create buttons, that represents sliders. Set "current_pos" key for each button
   * and connect each button to appropriate callback.
   */
  for (x = 0; x < SLIDERS_NUMBER; x++)
  {
    gchar *temp = g_strdup_printf ("%i", x+1);
    appdata.buttons[x] = hildon_gtk_button_new (button_size);
    gtk_button_set_label (GTK_BUTTON(appdata.buttons[x]), temp);
    g_object_set_data (G_OBJECT(appdata.buttons[x]), "current_pos", (gpointer) x);
    g_free (temp);
    gtk_signal_connect(GTK_OBJECT(appdata.buttons[x]), "clicked", GTK_SIGNAL_FUNC(on_button_clicked), NULL);
  }
  /* Add sliders to table */
  for (x = 0; x < 4; x++)
  for (y = 0; y < 4; y++)
    if (!(x == 3 && y == 3))
      gtk_table_attach_defaults (GTK_TABLE (table), appdata.buttons[c++], y, y+1, x, x+1);
  return table;
}
Maemo Sliders table

[edit] Implementing Scores Window

We have replaced original Sliders win picture by scores window (figure 16.14), that appears in two cases. It's shown after "Scores" menu button clicked maemo-sliders/sliders.c

static void
on_scores_clicked (GtkMenuItem *menuitem, gpointer user_data)
{
  scores_window (0);
}

and when player has won. maemo-sliders/sliders.c

static void
on_button_clicked (GtkWidget *button, gpointer user_data)
{
  /* ... */
    if ( has_player_won() )
    {
      scores_window (1);
      gtk_window_set_title (GTK_WINDOW (appdata.main_window), "Welcome to Sliders");
      appdata.move_no = 0;
    }
    else
    {
      gchar *temp = g_strdup_printf ("Move no.: %i", appdata.move_no);
      gtk_window_set_title (GTK_WINDOW (appdata.main_window), temp);
      g_free (temp);
    }
  /* ... */
}
Screenshot of scores window
Maemo sliders scores

Scores window contains pannable area with tree view maemo-sliders/sliders.c

/* Create a tree view that represents scores table and an area for it */
area = hildon_pannable_area_new ();
tree_view = create_treeview (HILDON_UI_MODE_EDIT);
/* ... */
/* Pack tree view */
gtk_container_add (GTK_CONTAINER (area), tree_view);
gtk_container_add (GTK_CONTAINER (window), area);

and edit toolbar. maemo-sliders/sliders.c

/* Create a new edit toolbar */
toolbar = hildon_edit_toolbar_new_with_text ("Choose scores to delete",
                                             "Delete");
/* ... */
/* Add toolbar to the window */
hildon_window_set_edit_toolbar (HILDON_WINDOW (window),
                                HILDON_EDIT_TOOLBAR (toolbar));
/* ... */
/* Set callback for "Delete" button */
g_signal_connect (toolbar, "button-clicked",
                  G_CALLBACK (delete_selected_scores),
                  tree_view);
/* Destroy scores window when upper corner arrow clicked */
g_signal_connect_swapped (toolbar, "arrow-clicked",
                          G_CALLBACK (gtk_widget_destroy),
                          window);

Win banner is shown if scores_window () called in case player has won. maemo-sliders/sliders.c

/* Additional actions for case, when player has won */
if (win)
{
  /* Show appropriate banner */
  hildon_banner_show_informationf (window, NULL, "Win! Score: %i", appdata.move_no);
  /* ... */
}

Other scores related code is quite straightforward and does not use Hildon-specific features, see the scores_window () implementation for details.

[edit] State Saving

Maemo supports state saving feature, that we have used for current Sliders game saving. The application can later open this game with the same state as before. This section describes most important steps needed to make Sliders support state savings.

state_save () is called when the user presses the "Save" menu button. Saving data should be updated before it is called. maemo-sliders/sliders.c

static void
on_save_clicked (GtkMenuItem *menuitem, gpointer user_data)
{
  /* Save moves number, vacant position and board layout to "state" */
  state.move_no = appdata.move_no;
  state.vacant_pos = appdata.vacant_pos;
  int i;
  for (i = 0; i < SLIDERS_NUMBER; i++)
    state.map[i] = (gint) gtk_object_get_data(GTK_OBJECT(appdata.buttons[i]), "current_pos");
  /* Call state saving method and process return value */
  if (state_save ())
    hildon_banner_show_information (appdata.main_window, NULL, "Saved current game");
  else hildon_banner_show_information (appdata.main_window, NULL, "Can't save current game");
}

Then osso_state_write () can be called for correct data. maemo-sliders/sliders.c

state_save(void)
{
  osso_state_t osso_state;
  osso_return_t ret;
  osso_state.state_size = sizeof(StateData);
  osso_state.state_data = &state;
  ret = osso_state_write(appdata.osso_context, &osso_state);
  if (ret != OSSO_OK)
    return FALSE;
  return TRUE;
}

state_load () is called when user clicks the "Open" menu button. It uses osso_state_read () symmetric to state_save (). maemo-sliders/sliders.c

state_load(void)
{
  osso_state_t osso_state;
  osso_return_t ret;
  osso_state.state_size = sizeof(StateData);
  osso_state.state_data = &state;
  ret = osso_state_read(appdata.osso_context, &osso_state);
  if (ret != OSSO_OK)
    return FALSE;
  return TRUE;
}

Do not forget to initialize the library context before state-saving usage. maemo-sliders/sliders.c

/* Initialize Libosso */
appdata.osso_context = osso_initialize(SL_SERVICE, VERSION, TRUE, NULL);

The information saved by using state saving functions does not survive over power off of the device, so if you need to save some data more permanently you could use e.g. libxml as shown in the next section for the scores table.

[edit] Scores Saving

Scores data store is created, when get_scores_store () called first. The store is populated from a file saved under the user's home directory. The document tree is parsed with node pointer starting from root element. maemo-sliders/sliders.c

static GtkListStore *
get_scores_store (void)
{
  static GtkListStore *store = NULL;
 
  /* Return store if it has been created before */
  if (store != NULL)
    return store;
 
  /* Create store with 2 columns of specified types */
  store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_INT);
 
  xmlDocPtr doc; /* the resulting document tree */
  xmlNodePtr cur; /* node pointer */
  xmlChar *attribute; /* node attribute name */
  char *row_name, *temp;
  gint row_score;
 
  /* Parse an XML file from the filesystem */
  char *slidersdir = g_strdup_printf ("%s%s%s", getenv("HOME"), SLIDERS_DIR, SCORES_XML);
  doc = xmlReadFile (slidersdir, NULL, XML_PARSE_NOERROR);
  g_free (slidersdir);
  if (doc == NULL) {
    g_print ("%s, %i: Failed to parse document %s\n", __PRETTY_FUNCTION__, __LINE__, SCORES_XML);
    return store;
  }
 
  /* Parse root element */
  cur = xmlDocGetRootElement(doc); //scores
  if (cur == NULL) {
    g_print ("%s, %i: Empty xml doc\n", __PRETTY_FUNCTION__, __LINE__);
    xmlFreeDoc (doc);
    return store;
  }
 
  /* Parse first row tag */
  cur = cur->xmlChildrenNode;
  if (cur == NULL) {
    g_print ("%s, %i: No row tag\n", __PRETTY_FUNCTION__, __LINE__);
    xmlFreeDoc (doc);
    return store;
  }
 
  /* Parse row tags one by one */
  while (cur != NULL)
  {
    /* ... */
 
    /* Add new row to the store */
    gtk_list_store_insert_with_values (store, NULL, gtk_tree_model_iter_n_children (GTK_TREE_MODEL(store), NULL), 0, row_name, 1, row_score, -1);
 
    /* Parse next row element */
    g_free (row_name);
    cur = cur->next;
  }
 
  /* Free the resulting tree */
  xmlFreeDoc(doc);
  return store;
}

[edit] Portrait Mode

This section explains how to use accelerometer D-Bus interface in order to switch your maemo application to portrait/landscape mode.

First at all we need to include MCE headers and connect to appropriate D-Bus signal. maemo-sliders/sliders.c

#include <dbus/dbus.h>
#include <mce/mode-names.h>
#include <mce/dbus-names.h>
#define MCE_SIGNAL_MATCH "type='signal'," \
        "sender='"    MCE_SERVICE     "'," \
        "path='"      MCE_SIGNAL_PATH "'," \
        "interface='" MCE_SIGNAL_IF   "'"
 
/* ... */
 
  /* Connect to session bus, add a match rule, install filter callback */
  appdata.system_bus = dbus_bus_get (DBUS_BUS_SYSTEM, NULL);
  if (appdata.system_bus) {
    dbus_bus_add_match (appdata.system_bus, MCE_SIGNAL_MATCH, NULL);
    dbus_connection_add_filter (appdata.system_bus,
                                (DBusHandleMessageFunction) mce_filter_func,
                                &appdata, NULL);
  }

Second action point is rotation directly. We have used Hildon API for this purpose. maemo-sliders/sliders.c

static DBusHandlerResult
mce_filter_func (DBusConnection * connection,
                 DBusMessage * message, AppData *data)
{
  /* ... */
      /* Rotate main window */
      if (!strcmp (rotation, MCE_ORIENTATION_PORTRAIT))
        hildon_gtk_window_set_portrait_flags (GTK_WINDOW(appdata.main_window), HILDON_PORTRAIT_MODE_REQUEST);
      else hildon_gtk_window_set_portrait_flags (GTK_WINDOW(appdata.main_window),  ~HILDON_PORTRAIT_MODE_REQUEST);
    }
  /* ... */
}

[edit] Enabling volume/zoom keys

This section explains how to enable use of the device's Volume up/down keys.

Since hildon-2.2.2 you can just use hildon_gtk_window_enable_zoom_keys:

void hildon_gtk_window_enable_zoom_keys(GtkWindow *window, gboolean enable);

Or if you are not using hildon you use the more complicated way:

First two needed libraries are included

#include <gdk/gdkx.h>
#include <X11/Xatom.h>

By default, maemo-status-volume daemon grabs keys for volume control so they need to be released

static void
ungrab_volume_keys()
{
    /* Tell maemo-status-volume daemon to ungrab keys */
    unsigned long val = 1; /* ungrab, use 0 to grab */
    Atom atom;
    GdkDisplay *display = NULL;
    display = gdk_drawable_get_display (appdata.main_window->window);
    atom = gdk_x11_get_xatom_by_name_for_display (display, "_HILDON_ZOOM_KEY_ATOM");
    XChangeProperty (GDK_DISPLAY_XDISPLAY (display),
                     GDK_WINDOW_XID (appdata.main_window->window), atom, XA_INTEGER, 32,
                     PropModeReplace, (unsigned char *) &val, 1);
}

[edit] Remapping volume keys

This section explains how to use device's Volume up/down keys to set application to fullsceen or unfullscreen mode.

Next event for key press is created. By returning TRUE when the desired key is pressed function tells to GTK+ that keypress was handled. All other keys return FALSE so application lets GTK+ decide what to do with them.

static gboolean
on_key_pressed(GtkWidget* widget, GdkEventKey* event)
{
  /* set application to fullscreen when volume up is pressed and vice versa */
  if (event->keyval == HILDON_HARDKEY_INCREASE){
    gtk_window_fullscreen(GTK_WINDOW(appdata.main_window));
    /* Tell that event was handled */
    return TRUE;
  }
 
  else if (event->keyval == HILDON_HARDKEY_DECREASE){
    gtk_window_unfullscreen(GTK_WINDOW(appdata.main_window));
    return TRUE;
  }
  /* tell that event wasn't handled */
  return FALSE;
}

Two more things to do, both inside main(). The key_press_event signal is connected to newly created callback function

g_signal_connect(G_OBJECT(appdata.main_window), "key_press_event",
                 G_CALLBACK(on_key_pressed),  NULL);

And Volume keys ungrabbing function is called before gtk_main()

>
ungrab_volume_keys();

[edit] Integration to Menu

Application integration in the application launcher menu needs a .desktop file. This section describes the needed additions and changes.

Sliders does not have an appropriate .desktop file, so we need to generate it from maemo-sliders.desktop.in with a configure script. maemo-sliders.desktop.in

[Desktop Entry]
Encoding=UTF-8
Version=@VERSION@
Type=Application
Name=Maemo Sliders
Exec=@prefix@/bin/maemo-sliders

[edit] Application Packaging

The dh_make tool has been used for Sliders Debianization.