Using the maemo games startup screen

This document has been reviewed for maemo 3.x.

This document is released under the GPL license.

Goal: To show how to use the maemo games startup screen in the game applications

Target audience: Maemo game developers

Introduction

The maemo-games-startup application is a common game startup interface which provides an easy way for the game to communicate with the platform. For example, the application allows the platform to control the game state (such as paused or running) and pause all games when a "Battery low" message is received.

Basic maemo game startup screen

Figure 1. Basic maemo-games-startup application screen

Application functionality

In order to execute the game, call the maemo-games-startup application. During the call, pass the game configuration file as an argument. Once loaded, maemo-games-startup creates a common interface for all games (see Figure 1) and, if needed, loads a specifc plugin for each game that is being executed.

When the Play button is pressed, the D-BUS service defined in the configuration file is executed. For games that do not use GTK+, the service must call a wrapper that executes the game (for more information about the wrapper, see Section Integrating non-GTK+ games).

In the case of GTK+ games, the service executes the game binary directly. The service is called every time communication between maemo-games-startup and the game is needed.

Basic maemo game startup screen with plugins

Figure 2. Maemo games startup screen with a simple plugin containing difficulty level and sound configuration

Activity diagram

The following diagram illustrates the operational execution flow of the maemo­games­startup application.

Activity Diagram

Figure 3. Execution flow of the maemo-games-startup application

Integration

The integration tasks depend on the toolkit the game is based on. The following sections describe the integration tasks for:

  • Games based on GTK+
  • Games based on other toolkits, such as SDL

Integrating GTK+ games

To integrate games based on GTK+, first create a configuration file that defines the information necessary for the maemo-games-startup application. An example configuration file is located in /usr/share/doc/maemo­games­startup­dev/examples/example.conf. The following example illustrates the contents of the configuration file:


[Startup Entry] 
# the name of the application 
Name=example 
# the current version of the application 
Version=0.2.0 
# the title that will be used if not specified by the GettextPackage 
Title=Example 
# the GettextPackage to be used to locate the title string 
GettextPackage=example 
# if the TitleId is defined, it will search for it inside  the gettext package 
TitleId=example_title 
# if your game has a plugin 
PluginPath=/usr/share/example/example_plugin.so 
# the games­startup screen image 
Image=/usr/share/example/pixmaps/example.png 
# the D-BUS service, path and interface names 
ServiceName=com.domain.example 
PathName=/com/domain/example 
InterfaceName=com.domain.example 

Secondly, integrate the game with libosso to enable it to receive and send D­BUS messages such as "pause", "restart" and "continue". In order to receive messages from the maemo-games-startup application, the game must register the service as defined in the configuration file.

When sending messages to maemo-games-startup, the game must use the service, path and interface name defined in the configuration file, but with a “_startup” prefix.

Thirdly, create a D-BUS service (a .service file) using the name of the service that executes the game binary, as defined in the configuration file. An example file is located in /usr/share/doc/maemo­games­startup­dev/examples/example.service. The following example illustrates the contents of the file:


[D­BUS Service] 
Name=com.domain.example 
Exec=/usr/games/wrapper/games/wrapper.l

Finally, the game must create a shell script that calls the maemo-games-startup application executable and passes the game configuration file as an argument. An example shell script is located in /usr/share/doc/maemo­games­startup­dev/examples/example.sh. The following example illustrates the contents of the script:


/usr/bin/maemo_games_startup ­­name example /usr/share/example/example.conf 

Integrating non-GTK+ games

This section uses a Game01 maemo game as an example to illustrate how simple it is to use the SDL API.

First, create a configuration file that defines the information necessary for the maemo-games-startup application. An example configuration file is located in /usr/share/doc/maemo­games­startup­dev/examples/example.conf. The following example illustrates the contents of the Game01 configuration file:


# Game01 config file 
[Startup Entry] 
Name=game01 
Version=@VERSION@ 
Title=Game01 
GettextPackage=game01 
Image=/usr/share/game01/pixmaps/games­startup­game01.png 
ServiceName=br.org.indt.game01 
PathName=/br/org/indt/game01 
InterfaceName=br.org.indt.game01 
	  	

