Introduction to GConf and GnomeVFS
Mikael Hallendal, Imendio AB
Richard Hult, Imendio AB
Copyright © 2002 Mikael Hallendal and Richard Hult
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation.
Introduction
This is the continuation of the article we wrote for IBM developerWorks earlier this year. This article shows of the basics of two of the cool libraries that are used extensively in GNOME 2.
GConf
GConf is the configuration system of choice for GNOME 2 applications. It is replacing gnome-config, a very simple INI file based format, which turned out to be rather limited for large applications, and for handling advanced system-wide configuration by sysadmins. GConf on the other hand, is a lot more capable in those areas.
GConf was introduced in the GNOME 1.4 platform, but most GNOME applications didn't use it, with the most notable exception being Nautilus, the new GNOME file manager.
For GNOME 2 though, GConf is being rolled in more aggressively, with all (or at least most) of the core applications using GConf to store and manage their configuration data.
Advantages of GConf
There are numerous advantages to using GConf. The configuration data is stored in a tree structure, which makes it a lot easier to manage the preferences of a large application, which might have many preferences and configuration options.
An example of this tree structured configuration data might look like the following, taken from the web browser Galeon:
/apps/galeon:
/apps/galeon/Rendering:
/apps/galeon/Rendering/FontsColors:
background_color = #FFFFFF
visited_link_color = #FF0000
Here you see that Galeon is storing its preferences under the apps category, which is the place for applications to do so. Under the desktop category, on the other hand, desktop wide preferences, such as whether to use a left or right handed mouse, are stored.
Using subcategories, the preferences are split in small groups that belong together. This makes things clearer than just keeping all the options in one huge list, like the old-fashioned way dictated by gnome-config.
Data types
The kinds of data you can store in a GConf entry are: integer, string, float, boolean, schema, list, pair.
Most of these should be familiar to you, perhaps with the exception of schema, list and pair.
Schemas store a GConfSchema data type, which contains meta-information about a key. This will be described a bit more in detail further on in the article.
The list type stores a list of keys, although it has a few limitations. First all the elements must be of the same type, second, only the primitive types can be contained in a list (that is, all the types except list and pair).
Pairs can store two primitive values, either of the same type, or two different ones. Pairs can, like lists, only contain primitive types.
Notification
GConf offers another very nice feature for the modern desktop, namely notification across applications. This makes it possible for multiple applications, or multiple running instances of the same application, to immediately react to changes to preferences, and perform the necessary work to adapt to the new preferences.
In order to get notified of such a change, your application must tell GConf that it wants to listen to changes to a certain value, or to a whole directory in the configuration database.
Let's try all this out with a small example:
#include <glib.h> #include <gtk/gtk.h> #include <gconf/gconf-client.h> #define PATH "/apps/GNOMEnclature/gconf_example" #define KEY "/apps/GNOMEnclature/gconf_example/my_option" /* Update the GConf key when the user toggles the check button. */ static void button_toggled_cb (GtkWidget *button, gpointer data) { GConfClient *client; gboolean value; client = gconf_client_get_default (); value = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); gconf_client_set_bool (client, KEY, value, NULL); } /* This is called when our key is changed. */ static void gconf_notify_func (GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer user_data) { GtkWidget *check_button = GTK_WIDGET (user_data); GConfValue *value; gboolean checked; value = gconf_entry_get_value (entry); checked = gconf_value_get_bool (value); /* Update the check button accordingly. */ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), checked); } int main (int argc, char **argv) { GtkWidget *window; GtkWidget *check_button; GConfClient *client; gboolean value; gtk_init (&argc, &argv); client = gconf_client_get_default (); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (window, "destroy", gtk_main_quit, NULL); /* Get the initial value from GConf. */ value = gconf_client_get_bool (client, KEY, NULL); check_button = gtk_check_button_new_with_label ("My option"); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button), value); g_signal_connect (check_button, "toggled", (GCallback) button_toggled_cb, NULL); gtk_container_add (GTK_CONTAINER (window), check_button); gtk_widget_show_all (window); /* Add our directory to the list of directories the GConfClient will * watch. */ gconf_client_add_dir (client, PATH, GCONF_CLIENT_PRELOAD_NONE, NULL); /* Listen to changes to our key. */ gconf_client_notify_add (client, KEY, gconf_notify_func, check_button, NULL, NULL); gtk_main (); return 0; }
To compile this example:
# gcc `pkg-config --libs --cflags gtk+-2.0 gconf-2.0` \
gconf-example.c -o gconf-example
This example will popup a small window with a check button, that is connected to a GConf key. Try compiling the program, and start two instances of it, to see the notification service at work. When you check the button in one of the programs, it will immediately update the second program window as well! Also note that the state of the button is saved so the next time you start the program, it will be the same way as the last time you ran it.
Schemas
As mentioned above, each key can have a schema, which is a description that documents that particular key. This makes it a lot easier for system administrators to configure the desktop for their users, since each key is documented. A schema also contains a default value for each key, which is used if no value has been assigned to the key, letting the admin easily assign a nice default setup, for example.
Schemas also make it easier to clean out old configurations left from long gone applications, since they store the name of the application that created the key.
Writing and using schema files
To use schemas with your application, you first need to create a schema file, which is an XML file, listing all the schemas. A simple example might look like:
<gconfschemafile> <schemalist> <schema> <key>/schemas/apps/my_app/my_key</key> <applyto>/apps/my_app/my_key</applyto> <owner>my_app</owner> <type>int</type> <default>42</default> <locale name="C"> <short>A short description of this key</short> <long>A longer description here. We can have lots of text here, describing what the possible values are, and what they mean. </long> </locale> </schema> <schema> <key>/schemas/apps/my_app/my_other_key</key> <applyto>/apps/my_app/my_other_key</applyto> <owner>my_app</owner> <type>bool</type> <default>1</default> <locale name="C"> <short>A short description of this key</short> <long>A longer description here. We can have lots of text here, describing what the possible values are, and what they mean. </long> </locale> </schema> </schemalist> </gconfschemafile>
As you can see, a schema file is simply a list of schemas. For each schema, you specify the type, default value, and which key the schema applies to. Make sure to add useful descriptions of the key, both the short and the long versions.
When you have your schema file, you can use gconftool to install it:
# gconftool --install-schema-file=FILENAME
If you want to read more about GConf, check out the API reference at: http://developer.gnome.org/doc/API/gconf/index.html.
GnomeVFS
Another library that was introduced in GNOME 1.4, but still worth mentioning here, is GnomeVFS (GNOME Virtual File System). GnomeVFS is a great library which makes accessing various kinds of file systems transparent to the user and developer. The user doesn't even have to take care of whether a file is located on the local machine or on a remote server. Various protocols like HTTP, FTP, local files, WebDAV, are all used in the exact same way.
API
The API for accessing files have been kept close to the POSIX standard with some minor changes:
- For example, you use GnomeVFSHandle instead of file descriptors in GnomeVFS.
- All I/O operations return a GnomeVFSResult, indicating the result of the operation.
The API is very straight-forward and easy to use, as we'll show in the following example:
#include <glib.h> #include <gtk/gtk.h> #include <libgnomevfs/gnome-vfs.h> #define BUF_SIZE 8192 GtkWidget * load_image (const gchar *uri) { GnomeVFSHandle *handle; GnomeVFSResult result; GdkPixbufLoader *loader; GdkPixbuf *pixbuf; gchar buffer[BUF_SIZE]; GnomeVFSFileSize bytes_read; result = gnome_vfs_open (&handle, uri, GNOME_VFS_OPEN_READ); loader = gdk_pixbuf_loader_new (); while (result == GNOME_VFS_OK) { result = gnome_vfs_read (handle, buffer, BUF_SIZE, &bytes_read); gdk_pixbuf_loader_write (loader, buffer, bytes_read, NULL); } gdk_pixbuf_loader_close (loader, NULL); pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); if (!pixbuf) { return NULL; } return gtk_image_new_from_pixbuf (pixbuf); } int main (int argc, char **argv) { GtkWidget *window; GtkWidget *image; if (argc < 2) { g_print ("Run with %s <uri>\n", argv[0]); exit (1); } gtk_init (&argc, &argv); gnome_vfs_init (); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (window, "destroy", gtk_main_quit, NULL); image = load_image (argv[1]); if (!image) { g_print ("Not a valid uri: %s\n", argv[1]); return (1); } gtk_container_add (GTK_CONTAINER (window), image); gtk_widget_show_all (window); gtk_main (); return 0; }
To compile the example:
# gcc `pkg-config --libs --cflags gtk+-2.0 gnome-vfs-2.0` \
vfs-example.c -o vfs-example
The example reads a string from the command-line and uses this string to read an image which is displayed in a window. The string can represent an URL, like http://www.gnome.org/images/logo-large or it can represent a local file, like ~/picture.gif.
Asynchronous operations
GnomeVFS also provides methods for performing asynchronous operations, such as fetching a file in the background. If you, for example, are writing an FTP client, you don't want the entire GUI to freeze while a file is being received. This could be solved by using threads in your application, but using threads makes the application much more complex and harder to debug.
Using the asynchronous operations in GnomeVFS makes it very easy to get the same functionality without the complexity of threads (GnomeVFS actually uses threads internally, but hides them from you).
The method is very similar to using synchronous calls. But instead of waiting for the various function calls to open, read and close to finish, the asynchronous functions return immediately. When the operation is finished, a callback is invoked.
void gnome_vfs_async_open (GnomeVFSAsyncHandle **handle_return, const gchar *text_uri, GnomeVFSOpenMode open_mode, int priority, GnomeVFSAsyncOpenCallback callback, gpointer callback_data);
By calling gnome_vfs_async_open() you start asynchronous operation of opening an URI. This function will return immediately so that you can carry on with other operations.
When the uri has been opened, 'callback' will be called and you can start reading with gnome_vfs_async_read() from the handle. When all data has been read, you call gnome_vfs_async_close() to close the connection.
Writing your own GnomeVFS modules
If GnomeVFS is lacking support for some file system or network protocol, you can write your own module to handle this extra functionality. You then have instant transparent support for this, in all programs using GnomeVFS to access files! How to write such a GnomeVFS module is outside the scope of this article and will be handled in a later article.
Conclusion
In this article we started off by describing GConf, the configuration system to be used in GNOME 2. After that we described the basics and benefits of using GnomeVFS for your file operations.
Neither of these libraries depend on X so you can with great advantage use them for writing your command-line tools, or other non-X programs.
References
The first article can be read at
http://www-106.ibm.com/developerworks/linux/library/l-gnome1/?t=grgn,p=GTK2
More information about GConf: http://www.gnome.org/projects/gconf/
More information about GnomeVFS: http://developer.gnome.org/doc/API/gnome-vfs/
For general GNOME and GNOME developer information, visit The GNOME web site and The GNOME developer site.
To learn more about the company the authors work for, visit the Imendio web site.
Mikael Hallendal is a GNOME and Jabber developer at Imendio in Sweden, currently working on Loudmouth, a Jabber client library using GLib. In addition, Mikael is developing a jabber client for GNOME 2 called Gossip, and is also is the maintainer of DevHelp, a document browser for developers.
Richard Hult works also for Imendio and has been a GNOME developer for about three years. At Imendio, Richard works on Gossip and DrWright.
