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.
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.
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 maemogamesstartup application.
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/maemogamesstartupdev/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 gamesstartup 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 DBUS 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/maemogamesstartupdev/examples/example.service
.
The following example illustrates the contents of the file:
[DBUS 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/maemogamesstartupdev/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/maemogamesstartupdev/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/gamesstartupgame01.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 DBUS 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/maemogamesstartupdev/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/gamesstartup/examples/example.game
.
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/maemogamesstartupdev/examples/example.service
.
The following example illustrates the contents of the Game01 D-BUS
service file:
[DBUS 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
maemogamesstartup application. For more information, see
the
/usr/share/doc/maemogamesstartupdev/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 maemogamesstartup 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 maemogamesstartup main menu.static void update_menu (void)
This function updates the game-specific menu.
The struct below must be filled and sent to the maemogamesstartup 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
maemogamesstartup info. The reference is given when
STARTUP_INIT_PLUGIN
is called. The following example
illustrates a reference to maemogamesstartup:
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 maemogamesstartup menu has open/save game options
STARTUP_INIT_PLUGIN(StartupPluginInfo, GameStartupInfo, gboolean)
In order to inform maemogamesstartup 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.
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 maemogamesstartup are written with
GTK+2.0, they must include startup_plugin.h
from
maemogamesstartup. 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/gconfclient.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
maemogamesstartup: 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 maemogamesstartup 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 maemogamesstartup:
static GtkWidget **load_menu (guint *nitems) { int enable_sound; //number of entries in maemogamesstartup 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 maemogamesstartup, if necessary.
static void update_menu (void) { settings_callback(board_box, NULL); sound_callback(sound_check, NULL); }
References
Improve this page