Secondly, add support for the libshadowapp library to the game to enable it to receive and send D­BUS messages such as "pause", "restart" and "continue". Unfortunately, there is no documentation available for this library. However, you can see how it is used by looking at any maemo game source code available in the internet (look for sapp_* function calls).

Thirdly, create a game wrapper that is responsible for intermediating the communication between the game and the maemo-games-startup application. An example wrapper is located in /usr/share/doc/maemo­games­startup­dev/examples/example.c. The game wrapper also requires a configuration file in order to execute the game. An example configuration file is located in /usr/share/doc/games­startup/examples/example.game.

The information in the example.game file must be consistent with the information in the game's maemo-games-startup configuration file.

The following example illustrates the contents of the Game01 game wrapper:

		
#include <stdio.h> 
#include <unistd.h> 

char *argh[] = { "/usr/games/wrapper/wrapper", 
 "usr/games/wrapper/games/game01.game", 
 NULL }; 

int main(int argc, char *argv[]) 
{ 
  FILE *pFile; 
		  
  pFile = fopen ("/usr/games/.gamewrapper/game01.debug", "a"); 

  if (pFile) { 
	fprintf (pFile, "app launched\n"); 
	fclose (pFile); 
  } 

  if (execv ("/usr/games/wrapper/wrapper", argh) == ­1) { 
	pFile = fopen ("/tmp/.gamewrapper/debug/game01.debug", "a"); 
	if (pFile) { 
	  fprintf (pFile, "cannot execute wrapper\n"); 
	  fclose (pFile); 
	} 
  } 

  return 0; 

} 

The following example illustrates the contents of the configuration file that the wrapper requires in order to execute the Game01 game:


game01 
/usr/bin/game01 
br.org.indt.game01 
/br/org/indt/game01 
br.org.indt.game01 
0.2.5
		

Fourthly, create a D-BUS service using the name of the service that executes the wrapper, as defined in the configuration file. An example file is located in /usr/share/doc/maemo­games­startup­dev/examples/example.service. The following example illustrates the contents of the Game01 D-BUS service file:


[D­BUS Service] 
Name=br.org.indt.game01 
Exec=/usr/games/wrapper/games/game01.l 
		

Finally, the game must create a shell script that calls the maemo-games-startup application executable and passes the game configuration file as an argument.. The following example illustrates the contents of the Game01 script:


/usr/bin/maemo_games_startup ­­name game01 /usr/share/game01/game01.conf 
		

Creating a game-specific plugin

Each game can create a plugin for specific settings, such as sound control or difficulty level (see Figure 2). Basically, each plugin must implement some predefined functions that are executed by the maemo­games­startup application. For more information, see the /usr/share/doc/maemo­games­startup­dev/examples/plugin.c file.

The functions that can be implemented by each plugin are:

  • static GtkWidget *load_plugin(void)

    This function creates the game-specific plugin.



  • static void unload_plugin(void)

    This function destroys global variables, if necessary.



  • static void write_config(void)

    This function saves the game configuration chosen by the player using the plugin options.



If the game needs a submenu in the maemo­games­startup screen main menu (see Figure 1), the following functions must be used:

  • static GtkWidget **load_menu (guint *)

    This function creates the game-specific submenu that is added to the maemo­games­startup main menu.



  • static void update_menu (void)

    This function updates the game-specific menu.



The struct below must be filled and sent to the maemo­games­startup application. It specifies which plugin functions the startup must call.


static StartupPluginInfo plugin_info = { 
  GtkWidget *  (* load)         (void); 
  void         (* unload)       (void); 
  void         (* write_config) (const gchar *nick, gchar *ip); 
  GtkWidget ** (* load_menu)    (guint *); 
  void         (* update_menu)  (void); 
  void         (* plugin_cb)    (GtkWidget *menu_item, gpointer cb_data); 
}; 
		

The last item on the struct is necessary only if the game plugin requires items such as “save”, “save as” or "open" to be a submenu in the default Game submenu.

Each plugin must contain a reference to the maemo­games­startup info. The reference is given when STARTUP_INIT_PLUGIN is called. The following example illustrates a reference to maemo­games­startup:


