Article On GConf



Hello everyone,
I'd like to thank all of you for giving me the chance to write an article for GJ as part of GHOP. I chose to write about GConf because the documentation is extremly sparse, and its something that nearl y every dev needs to know about.

Anyway, I've attached my article below. Please tell me what you think.

The GConf Configuration System is a system used by the GNOME desktop to handle user preferences. This article will explain the fundamental concepts of how GConf works and will demonstrate how developers can use GConf in their applications. The examples in this article are all written in C.

Before we begin, there are a few details about GConf that are worth noting:
• GConf should not be used for crucial application settings (e.g. an address book's contacts). It should only be used for user preferences ( e.g. the address book's font).
• The GConf database is not guaranteed to be constantly running. As we'll see later on, values fetched from GConf should be checked to make sure that they exist.
• GConf values are (by definition) user writable. Applications should verify data stored in GConf when necessary.
• GConf auto-applies settings. In other words, when a setting changes, the change applies immediately without waiting for the user to press an "apply" button.
• GConfClient wraps the GConfEngine functions in a friendlier object that descends from GObject. For the most part, you won't want to use GConfEngine directly.
• GConf uses the Model View Controller (MVC) programming model. If you provide a preferences window to change settings, it should simply set the gconf keys and not update your app's settings directly. In other words, you should never do the following: gconf_client_set(client, key, value); application_update_to_reflect_setting();
You should always do something like: /* At application startup */
gconf_client_notify_add(client, key, application_update_to_reflect_setting, data);

/* From preferences dialog */
gconf_client_set(client, key, value);
Don't worry about the code in the previous example. It'll be explained later. What you need to understand is that GConf will automatically call your callback when the key changes. If the key is changed from outside of your app, then the first example won't work. The second one will.

GConf settings are stored in a key-value format. The keys and values are stored in xml files located in a subdirectory of ~/.gconf. You should never read and write to these files directly. You should always use the GConfClient api.

You can view the GConf database using the app gconf-editor. Before you continue reading, I'd recommend starting up gconf-editor and giving things a good look. You'll be able to see how keys and values are arranged. There also is a command line tool, gconftool-2, which can be used to get and set keys. You can read the man page for more information.

Lets begin looking at some some code! The following function is used to create a new GConfClient:
GConfClient* gconf_client_get_default (void);
The returned engine has a reference count of 1. It should be unreferenced when you're done with it with g_object_unref().

We can set a key with the following functions:
gboolean gconf_client_set_float (GConfClient *client,
const gchar *key,
gdouble val,
GError **err);

The parameters are pretty much self explanatory, but we'll explain them anyway.
client is the GConfClient that we created earlier.
key is the name of the key to set.
val is the value to set for key.
err is the return location for an error or NULL to ignore errors.

Similar functions exist for manipulating integers, booleans, and strings.
gboolean gconf_client_set_int (GConfClient *client,
const gchar *key,
gint val,
GError **err);
gboolean gconf_client_set_bool (GConfClient *client,
const gchar *key,
gboolean val,
GError **err);
gboolean gconf_client_set_string (GConfClient *client,
const gchar *key,
const gchar *val,
GError **err);

There are three additional functions for setting schemas, lists, and pairs. They're a bit more complicated and much less common, so we'll leave them for another time.

To get a key use gchar * gconf_client_get_string (GConfClient *client, const gchar *key, GError **err) or the corresponding function for any of the other data types.

The following example is heavily commented, and will explain the bare basics of gconf.

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

// this callback is called whenever the button is pressed
void pressed_cb (GtkWidget *widget, GConfClient *config)
{
// gconf_client_get_int () returns 0 if the key does not exist. In this case, we don't need to bother checking for errors.
gint clicks = gconf_client_get_int (config, "/apps/gconf-test/clicks", NULL);
clicks++;

// set the value so that it will exist next time this function is called
gconf_client_set_int (config, "/apps/gconf-test/clicks", clicks, NULL);

g_print ("%d\n", clicks);
}

int main (int argc, char **argv)
{
GtkWidget *window;
GtkWidget *button;
GConfClient *config;
// common gtk init
gtk_init (&argc, &argv);
// create the GConfClient
config = gconf_client_get_default ();
// create the window and make it quit when we want it to
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
// and the button...
button = gtk_button_new_with_label ("Press me");
// when it is pressed call our callback
g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (pressed_cb), config);
gtk_container_add (GTK_CONTAINER (window), button);
// some more common stuff
gtk_widget_show_all (window);
gtk_main ();
return 0;
}

You can compile the file with:
gcc -Wall -g gconf-test.c -o gconf-test `pkg-config --cflags gtk+-2.0 gconf-2.0` \
`pkg-config --libs gtk+-2.0 gconf-2.0`

Test it out and play around with it. You'll notice that the program will "remember" the number of values clicked the last time that it was run.

