Documentation/Maemo 5 Developer Guide/Using Generic Platform Components/Application Preferences-Gconf
GConf is used by the GNOME desktop environment for storing shared configuration settings for the desktop and applications. The daemon process GConfd follows the changes in the database. When a change occurs in the database, the daemon applies the new settings to the applications using them. For example, the control panel application uses GConf.
If settings are used only by a single application, GLib utility for .ini style files should be used instead. Applications should naturally have working default settings. Settings should be saved only when the user changes them.
This chapter uses the following examples:
Contents |
[edit] GConf Basics
GConf is a system for GNOME applications to store settings into a database system in a centralized manner. The aim of the GConf library is to provide applications a consistent view of the database access functions, as well as to provide tools for system administrators to enable them to distribute software settings in a centralized manner (across multiple computers).
The GConf "database" may consist of multiple databases (configured by the system administrator), but normally there will be at least one database engine that uses XML to store settings. This keeps the database still in a human readable form (as opposed to binary), and allows some consistency checks via schema verifications.
The interface for the client (program that uses GConf to store its settings) is always the same, irrespective of the database back-end (the client does not see this).
What makes GConf interesting, is its capability of notifying running clients that their settings have been changed by some other process than themselves. This allows for the clients to react soon (not quite real-time), and this leads to a situation where a user will change, for example, the GNOME HTTP proxy settings, and clients that are interested in that setting will get a notification (via a callback function) that the setting has changed. The clients will then read the new setting and modify their data structures to take the new setting into account.
[edit] Using GConf
The GConf model consists of two parts: the GConf client library (which will be used here) and the GConf server daemon that is the guardian and reader/writer of the back-end databases. In a regular GNOME environment, the client communicates with the server either by using the Bonobo library (lightweight object IPC mechanism) or D-Bus.
As Bonobo is not used in Maemo (it is quite heavy, even if lightweight), the client will communicate with the server using D-Bus. This also allows the daemon to be started on demand, when there is at least one client wanting to use that service (this is a feature of D-Bus). The communication mechanism is encapsulated by the GConf client library, and as such, will be transparent.
In order to read or write the preference database, it is necessary to decide on the key to use to access the application values. The database namespace is hierarchical, and uses the '/'-character to implement this hierarchy, starting from a root location similar to UNIX file system namespace.
Each application will use its own "directory" under /apps/Maemo/appname/
. N.B. Even when the word "directory" is seen in connection to GConf, one has to be careful to distinguish real directories from preference namespaces inside the GConf namespace. The /apps/Maemo/appname/
above is in the GConf namespace, so there will not actually be a physical directory called /apps/
on a system.
The keys should be named according to the platform guidelines. The current guideline is that each application should store its configuration keys under /apps/Maemo/appname/
, where appname
is the name of the application. There is no central registry on the names in use currently, so names should be selected carefully. Key names should all be lowercase, with underscore used to separate multiple words. Also, ASCII should be used, since GConf does not support localization for key names (it does for key values, but that is not covered in this material).
GConf values are typed, which means that it is necessary to select the type for the data that the key is supposed to hold.
The following types are supported for values in GConf:
-
gint
(32-bit signed) -
gboolean
-
gchar
(ASCII/ISO 8859-1/UTF-8 C string) -
gfloat
(with the limitation that the resolution is not guaranteed nor specified by GConf because of portability issues) - a list of values of one type
- a pair of values, each having their own type (useful for storing "mapping" data)
What is missing from the above list is storing binary data (for a good reason). The type system is also fairly limited. This is on purpose, so that complex configurations (like the Apache HTTP daemon uses, or Samba) are not attempted using GConf.
There is a diagnostic and administration tool called gconftool-2
that is also available in the SDK. It can be used to set and unset keys, as well as display their current contents.
Some examples of using gconftool-2 (on the SDK):
- Displaying the contents of all keys stored under
/apps/
(listing cut for brevity)
[sbox-DIABLO_X86: ~] > run-standalone.sh gconftool-2 -R /apps /apps/osso: /apps/osso/inputmethod: launch_finger_kb_on_select = true input_method_plugin = himExample_vkb available_languages = [en_GB] use_finger_kb = true /apps/osso/inputmethod/hildon-im-languages: language-0 = en_GB current = 0 language-1 = list = [] /apps/osso/fontconfig: font_scaling_factor = Schema (type: `float' list_type: '*invalid*' car_type: '*invalid*' cdr_type: '*invalid*' locale: `C') /apps/osso/apps: /apps/osso/apps/controlpanel: groups = [copa_ia_general,copa_ia_connectivity, copa_ia_personalisation] icon_size = false group_ids = [general,connectivity,personalisation] /apps/osso/osso: /apps/osso/osso/thumbnailers: /apps/osso/osso/thumbnailers/audio@x-mp3: command = /usr/bin/hildon-thumb-libid3 /apps/osso/osso/thumbnailers/audio@x-m4a: command = /usr/bin/hildon-thumb-libid3 /apps/osso/osso/thumbnailers/audio@mp3: command = /usr/bin/hildon-thumb-libid3 /apps/osso/osso/thumbnailers/audio@x-mp2: command = /usr/bin/hildon-thumb-libid3
- Creating and setting the value to a new key.
[sbox-DIABLO_X86: ~] > run-standalone.sh gconftool-2 \ --set /apps/Maemo/testing/testkey --type=int 5
- Listing all keys under the namespace
/apps/Maemo/testing
.
[sbox-DIABLO_X86: ~] > run-standalone.sh gconftool-2 \ -R /apps/Maemo/testing testkey = 5
- Removing the last key will also remove the key directory.
[sbox-DIABLO_X86: ~] > run-standalone.sh gconftool-2 \ --unset /apps/Maemo/testing/testkey [sbox-DIABLO_X86: ~] > run-standalone.sh gconftool-2 \ -R /apps/Maemo/testing
- Removing whole key hierarchies is also possible.
[sbox-DIABLO_X86: ~] > run-standalone.sh gconftool-2 \ --recursive-unset /apps/Maemo/testing
For more detailed information, please see Gconf API documentation.
[edit] Using GConf to read and write preferences
The example is required to:
- Store the color that the user selects when the color button (in the toolbar) is used.
- Load the color preference on application startup.
Even if GConf concepts seem to be logical, it can be seen that using GConf will require one to learn some new things (e.g. the GError
-object). Since the GConf client code is in its own library, the relevant compiler flags and library options need to be added again. The pkg-config package name is gconf-2.0
hildon_helloworld-9.c
/** * hildon_helloworld-9.c * * This maemo code example is licensed under a MIT-style license, * that can be found in the file called "License" in the same * directory as this file. * Copyright (c) 2007-2008 Nokia Corporation. All rights reserved. * * We'll store the color that the user selects into a GConf * preference. In fact, we'll have three settings, one for each * channel of the color (red, green and blue). * * Look for lines with "NEW" or "MODIFIED" in them. */ #include <stdlib.h> #include <hildon/hildon-program.h> #include <hildon/hildon-color-button.h> #include <hildon/hildon-find-toolbar.h> #include <hildon/hildon-file-chooser-dialog.h> #include <hildon/hildon-banner.h> #include <libgnomevfs/gnome-vfs.h> /* Include the prototypes for GConf client functions (NEW). */ #include <gconf/gconf-client.h> /* The application name -part of the GConf namespace (NEW). */ #define APP_NAME "hildon_hello" /* This will be the root "directory" for our preferences (NEW). */ #define GC_ROOT "/apps/Maemo/" APP_NAME "/" /*... Listing cut for brevity ...*/ /** * NEW * * Utility function to store the given color into our application * preferences. We could use a list of integers as well, but we'll * settle for three separate properties; one for each of RGB * channels. * * The config keys that will be used are 'red', 'green' and 'blue'. * * NOTE: * We're doing things very non-optimally. If our application would * have multiple preference settings, and we would like to know * when someone will change them (external program, another * instance of our program, etc), we'd have to keep a reference to * the GConf client connection. Listening for changes in * preferences would also require a callback registration, but this * is covered in the "maemo Platform Development" material. */ static void confStoreColor(const GdkColor* color) { /* We'll store the pointer to the GConf connection here. */ GConfClient* gcClient = NULL; /* Make sure that no NULLs are passed for the color. GdkColor is not a proper GObject, so there is no GDK_IS_COLOR macro. */ g_assert(color); g_print("confStoreColor: invoked\n"); /* Open a connection to gconfd-2 (via D-Bus in maemo). The GConf API doesn't say whether this function can ever return NULL or how it will behave in error conditions. */ gcClient = gconf_client_get_default(); /* We make sure that it's a valid GConf-client object. */ g_assert(GCONF_IS_CLIENT(gcClient)); /* Store the values. */ if (!gconf_client_set_int(gcClient, GC_ROOT "red", color->red, NULL)) { g_warning(" failed to set %s/red to %d\n", GC_ROOT, color->red); } if (!gconf_client_set_int(gcClient, GC_ROOT "green", color->green, NULL)) { g_warning(" failed to set %s/green to %d\n", GC_ROOT, color->green); } if (!gconf_client_set_int(gcClient, GC_ROOT "blue", color->blue, NULL)) { g_warning(" failed to set %s/blue to %d\n", GC_ROOT, color->blue); } /* Release the GConf client object (with GObject-unref). */ g_object_unref(gcClient); gcClient = NULL; } /** * NEW * * A utility function to get an integer but also return the status * whether the requested key existed or not. * * NOTE: * It's also possible to use gconf_client_get_int(), but it's not * possible to then know whether they key existed or not, because * the function will return 0 if the key doesn't exist (and if the * value is 0, how could you tell these two conditions apart?). * * Parameters: * - GConfClient: the client object to use * - const gchar*: the key * - gint*: the address to store the integer to if the key exists * * Returns: * - TRUE: if integer has been updated with a value from GConf. * FALSE: there was no such key or it wasn't an integer. */ static gboolean confGetInt(GConfClient* gcClient, const gchar* key, gint* number) { /* This will hold the type/value pair at some point. */ GConfValue* val = NULL; /* Return flag (tells the caller whether this function wrote behind the 'number' pointer or not). */ gboolean hasChanged = FALSE; /* Try to get the type/value from the GConf DB. NOTE: We're using a version of the getter that will not return any defaults (if a schema would specify one). Instead, it will return the value if one has been set (or NULL). We're not really interested in errors as this will return a NULL in case of missing keys or errors and that is quite enough for us. */ val = gconf_client_get_without_default(gcClient, key, NULL); if (val == NULL) { /* Key wasn't found, no need to touch anything. */ g_warning("confGetInt: key %s not found\n", key); return FALSE; } /* Check whether the value stored behind the key is an integer. If it is not, we issue a warning, but return normally. */ if (val->type == GCONF_VALUE_INT) { /* It's an integer, get it and store. */ *number = gconf_value_get_int(val); /* Mark that we've changed the integer behind 'number'. */ hasChanged = TRUE; } else { g_warning("confGetInt: key %s is not an integer\n", key); } /* Free the type/value-pair. */ gconf_value_free(val); val = NULL; return hasChanged; } /** * NEW * * Utility function to change the given color into the one that is * specified in application preferences. * * If some key is missing, that channel is left untouched. The * function also checks for proper values for the channels so that * invalid values are not accepted (guint16 range of GdkColor). * * Parameters: * - GdkColor*: the color structure to modify if changed from prefs. * * Returns: * - TRUE if the color was been changed by this routine. * FALSE if the color wasn't changed (there was an error or the * color was already exactly the same as in the preferences). */ static gboolean confLoadCurrentColor(GdkColor* color) { GConfClient* gcClient = NULL; /* Temporary holders for the pref values. */ gint red = -1; gint green = -1; gint blue = -1; /* Temp variable to hold whether the color has changed. */ gboolean hasChanged = FALSE; g_assert(color); g_print("confLoadCurrentColor: invoked\n"); /* Open a connection to gconfd-2 (via d-bus). */ gcClient = gconf_client_get_default(); /* Make sure that it's a valid GConf-client object. */ g_assert(GCONF_IS_CLIENT(gcClient)); if (confGetInt(gcClient, GC_ROOT "red", &red)) { /* We got the value successfully, now clamp it. */ g_print(" got red = %d, ", red); /* We got a value, so let's limit it between 0 and 65535 (the legal range for guint16). We use the CLAMP macro from GLib for this. */ red = CLAMP(red, 0, G_MAXUINT16); g_print("after clamping = %d\n", red); /* Update & mark that at least this component changed. */ color->red = (guint16)red; hasChanged = TRUE; } /* Repeat the same logic for the green component. */ if (confGetInt(gcClient, GC_ROOT "green", &green)) { g_print(" got green = %d, ", green); green = CLAMP(green, 0, G_MAXUINT16); g_print("after clamping = %d\n", green); color->green = (guint16)green; hasChanged = TRUE; } /* Repeat the same logic for the last component (blue). */ if (confGetInt(gcClient, GC_ROOT "blue", &blue)) { g_print(" got blue = %d, ", blue); blue = CLAMP(blue, 0, G_MAXUINT16); g_print("after clamping = %d\n", blue); color->blue = (guint16)blue; hasChanged = TRUE; } /* Release the client object (with GObject-unref). */ g_object_unref(gcClient); gcClient = NULL; /* Return status if the color was been changed by this routine. */ return hasChanged; } /** * MODIFIED * * Invoked when the user selects a color (or will cancel the dialog). * * Will also write the color to preferences (GConf) each time the * color changes. We'll compare whether it has really changed (to * avoid writing to GConf is nothing really changed). */ static void cbActionColorChanged(HildonColorButton* colorButton, ApplicationState* app) { /* Local variables that we'll need to handle the change (NEW). */ gboolean hasChanged = FALSE; GdkColor newColor = {}; GdkColor* curColor = NULL; g_assert(app != NULL); g_print("cbActionColorChanged invoked\n"); /* Retrieve the new color from the color button (NEW). */ hildon_color_button_get_color(colorButton, &newColor); /* Just an alias to save some typing (could also use app->currentColor) (NEW). */ curColor = &app->currentColor; /* Check whether the color really changed (NEW). */ if ((newColor.red != curColor->red) || (newColor.green != curColor->green) || (newColor.blue != curColor->blue)) { hasChanged = TRUE; } if (!hasChanged) { g_print(" color not really changed\n"); return; } /* Color really changed, store to preferences (NEW). */ g_print(" color changed, storing into preferences.. \n"); confStoreColor(&newColor); g_print(" done.\n"); /* Update the changed color into the application state. */ app->currentColor = newColor; } /*... Listing cut for brevity ...*/ /** * MODIFIED * * The color of the color button will be loaded from the application * preferences (or keep the default if preferences have no setting). */ static GtkWidget* buildToolbar(ApplicationState* app) { GtkToolbar* toolbar = NULL; GtkToolItem* tbOpen = NULL; GtkToolItem* tbSave = NULL; GtkToolItem* tbSep = NULL; GtkToolItem* tbFind = NULL; GtkToolItem* tbColorButton = NULL; GtkWidget* colorButton = NULL; g_assert(app != NULL); tbOpen = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN); tbSave = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE); tbSep = gtk_separator_tool_item_new(); tbFind = gtk_tool_button_new_from_stock(GTK_STOCK_FIND); tbColorButton = gtk_tool_item_new(); colorButton = hildon_color_button_new(); /* Copy the color from the color button into the application state. This is done to detect whether the color in preferences matches the default color or not (NEW). */ hildon_color_button_get_color(HILDON_COLOR_BUTTON(colorButton), &app->currentColor); /* Load preferences and change the color if necessary. */ g_print("buildToolbar: loading color pref.\n"); if (confLoadCurrentColor(&app->currentColor)) { g_print(" color not same as default one\n"); hildon_color_button_set_color(HILDON_COLOR_BUTTON(colorButton), &app->currentColor); } else { g_print(" loaded color same as default\n"); } gtk_container_add(GTK_CONTAINER(tbColorButton), colorButton); /*... Listing cut for brevity ...*/ }
Since the graphical appearance of the program does not change (except that the ColorButton
will display the correct initial color), a look will be taken at the stdout display of the program.
[sbox-DIABLO_X86: ~/appdev] > run-standalone.sh ./hildon_helloworld-9 buildToolbar: loading color pref. confLoadCurrentColor: invoked hildon_helloworld-9[19840]: GLIB WARNING ** default - confGetInt: key /apps/Maemo/hildon_hello/red not found hildon_helloworld-9[19840]: GLIB WARNING ** default - confGetInt: key /apps/Maemo/hildon_hello/green not found hildon_helloworld-9[19840]: GLIB WARNING ** default - confGetInt: key /apps/Maemo/hildon_hello/blue not found loaded color same as default main: calling gtk_main cbActionMainToolbarToggle invoked cbActionColorChanged invoked color changed, storing into preferences.. confStoreColor: invoked done. main: returned from gtk_main and exiting with success
When running the program for the first time, warnings about the missing keys can be expected (since the values were not present in GConf).
Run the program again and exit:
[sbox-DIABLO_X86: ~/appdev] > run-standalone.sh ./hildon_helloworld-9 buildToolbar: loading color pref. confLoadCurrentColor: invoked got red = 65535, after clamping = 65535 got green = 65535, after clamping = 65535 got blue = 0, after clamping = 0 color not same as default one main: calling gtk_main main: returned from gtk_main and exiting with success
The next step is to remove one key (red), and run the program again (this is to test and verify that the logic works):
[sbox-DIABLO_X86: ~/appdev] > run-standalone.sh gconftool-2 \ --unset /apps/Maemo/hildon_hello/red [sbox-DIABLO_X86: ~/appdev] > run-standalone.sh gconftool-2 \ -R /apps/Maemo/hildon_hello green = 65535 blue = 0 [sbox-DIABLO_X86: ~/appdev] > run-standalone.sh ./hildon_helloworld-9 buildToolbar: loading color pref. confLoadCurrentColor: invoked hildon_helloworld-9[19924]: GLIB WARNING ** default - confGetInt: key /apps/Maemo/hildon_hello/red not found got green = 65535, after clamping = 65535 got blue = 0, after clamping = 0 color not same as default one main: calling gtk_main main: returned from gtk_main and exiting with success
[edit] Asynchronous GConf
[edit] Listening to changes in GConf
Now it is time to see how to extend GConf to be more suitable in asynchronous work, and especially when implementing services.
When the configuration needs for the service are simple, and reacting to configuration changes in "realtime" is desired, it is advisable to use GConf. Also, people tend to use GConf when they are too lazy to write their own configuration file parsers (although there is a simple one in GLib), or too lazy to write the GUI part to change the settings. This example program will simulate the first case, and react to changes in a subset of GConf configuration name space when the changes happen.
The application will be interested in two string values; one to set the device to use for communication (connection), and the other to set the communication parameters for the device (connectionparams). Since this example will be concentrating on just the change notifications, the program logic is simplified by omitting the proper set-up code in the program. This means that it is necessary to set up some values to the GConf keys prior to running the program. For this, gconftool-2
will be used, and a target has been prepared in the Makefile just for this (see section GNU Make and Makefiles if necessary): gconf-listener/Makefile
# Define a variable for this so that the GConf root may be changed gconf_root := /apps/Maemo/platdev_ex # ... Listing cut for brevity ... # This will setup the keys into default values. # It will first do a clear to remove any existing keys. primekeys: clearkeys gconftool-2 -set -type string \ $(gconf_root)/connection btcomm0 gconftool-2 -set -type string \ $(gconf_root)/connectionparams 9600,8,N,1 # Remove all application keys clearkeys: @gconftool-2 -recursive-unset $(gconf_root) # Dump all application keys dumpkeys: @echo Keys under $(gconf_root): @gconftool-2 -recursive-list $(gconf_root)
The next step is to prepare the keyspace by running the primekeys
target, and to verify that it succeeds by running the dumpkeys
target:
[sbox-DIABLO_X86: ~/gconf-listener] > make primekeys gconftool-2 --set --type string \ /apps/Maemo/platdev_ex/connection btcomm0 gconftool-2 --set --type string \ /apps/Maemo/platdev_ex/connectionparams 9600,8,N,1 [sbox-DIABLO_X86: ~/gconf-listener] > make dumpkeys Keys under /apps/Maemo/platdev_ex: connectionparams = 9600,8,N,1 connection = btcomm0
[edit] Implementing Notifications on Changes in GConf
The first step here is to take care of the necessary header information. The GConf namespace settings have been all implemented using cpp macros, so that one can easily change the prefix of the name space if required later on. gconf-listener/gconf-key-watch.c
#include <glib.h> #include <gconf/gconf-client.h> #include <string.h> /* strcmp */ /* As per maemo Coding Style and Guidelines document, we use the /apps/Maemo/ -prefix. NOTE: There is no central registry (as of this moment) that you could check that your application name doesn't collide with other application names, so caution is advised! */ #define SERVICE_GCONF_ROOT "/apps/Maemo/platdev_ex" /* We define the names of the keys symbolically so that we may change them later if necessary, and so that the GConf "root directory" for our application will be automatically prefixed to the paths. */ #define SERVICE_KEY_CONNECTION \ SERVICE_GCONF_ROOT "/connection" #define SERVICE_KEY_CONNECTIONPARAMS \ SERVICE_GCONF_ROOT "/connectionparams"
The main starts innocently enough, by creating a GConf client object (that encapsulates the connection to the GConf daemon), and then displays the two values on output: gconf-listener/gconf-key-watch.c
int main (int argc, char** argv) { /* Will hold reference to the GConfClient object. */ GConfClient* client = NULL; /* Initialize this to NULL so that we'll know whether an error occurred or not (and we don't have an existing GError object anyway at this point). */ GError* error = NULL; /* This will hold a reference to the mainloop object. */ GMainLoop* mainloop = NULL; g_print(PROGNAME ":main Starting.\n"); /* Must be called to initialize GType system. The API reference for gconf_client_get_default() insists. NOTE: Using gconf_init() is deprecated! */ g_type_init(); /* Create a new mainloop structure that we'll use. Use default context (NULL) and set the 'running' flag to FALSE. */ mainloop = g_main_loop_new(NULL, FALSE); if (mainloop == NULL) { g_error(PROGNAME ": Failed to create mainloop!\n"); } /* Create a new GConfClient object using the default settings. */ client = gconf_client_get_default(); if (client == NULL) { g_error(PROGNAME ": Failed to create GConfClient!\n"); } g_print(PROGNAME ":main GType and GConfClient initialized.\n"); /* Display the starting values for the two keys. */ dispStringKey(client, SERVICE_KEY_CONNECTION); dispStringKey(client, SERVICE_KEY_CONNECTIONPARAMS);
The dispStringKey
utility is rather simple as well, building on the GConf material that was covered in the previous section: gconf-listener/gconf-key-watch.c
/** * Utility to retrieve a string key and display it. * (Just as a small refresher on the API.) */ static void dispStringKey(GConfClient* client, const gchar* keyname) { /* This will hold the string value of the key. It will be dynamically allocated for us, so we need to release it ourselves when done (before returning). */ gchar* valueStr = NULL; /* We're not interested in the errors themselves (the last parameter), but the function will return NULL if there is one, so we just end in that case. */ valueStr = gconf_client_get_string(client, keyname, NULL); if (valueStr == NULL) { g_error(PROGNAME ": No string value for %s. Quitting\n", keyname); /* Application terminates. */ } g_print(PROGNAME ": Value for key '%s' is set to '%s'\n", keyname, valueStr); /* Normally one would want to use the value for something beyond just displaying it, but since this code doesn't, we release the allocated value string. */ g_free(valueStr); }
Next, the GConf client is told to attach itself to a specific name space part that this example is going to operate with: gconf-listener/gconf-key-watch.c
/** * Register directory to watch for changes. This will then tell * GConf to watch for changes in this namespace, and cause the * "value-changed"-signal to be emitted. We won't be using that * mechanism, but will opt to a more modern (and potentially more * scalable solution). The directory needs to be added to the * watch list in either case. * * When adding directories, you can sometimes optimize your program * performance by asking GConfClient to preload some (or all) keys * under a specific directory. This is done via the preload_type * parameter (we use GCONF_CLIENT_PRELOAD_NONE below). Since our * program will only listen for changes, we don't want to use extra * memory to keep the keys cached. * * Parameters: * - client: GConf-client object * - SERVICEPATH: the name of the GConf namespace to follow * - GCONF_CLIENT_PRELOAD_NONE: do not preload any of contents * - error: where to store the pointer to allocated GError on * errors. */ gconf_client_add_dir(client, SERVICE_GCONF_ROOT, GCONF_CLIENT_PRELOAD_NONE, &error); if (error != NULL) { g_error(PROGNAME ": Failed to add a watch to GCClient: %s\n", error->message); /* Normally we'd also release the allocated GError, but since this program will terminate on g_error, we won't do that. Hence the next line is commented. */ /* g_error_free(error); */ /* When you want to release the error if it has been allocated, or just continue if not, use g_clear_error(&error); */ } g_print(PROGNAME ":main Added " SERVICE_GCONF_ROOT ".\n");
Proceeding with the callback function registration, we have: gconf-listener/gconf-key-watch.c
/* Register our interest (in the form of a callback function) for any changes under the namespace that we just added. Parameters: - client: GConfClient object. - SERVICEPATH: namespace under which we can get notified for changes. - gconf_notify_func: callback that will be called on changes. - NULL: user-data pointer (not used here). - NULL: function to call on user-data when notify is removed or GConfClient destroyed. NULL for none (since we don't have user-data anyway). - error: return location for an allocated GError. Returns: guint: an ID for this notification so that we could remove it later with gconf_client_notify_remove(). We're not going to use it so we don't store it anywhere. */ gconf_client_notify_add(client, SERVICE_GCONF_ROOT, keyChangeCallback, NULL, NULL, &error); if (error != NULL) { g_error(PROGNAME ": Failed to add register the callback: %s\n", error->message); /* Program terminates. */ } g_print(PROGNAME ":main CB registered & starting main loop\n");
When dealing with regular desktop software, one could use multiple callback functions; one for each key to track. However, this would require implementing multiple callback functions, and this runs a risk of enlarging the size of the code. For this reason, the example code uses one callback function, which will internally multiplex between the two keys (by using strcmp): gconf-listener/gconf-key-watch.c
/** * Callback called when a key in watched directory changes. * Prototype for the callback must be compatible with * GConfClientNotifyFunc (for ref). * * It will find out which key changed (using strcmp, since the same * callback is used to track both keys) and the display the new value * of the key. * * The changed key/value pair will be communicated in the entry * parameter. userData will be NULL (can be set on notify_add [in * main]). Normally the application state would be carried within the * userData parameter, so that this callback could then modify the * view based on the change. Since this program does not have a state, * there is little that we can do within the function (it will abort * the program on errors though). */ static void keyChangeCallback(GConfClient* client, guint cnxn_id, GConfEntry* entry, gpointer userData) { /* This will hold the pointer to the value. */ const GConfValue* value = NULL; /* This will hold a pointer to the name of the key that changed. */ const gchar* keyname = NULL; /* This will hold a dynamically allocated human-readable representation of the changed value. */ gchar* strValue = NULL; g_print(PROGNAME ": keyChangeCallback invoked.\n"); /* Get a pointer to the key (this is not a copy). */ keyname = gconf_entry_get_key(entry); /* It will be quite fatal if after change we cannot retrieve even the name for the gconf entry, so we error out here. */ if (keyname == NULL) { g_error(PROGNAME ": Couldn't get the key name!\n"); /* Application terminates. */ } /* Get a pointer to the value from changed entry. */ value = gconf_entry_get_value(entry); /* If we get a NULL as the value, it means that the value either has not been set, or is at default. As a precaution we assume that this cannot ever happen, and will abort if it does. NOTE: A real program should be more resilient in this case, but the problem is: what is the correct action in this case? This is not always simple to decide. NOTE: You can trip this assert with 'make primekeys', since that will first remove all the keys (which causes the CB to be invoked, and abort here). */ g_assert(value != NULL); /* Check that it looks like a valid type for the value. */ if (!GCONF_VALUE_TYPE_VALID(value->type)) { g_error(PROGNAME ": Invalid type for gconfvalue!\n"); } /* Create a human readable representation of the value. Since this will be a new string created just for us, we'll need to be careful and free it later. */ strValue = gconf_value_to_string(value); /* Print out a message (depending on which of the tracked keys change. */ if (strcmp(keyname, SERVICE_KEY_CONNECTION) == 0) { g_print(PROGNAME ": Connection type setting changed: [%s]\n", strValue); } else if (strcmp(keyname, SERVICE_KEY_CONNECTIONPARAMS) == 0) { g_print(PROGNAME ": Connection params setting changed: [%s]\n", strValue); } else { g_print(PROGNAME ":Unknown key: %s (value: [%s])\n", keyname, strValue); } /* Free the string representation of the value. */ g_free(strValue); g_print(PROGNAME ": keyChangeCallback done.\n"); }
The complications in the above code rise from the fact that GConf communicates values using a GValue
structure, which can carry values of any simple type. Since GConf (or the user for that matter) cannot be completely trusted to return the correct type for the value, it is necessary to be extra careful, and not assume that it will always be a string. GConf also supports "default" values, which are communicated to the application using NULL
s, so it is also necessary to guard against that. Especially since the application does not provide a schema for the configuration space that would contain the default values.
The next step is to build and test the program. The program will be started on the background, so that gconftool-2
can be used to see how the program reacts to changing parameters:
[sbox-DIABLO_X86: ~/gconf-listener] > make cc -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/gconf/2 \ -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include -Wall -g \ -DPROGNAME=\"gconf-key-watch\" gconf-key-watch.c -o gconf-key-watch \ -lgconf-2 -ldbus-glib-1 -ldbus-1 -lgobject-2.0 -lglib-2.0 [sbox-DIABLO_X86: ~/gconf-listener] > run-standalone.sh ./gconf-key-watch & [2] 21385 gconf-key-watch:main Starting. gconf-key-watch:main GType and GConfClient initialized. gconf-key-watch: Value for key '/apps/Maemo/platdev_ex/connection' is set to 'btcomm0' gconf-key-watch: Value for key '/apps/Maemo/platdev_ex/connectionparams' is set to '9600,8,N,1' gconf-key-watch:main Added /apps/Maemo/platdev_ex. gconf-key-watch:main CB registered & starting main loop [sbox-DIABLO_X86: ~/gconf-listener] > gconftool-2 --set --type string \ /apps/Maemo/platdev_ex/connection ttyS0 gconf-key-watch: keyChangeCallback invoked. gconf-key-watch: Connection type setting changed: [ttyS0] gconf-key-watch: keyChangeCallback done. [sbox-DIABLO_X86: ~/gconf-listener] > gconftool-2 --set --type string \ /apps/Maemo/platdev_ex/connectionparams '' gconf-key-watch: keyChangeCallback invoked. gconf-key-watch: Connection params setting changed: [] gconf-key-watch: keyChangeCallback done.
The latter change is somewhat problematic (which the code needs to deal with as well). It is necessary to decide how to react to values that are of the correct type, but with senseless values. GConf in itself does not provide syntax checking for the values, or any semantic checking support. It is recommended that configuration changes will only be reacted to when they pass some internal (to the application) logic that will check their validity, both at syntax level and also at semantic level.
One option would also be resetting the value back to a valid value, whenever the program detects an invalid value set attempt. This will lead to a lot of problems, if the value is set programmatically from another program that will obey the same rule, so it is not advisable. Quitting the program on invalid values is also an option that should not be used, since the restricted environment does not provide many ways to inform the user that the program has quit.
An additional possible problem is having multiple keys that are all "related" to a single setting or action. It is necessary to decide, how to deal with changes across multiple GConf keys that are related, yet changed separately. The two key example code demonstrates the inherent problem: should the server re-initialize the (theoretic) connection, when the connection key is changed, or when the connectionparams key is changed? If the connection is re-initialized when either of the keys is changed, then the connection will be re-initialized twice when both are changed "simultaneously" (user presses "Apply" on a settings dialog, or gconftool-2 is run and sets both keys). It is easy to see how this might be an even larger problem, if instead of two keys, there were five per connection. GConf and the GConfClient
GObject
wrapper that has been used here do not support "configuration set transactions", which would allow setting and processing multiple related keys using an atomic model. The example program ignores this issue completely.
The next step is to test how the program (which is still running) reacts to other problematic situations:
[sbox-DIABLO_X86: ~/gconf-listener] > gconftool-2 --set --type int \ /apps/Maemo/platdev_ex/connectionparams 5 gconf-key-watch: keyChangeCallback invoked. gconf-key-watch: Connection params setting changed: [5] gconf-key-watch: keyChangeCallback done. [sbox-DIABLO_X86: ~/gconf-listener] > gconftool-2 --set --type boolean \ /apps/Maemo/platdev_ex/connectionparams true gconf-key-watch: keyChangeCallback invoked. gconf-key-watch: Connection params setting changed: [true] gconf-key-watch: keyChangeCallback done.
The next example removes the configuration keys, while the program is still running:
[sbox-DIABLO_X86: ~/gconf-listener] > make clearkeys gconf-key-watch: keyChangeCallback invoked. gconf-key-watch[21403]: GLIB ERROR ** default - file gconf-key-watch.c: line 129 (keyChangeCallback): assertion failed: (value != NULL) aborting... /usr/bin/run-standalone.sh: line 11: 21403 Aborted (core dumped) "$@" [1]+ Exit 134 run-standalone.sh ./gconf-key-watch
Since the code (in the callback function) contains an assert that checks for non-NULL
values, it will abort when the key is removed, and that causes the value to go to NULL
. So the abortion in the above case is expected.
- This page was last modified on 17 August 2010, at 13:38.
- This page has been accessed 27,820 times.