static GameStartupInfo gs; 
		

The following example illustrates the STARTUP_INIT_PLUGIN that initialises the plugin. The parameters, in the order they are shown, are:

  • Pointer for plugin information (see the struct shown above)
  • GameStartupInfo
  • Definition on whether the maemo­games­startup menu has open/save game options

STARTUP_INIT_PLUGIN(StartupPluginInfo, GameStartupInfo, gboolean) 
		

In order to inform maemo­games­startup that the game has a plugin, the game's .conf configuration file must include the PluginPath entry, such asPluginPath=@datadir@/game01/game01_plugin.so.

Building an example plugin for Game01

This section illustrates the plugin for the Game01 implementation. The plugin allows the player to set the initial level of the game and to define whether the game uses sounds.

Game01 Screen

Figure 4. Maemo games startup screen with the Game01 plugin's level and sound configuration

The following code examples illustrate how the maemo games startup screen works, but the best way to learn to use it is to tinker with the game itself. Use the source code as you want: as a basic skeleton for your game, or simply to gain a better understanding of the game startup.

Since the plugin and maemo­games­startup are written with GTK+­2.0, they must include startup_plugin.h from maemo­games­startup. In addition, Gconf is used to save the user settings.


#include <stdio.h> 
#include <stdlib.h> 
#include <gtk/gtk.h> 
#include <startup_plugin.h> 
#include <gconf/gconf.h> 
#include <gconf/gconf­client.h>
		
#define MENU_SOUND     15 
			

The following example illustrates the labels for retrieving information at GConf:


#define SETTINGS_LEVEL "/apps/osso/game01/level" 
#define SETTINGS_SOUND "/apps/osso/game01/sound"
			

The following example illustrates the functions that are implemented:

			
static GtkWidget  *load_plugin           (void); 
static void        unload_plugin         (void); 
static void        write_config          (void); 
static GtkWidget **load_menu             (guint *); 
static void        update_menu           (void); 
static void        difficulty_callback   (GtkWidget   *widget, 	 gpointer data); 
static void        plugin_callback       (GtkWidget   *menu_item,  gpointer data);

The following example illustrates some global variables:


GConfClient *gcc = NULL; 
GtkWidget *board_box; 
GtkWidget *level_1_item; 
GtkWidget *level_2_item; 
GtkWidget *level_3_item; 
GtkWidget *level_4_item; 
GtkWidget *level_5_item; 
GtkWidget *level_6_item; 
GtkWidget *level_7_item; 
GtkWidget *level_8_item; 
GtkWidget *level_9_item; 
GtkWidget *level_10_item; 
GtkWidget *level_11_item; 
GtkWidget *level_12_item; 
GtkWidget *settings_item; 
GtkWidget *settings_menu; 
GtkWidget *difficulty_item; 
GtkWidget *difficulty_menu; 
static GameStartupInfo gs; 
GtkWidget *menu_items[2]; 
static int changed = FALSE; 
GSList * group = NULL; 
GtkWidget *sound_check = NULL; 
GtkWidget *sound_item; 
			

The implemented functions of the plugin must be sent to maemo­games­startup: in this case, a GTK_SPIN_BUTTON and a GTK_CHECK_ITEM. If the plugin has no specific menu, load_menu and update_menu must be NULL.


static StartupPluginInfo plugin_info = { 
	  load_plugin, 
	  unload_plugin, 
	  write_config, 
	  load_menu, 
	  update_menu, 
	  NULL 
}; 
			

The following example illustrates the initializing plugin that informs the application that there is a plugin:


     STARTUP_INIT_PLUGIN(plugin_info, gs, FALSE);
			

The following example illustrates the function that initialises the widgets that localise the maemo­games­startup standard buttons:


