Design Draft: Multi-Channel Settings for Xfce 4.6

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.

  1. Introduction
  2. Library
    1. API Fundamentals
    2. Manager API
    3. Channel API
    4. Client API
    5. Client Example
  3. Daemon
    1. D-Bus Interface
    2. Plugins
    3. Permissions
  4. Xfce Applications
    1. xfce-setting-show
    2. xfce4-panel

Introduction

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).

Library

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.

API Fundamentals

/* 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);

Manager API

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);
};

Channel API

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);
};

Client API

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);

Client Example

#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;
}

Daemon

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.

D-Bus Interface

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

Plugins would register by using regular .desktop files just like they do now.

Permissions

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.

Xfce Applications

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.

xfce-setting-show

#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;
}

xfce4-panel

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 XfceRcs anymore. Instead they could just use the XfceMcsChannel interface.