Written by Jannis Pohlmann, March 2007. Last updated on June 5th, 2007.
Please note that this document only reflects my ideas and opinions and not necessarily those of other Xfce developers. It was written for being discussed on the xfce4-dev mailinglist.
The multi-channel settings (MCS) concept has been invented to make it easier for programs to manage their settings (invidual settings will be called properties in this document). Its split up into an abstraction library to be used in client programs and a per-user daemon for handling the actual property management.
In this concept the way properties are organized on the hard drive is hidden from clients. Clients only know about so-called channels. The channels concept is similar to that of namespaces. An application may use one or more channels (with channel names being something like /xfce4/panel or /Thunar/renamer).
The library (libxfce4mcs) provides an API for client programs to use if they want to use the MCS for storing their settings. It basically contains three classes: XfceMcsManager for connecting to the session bus and the communication with the MCS daemon, XfceMcsClient to manage the configuration channels of the program and XfceMcsChannel for reading/writing the properties of a channel as well as listening for property changes.
/* Initializes the MCS libary and throws an error if the MCS daemon is unavailable for some reason */
void xfce_mcs_init (GError **error);
/* Shuts the library down and disconnects from the session bus */
void xfce_mcs_shutdown (void);
The XfceMcsManager API basically represents the D-Bus interface of the daemon. It is used by XfceMcsChannel and should not be used directly other than for displaying the settings dialogs.
typedef _XfceMcsManagerClass XfceMcsManagerClass;
typedef _XfceMcsManager XfceMcsManager;
XfceMcsManager *xfce_mcs_manager_get_default (void);
void xfce_mcs_manager_set (XfceMcsManager *manager,
XfceMcsChannel *channel,
const gchar *property,
const GValue *value);
GValue *xfce_mcs_manager_get (XfceMcsManager *manager,
XfceMcsChannel *channel,
const gchar *property);
void xfce_mcs_manager_show (XfceMcsManager *manager);
void xfce_mcs_manager_show_plugin (XfceMcsManager *manager,
const gchar *plugin_name);
void xfce_mcs_manager_monitor_channel (XfceMcsManager *manager,
XfceMcsChannel *channel);
void xfce_mcs_manager_cancel_monitor (XfceMcsManager *manager,
XfceMcsChannel *channel);
struct _XfceMcsManagerClass
{
GObjectClass __parent__;
/* Signal to be emitted when a property of a channel has changed. Used
* by all XfceMcsChannels to generate the property-changed signal */
void (*changed) (XfceMcsManager *manager,
const gchar *channel_name,
const gchar *property,
const GValue *value);
};
A channel represents the config domain for one program/client. Larger applications may instantiate more than one channel if they need. When created, a channel asks the XfceMcsManager to monitor the session bus for changes belonging to this channel.
typedef _XfceMcsChannelClass XfceMcsChannelClass;
typedef _XfceMcsChannel XfceMcsChannel;
XfceMcsChannel *xfce_mcs_channel_new (const gchar *channel_name);
const gchar *xfce_mcs_channel_get_string (XfceMcsChannel *channel,
const gchar *property,
const gchar *default_value);
void xfce_mcs_channel_set_string (XfceMcsChannel *channel,
const gchar *property,
const gchar *value);
guint xfce_mcs_channel_get_uint (XfceMcsChannel *channel,
const gchar *property,
guint default_value);
void xfce_mcs_channel_set_uint (XfceMcsChannel *channel,
const gchar *property,
guint value);
glong xfce_mcs_channel_get_long (XfceMcsChannel *channel,
const gchar *property,
glong default_value);
void xfce_mcs_channel_set_long (XfceMcsChannel *channel,
const gchar *property,
glong value);
gfloat xfce_mcs_channel_get_float (XfceMcsChannel *channel,
const gchar *property,
gfloat default_value);
void xfce_mcs_channel_set_float (XfceMcsChannel *channel,
const gchar *property,
gfloat value);
gdouble xfce_mcs_channel_get_double (XfceMcsChannel *channel,
const gchar *property,
gdouble default_value);
void xfce_mcs_channel_set_double (XfceMcsChannel *channel,
const gchar *property,
gdouble value);
gboolean xfce_mcs_channel_get_boolean (XfceMcsChannel *channel,
const gchar *property,
gboolean default_value);
void xfce_mcs_channel_set_boolean (XfceMcsChannel *channel,
const gchar *property,
gboolean value);
struct _XfceMcsChannelClass
{
GObjectClass __parent__;
/* Signal to be emitted when a property of this channel has changed */
void (*property_changed) (XfceMcsChannel *channel,
const gchar *property,
const GValue *value);
};
typedef _XfceMcsClientPrivate XfceMcsClientPrivate;
typedef _XfceMcsClientClass XfceMcsClientClass;
typedef _XfceMcsClient XfceMcsClient;
XfceMcsClient *xfce_mcs_client_get_default (void);
XfceMcsChannel *xfce_mcs_client_request_channel (XfceMcsClient *client,
const gchar *channel_name);
#include <gtk/gtk.h>
#include <libxfce4mcs/libxfce4mcs.h>
static void
channel_property_changed (XfceMcsChannel *channel,
const gchar *property,
const GValue *value)
{
g_debug ("Property '%s' has changed");
switch (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (value)))
{
case G_TYPE_STRING:
g_debug ("New value (string): '%s'", g_value_get_string (value));
break;
case G_TYPE_BOOLEAN:
g_debug ("New value (boolean): '%s'", g_value_get_boolean (value) ? "TRUE" : "FALSE");
break;
case G_TYPE_LONG:
g_debug ("New value (long): '%s'", g_value_get_long (value));
break;
}
}
int
main (int argc
char **argv)
{
XfceMcsClient *client;
XfceMcsChannel *channel;
GTimeVal starttime;
const gchar *username;
/* Get MCS client instance */
client = xfce_mcs_client_get_default ();
/* Request channel */
channel = xfce_mcs_client_request_channel (client, "/xfce4/apps/mcs-client-example");
/* Be notified when a property has changed */
g_signal_connect (G_OBJECT (channel), "property-changed", G_CALLBACK (channel_property_changed), NULL);
/* Determine current time and save it as last start time */
g_get_current_time (&starttime);
xfce_channel_set_long (channel, "/misc/last-starttime", starttime.tv_sec);
/* Determine username */
username = xfce_channel_get_string (channel, "/personal/username", g_get_user_name ());
/* Enter GTK+ main loop */
gtk_main ();
return 0;
}
The MCS damon is a D-Bus service running in the background. It handles read/write requests for channels and properties and also proxies XSETTINGS events. It provides an easy-to-use D-Bus interface for clients to use when they want to read/write properties or want to be notified of property changes on a certain channel.
How settings are stored on the hard drive may still be discussed. There are several ways to do this:
The daemon loads these files either on startup or when requested and cache them. Whenever a property changes, the related config file is written and notifications are sent to all channel listeners.
The following D-Bus interface defines methods for reading/writing config values (properties) as well as for notification. The parameter type v means Variant and represents GValues. To make this easier, the MCS library provides convenience methods to build and send the methods specified in this interface.
The Show and ShowPlugin methods can be used to display settings dialogs.
<interface name="org.xfce.MCS">
<method name="Set">
<arg name="channel" type="s" direction="in"/>
<arg name="property" type="s" direction="in"/>
<arg name="value" type="v" direction="in"/>
</method>
<method name="Get">
<arg name="channel" type="s" direction="in"/>
<arg name="property" type="s" direction="in"/>
<arg name="value" type="v" direction="out"/>
</method>
<signal name="Changed">
<arg name="channel" type="s"/>
<arg name="property" type="s"/>
<arg name="value" type="v"/>
</signal>
<method name="Show"/>
<method name="ShowPlugin">
<arg name="name" type="s"/>
</method>
</interface>
Plugins would register by using regular .desktop files just like they do now.
Through the use of an additional .desktop file or XML format permissions for accessing channels and properties could be restricted. This might be useful for kiosk mode for example. In that case the daemon would load the permission information files and would deny user access on certain channels and properties.
This part could be made optional at first as it should be easy to integrate it into the daemon later.
Below you can see several examples of how Xfce applications could make use of this design. Note that features mentioned in this document would help all applications. The following examples only demonstrate use-cases of the design.
#include <stdlib.h>
#include <libxfce4mcs/libxfce4mcs.h>
int
main (int argc,
char **argv)
{
XfceMcsManager *manager;
manager = xfce_mcs_manager_get_default (void);
if (argc > 1)
xfce_mcs_manager_show_plugin (manager, argv[1]);
else
xfce_mcs_manager_show (manager);
return EXIT_SUCCESS;
}
The panel could profit from this design by making it easier for plugins to read/write their configuration. It could request one channel with a unique name for each plugin and make it available through XfceMcsChannel *xfce_panel_plugin_get_channel (XfcePanelPlugin *plugin)
. This way plugins wouldn't have to deal with opening/reading/writing/closing XfceRc
s anymore. Instead they could just use the XfceMcsChannel interface.