static GtkWidget * load_plugin (void) 
{ 
	  int board, enable_sound; 

	  GtkWidget *game_vbox,  *game_hbox; 
	  GtkWidget *board_hbox, *board_label; 
	  GtkWidget *sound_hbox, *sound_label; 

	  g_type_init(); 

	  gcc = gconf_client_get_default(); 

	  board = gconf_client_get_int(gcc, SETTINGS_LEVEL, NULL); 

	  enable_sound = gconf_client_get_int(gcc, SETTINGS_SOUND, NULL); 

	  game_vbox = gtk_vbox_new (TRUE, 0); 
	  game_hbox = gtk_hbox_new (TRUE, 0); 

	  g_assert (game_hbox); 
	  g_assert (game_vbox); 

	  board_hbox = gtk_hbox_new (FALSE, 4); 
			  
	  board_box = gtk_spin_button_new_with_range (1, 12, 1); 
	  g_assert (board_box); 
			  
	  gtk_spin_button_set_value (GTK_SPIN_BUTTON (board_box), board); 
	  gtk_spin_button_set_numeric(GTK_SPIN_BUTTON (board_box), TRUE); 
	  gtk_widget_set_size_request (board_box, 200, ­1); 

	  g_signal_connect(G_OBJECT(board_box), "changed", G_CALLBACK(settings_callback), NULL); 

	  board_label = gtk_label_new ("Starting level"); 
	  g_assert(board_label); 

	  sound_label = gtk_label_new ("Sound"); 
	  g_assert (sound_label); 

	  sound_check = gtk_check_button_new(); 
	  g_assert (sound_check); 

	  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(sound_check), enable_sound); 

	  gtk_box_pack_end (GTK_BOX (board_hbox), sound_check, FALSE, FALSE, 0); 
	  gtk_box_pack_end (GTK_BOX (board_hbox), sound_label, TRUE, TRUE, 0); 
	  gtk_box_pack_end (GTK_BOX (board_hbox), board_box, FALSE, FALSE, 0); 
	  gtk_box_pack_end (GTK_BOX (board_hbox), board_label, FALSE, FALSE, 0); 

	  gtk_box_pack_start (GTK_BOX (game_hbox),board_hbox, FALSE, FALSE, 2); 
	  gtk_box_pack_start (GTK_BOX (game_vbox), game_hbox, FALSE, FALSE, 2); 
	  
	  g_signal_connect (G_OBJECT(sound_check), "clicked", G_CALLBACK(sound_callback), NULL);  

	  return game_vbox; 
} 

The following example illustrates the function that frees global variables at the end of plugin execution:


	static void unload_plugin (void) 
	{ 
	  free(gcc);
	  free(board_box); 
	  ... 
	} 
			

The following example illustrates the function that is responsible for using Gconf to store the user preferences:


	static void write_config (void) 
	{ 
	  gconf_client_set_int(gcc, SETTINGS_LEVEL, 
	  gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(board_box)), NULL); 

	  gconf_client_set_int(gcc, SETTINGS_SOUND, 
	  gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sound_check)), NULL); 
	} 
			