When you want to associate a particular GObject property ( e.g. a window's opacity) with a gconf key, then things begin to get complicated. The usual method of doing so is as follows:
1) Application starts up and sets the default value from GConf.
2) Application registers a notify event for the gconf key. (In other words, it tells gconf that it wants to be notified when the key changes.)
3) The user opens the app's preferences window and changes a key.
4) A callback gets called to set the GConf value.
5) Setting the GConf value triggers another callback that reads in the key and applies the setting.
This can get pretty lengthy when you have a few keys to set. In most cases, there's an easier way: Use libgconf-bridge.

Before we get to libgconf-bridge, I'll provide a quick overview of how to set notifications without it. You can find more details in the official docs.

There are two important functions to be used for notifications. The first is:
void gconf_client_add_dir (GConfClient *client,
const gchar *dir,
GConfClientPreloadType preload,
GError **err);
dir should be the directory to watch (e.g. "/apps/gconf-test"). GConf can preload the values before you try to read them in order to save time. preload should be either GCONF_CLIENT_PRELOAD_NONE, GCONF_CLIENT_PRELOAD_ONELEVEL, or GCONF_CLIENT_PRELOAD_RECURSIVE.

Once we have added a directory, we can tell GConf which specific keys we want to monitor with:
guint gconf_client_notify_add (GConfClient *client,
const gchar *namespace_section,
GConfClientNotifyFunc func,
gpointer user_data,
GFreeFunc destroy_notify,
GError **err);
client is a GConfClient.
namespace_section is a directory or key to listen to for changes.
func is the function to call when changes occur.
user_data is the data to pass to func.
destroy_notify is the function to call on user_data when the notify is removed or the GConfClient is destroyed, or NULL for none.
err is the return location for an allocated GError, or NULL to ignore errors.
The function return a connection id that can be used to remove the connection.

Finally, lets look at the GConfClientNotifyFunc function signature:
void (*GConfClientNotifyFunc) (GConfClient *client,
guint cnxn_id,
GConfEntry *entry,
gpointer user_data);
client is the GConfClient notifying us.
cnxn_id is connection ID from gconf_client_notify_add().
entry is a GConfEntry.
user_data is the user data that we specified with gconf_client_notify_add().

If you want to do things this way, you'll have to read the page on GConfValues and GConfEntries. The docs are pretty clear, so we'll leave that part for you.

Now, onto libgconf-bridge.

libgconf-bridge allows you to simplify things a lot. There are a few functions in libgconf-bridge, but the main one that you'll want use is gconf_bridge_bind_property(bridge, key, object, prop). The "bridge" argument can just be the return of gconf_bridge_get (). The following example should make it clear:

#include <gconf/gconf-client.h>
#include <gtk/gtk.h>
#include <libgconf-bridge/gconf-bridge.h>

int main (int argc, char **argv)
{
GtkWidget *window;
GtkWidget *scale;
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
gtk_window_set_default_size (GTK_WINDOW (window), 100, 30);
scale = gtk_hscale_new_with_range (0, 1, 0.1);
gtk_container_add (GTK_CONTAINER (window), scale);
gconf_bridge_bind_property (gconf_bridge_get (),
"/apps/gconf-test/scale",
G_OBJECT (gtk_range_get_adjustment (GTK_RANGE (scale))), "value");
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
Compile the example (you need to add libgconf-bridge to pkq-config) and watch the value in gconf-editor as you move the slider around. Nifty, huh?

The last gconf-related topic is schemas. Schemas are xml files that are installed for all users and contain the default values for an app. When the app is run for the first time, gconf will set the default values from the schema. Here's a sample schema file:
<?xml version="1.0"?>
<gconfschemafile>
<schemalist>
<schema>
<key>/schemas/apps/avant-window-navigator/applets/awn-terminal/opacity</key>
<applyto>/apps/avant-window-navigator/applets/awn-terminal/opacity</applyto>
<owner>avant-window-navigator</owner>
<type>float</type>
<default>0.5</default>
<locale name="C">
<short>opacity</short>
<long>The applet's opacity level.</long>
</locale>
</schema>
<schema>
<key>/schemas/apps/avant-window-navigator/applets/awn-terminal/bg_img</key>
<applyto>/apps/avant-window-navigator/applets/awn-terminal/bg_img</applyto>
<owner>avant-window-navigator</owner>
<type>string</type>
<default>""</default>
<locale name="C">
<short>Background</short>
<long>The applet's background image.</long>
</locale>
</schema>
</schemalist>
</gconfschemafile>

"Key" is the location of the actual schema. "Applyto" is the location of the key that the schema will set. The rest is self explanatory.

To install a schema with autotools, you need to add something like the following to your Makefile.am:
schemasdir = @GCONF_SCHEMA_FILE_DIR@
schemas_FILE = awn-terminal.schemas
schemas_in_files = $(schemas_FILE).in
schemas_DATA = $(schemas_in_files:.schemas.in=.schemas)

@INTLTOOL_SCHEMAS_RULE@

if GCONF_SCHEMAS_INSTALL
install-data-local: $(schemas_DATA)
GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(schemas_DATA)
endif

That's really all that there is to it. Now go out and add some preferences to your app!


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]