The following example illustrates the function that initialises the game-specific plugin menu which is between the Game and Close submenus of maemo­games­startup:


	static GtkWidget **load_menu (guint *nitems) 
	{ 
		int enable_sound; 
		//number of entries in maemo­games­startup main menu for this game 
		*nitems = 1; 

		settings_item = gtk_menu_item_new_with_label ("Settings"); 
		settings_menu = gtk_menu_new ();    

		menu_items[0] = settings_item; 
		gtk_menu_item_set_submenu (GTK_MENU_ITEM (settings_item), settings_menu); 

		//difficulty settings 
		difficulty_menu = gtk_menu_new (); 
		difficulty_item = gtk_menu_item_new_with_label ("Difficulty"); 

		gtk_menu_item_set_submenu (GTK_MENU_ITEM (difficulty_item), difficulty_menu); 
		gtk_menu_append (GTK_MENU (settings_menu), difficulty_item); 

		level_1_item = gtk_radio_menu_item_new_with_label (group, "Level 1"); 
		gtk_menu_append (GTK_MENU (difficulty_menu), level_1_item); 
		group = gtk_radio_menu_item_group(GTK_RADIO_MENU_ITEM(level_1_item)); 

		level_2_item = gtk_radio_menu_item_new_with_label (group, "Level 2"); 
		gtk_menu_append (GTK_MENU (difficulty_menu), level_2_item); 
		group = gtk_radio_menu_item_group(GTK_RADIO_MENU_ITEM(level_2_item)); 

		level_3_item = gtk_radio_menu_item_new_with_label (group, "Level 3"); 
		gtk_menu_append (GTK_MENU (difficulty_menu), level_3_item); 
		group = gtk_radio_menu_item_group(GTK_RADIO_MENU_ITEM(level_3_item)); 

		. . . 

		level_12_item = gtk_radio_menu_item_new_with_label (group, "Level 12"); 
		gtk_menu_append (GTK_MENU (difficulty_menu), level_12_item); 

		group = gtk_radio_menu_item_group(GTK_RADIO_MENU_ITEM(level_12_item)); 
		g_signal_connect (G_OBJECT (level_1_item), "toggled", 
								G_CALLBACK (plugin_callback), 
								(gpointer) LEVEL_1); 
		
		g_signal_connect (G_OBJECT (level_2_item), "toggled", 
					G_CALLBACK (plugin_callback), 
					(gpointer) LEVEL_2); 
					
		g_signal_connect (G_OBJECT (level_3_item), "toggled", 
								G_CALLBACK (plugin_callback), 
								(gpointer) LEVEL_3); 
		... 
	  
		g_signal_connect (G_OBJECT (level_12_item), "toggled", 
								G_CALLBACK (plugin_callback), 
								(gpointer) LEVEL_12); 
		
		gtk_menu_append (GTK_MENU (settings_menu), gtk_menu_item_new()); 
		
	  //sound settings 

	  sound_item = gtk_check_menu_item_new_with_label("Sound"); 
	  gtk_menu_append (GTK_MENU (settings_menu), sound_item); 

	  g_signal_connect (G_OBJECT (sound_item), 
						"toggled", 
						G_CALLBACK (plugin_callback), 
						(gpointer) MENU_SOUND); 

	  gtk_check_menu_item_set_state (GTK_CHECK_MENU_ITEM(sound_item), 
	  gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sound_check))); 
		
	  return menu_items;    
	} 

The following example illustrates the function that is called when any configuration is done in the menu. This function updates the GTK_SPIN_BUTTON (level change) or the GTK_CHECK_MENU_ITEM (sound change).


	static void plugin_callback (GtkWidget *menu_item, gpointer  data) 
	{ 
		if (MENU_SOUND == (int) data && !changed){ 
			changed = TRUE; 
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sound_check), 
								  gtk_check_menu_item_get_active ( 
								  GTK_CHECK_MENU_ITEM(sound_item))); 
		} else if (!changed) { 
			changed = TRUE; 
			gtk_spin_button_set_value (GTK_SPIN_BUTTON (board_box), (int) data); 
		} 
		changed = FALSE; 
	} 
			

The following example illustrates the function that is called to update the menu level option when the user chooses the level using the GTK_SPIN_BUTTON:


	static void settings_callback (GtkWidget *widget, gpointer   data) 
	{ 
	  if (!changed) { 
		  changed = TRUE; 
		  gint active = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget)); 
		  if (active == LEVEL_1) { 
			gtk_check_menu_item_set_state(GTK_CHECK_MENU_ITEM(level_1_item), TRUE); 
		  } 
		  else if (active == LEVEL_2) { 
			gtk_check_menu_item_set_state(GTK_CHECK_MENU_ITEM(level_2_item), TRUE); 
		  } 
		  else if (active == LEVEL_3) { 
			gtk_check_menu_item_set_state(GTK_CHECK_MENU_ITEM(level_3_item), TRUE); 
		  } 
		... 
	  else if (active == LEVEL_12) { 
			gtk_check_menu_item_set_state(GTK_CHECK_MENU_ITEM(level_12_item), TRUE); 
		  } 
	  } 
	  changed = FALSE; 
	} 
			

The following example illustrates the function that is called by maemo­games­startup, if necessary.


static void update_menu (void) 
{ 
  settings_callback(board_box, NULL); 
  sound_callback(sound_check, NULL); 
} 
			

References

SDL Library



Improve this page