GSettings
Depuis Gnome 3, le stockage de la configuration d'une application se fait par l'intermédiaire de GSettings, une api « agnostique » dont l'utilisation dans un plugin Gedit ou une extension Gnome-shell pose de petits problèmes car elle nécessite les droits administrateurs. Voici quelques pistes pour l'utiliser « en local ».
L'enregistrement de la configuration d'une application est un principe simple qui consiste à stocker une série de couples clé/valeur, soit le nom d'une propriété de configuration et sa valeur. L'utilisateur retrouve ensuite ces valeurs à chaque fois qu'il lance l'application.
Gnome 2 et GConf
Sous Gnome 2, le stockage des informations de configuration se faisait avec GConf. Son utilisation était dés plus simple :
# Importer gconf
import gconf
# Chemin de stockage de la configuration
# d'une application 'myapp'
GCONF_DIR = '/apps/myapp/'
# Récupérer la valeur de 'foo': 'bar'
value = gconf.client_get_default().get_string(GCONF_DIR + 'foo')
# Assigner une nouvelle valeur à 'foo': 'baz'
gconf.client_get_default().set_string(GCONF_DIR + 'foo', 'baz')
Gnome 3 et DConf - GSettings
Avec Gnome 3, les choses se complexifient un (tout) petit peu. D'abord parce que GConf est remplacé par Dconf ; ensuite parce que les applications n'interagissent plus directement avec l'outil de stockage mais avec GSettings, une API générique qui est chargée d'aller dialoguer avec DConf(1).
L'avantage de cette technique est évident : en utilisant GSettings, on reste indépendant de la manière dont les données vont être stockées. Si le projet Gnome décide un jour d'abandonner DConf pour utiliser un autre système de stockage, le changement sera transparent.
Utiliser un schéma
Nous partirons d'une application « myapp » possédant un dossier « data » contenant lui-même un dossier « schema » dans lequel nous stockerons les schémas GSettings.
- myapp
- data
- schema
- data
Création du schéma
GSettings se base sur l'utilisation d'un schéma de configuration au format xml :
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema path="/org/myapp/" id="org.myapp">
<key name="boolkey" type="b">
<default>true</default>
<summary>A boolean configuration key.</summary>
<description>...</description>
</key>
<key name="strkey" type="s">
<default>"string"</default>
<summary>A string configuration key.</summary>
<description>...</description>
</key>
<key name="intkey" type="i">
<default>25</default>
<range min="0" max="100" />
<summary>A integer configuration key.</summary>
<description>...</description>
</key>
<key name="choicekey" type="s">
<choices>
<choice value="choice 1"/>
<choice value="choice 2"/>
</choices>
<default>"choice 1"</default>
<summary>A choice configuration key.</summary>
</key>
</schema>
</schemalist>
La documentation de GSettings fournit la DTD de ce type de fichier qui est structuré autour d'une liste d'éléments key
représentant des données de configuration. Chaque clé doit être définie par :
- Un attribut
type
correspondant à son type. - Un attribut
name
pour l'identifier. - Un élément
default
pour spécifier sa valeur par défaut.
Chaque clé peut aussi contenir les éléments suivants :
sumamry
: courte définition de la clé.description
: description de la clé.
Ces fichiers de schémas doivent avoir un nom qui se termine par « gschema.xml ». Ils ne sont utilisés que sous forme compilée, compilation qui s'effectue grâce au programme glib-compile-schemas
.
- myapp
- data
- schema
- org.myapp.gschema.xml
- schema
- data
Compilation du schéma
Compilation au niveau du système
La documentation Gnome précise que les schémas compilés doivent être placés dans un des répertoires système de données (XDG_DATA_DIRS
)(2) pour pouvoir être lus, GSettings ne pouvant pas les lire dans le répertoire local de données propre à l'utilisateur (XDG_DATA_HOME
)(3). C'est un problème si on souhaite l'utiliser pour développer un plugin Gedit ou une extension Gnome-shell par exemple : l'utilisateur devra copier le fichier xml et le compiler dans un répertoire système avec les droits administrateurs :
# Se loguer en root
su -
# Copier le schéma dans le répretoire système adéquat
cp path/to/myapp/data/schema/org.myapp.gschema.xml /usr/share/glib-2.0/schemas/
# (Re)compiler les schémas GSettings.
glib-compile-schemas /usr/share/glib-2.0/schemas/
/usr/share/glib-2.0/schemas/
.- [^fn-xdg-data-dirs]
/usr/local/share/glib-2.0/schemas/
/usr/share/glib-2.0/schemas/
Compilation embarquée
Cette manipulation est une véritable regression par rapport à GConf. On peut cependant l'éviter en compilant le schéma à l'endroit où il se trouve :
glib-compile-schemas path/to/myapp/data/schema/
La commande glib-compile-schemas
va créer un fichier de compilation nommé « gschemas.compiled ».
- myapp
- data
- schema
- gschemas.compiled
- org.myapp.gschema.xml
- schema
- data
Compilation locale
Comme expliqué plus haut, GSettings n'est pas capable de lire des schémas de configuration déployés en local dans ~/.local/share/glib-2.0/schemas/
. En fait, cela n'est pas tout à fait vrai, cela grâce à la variable d'environnement GSETTINGS_SCHEMA_DIR
:
GSETTINGS_SCHEMA_DIR. This variable can be set to the name of a directory that is considered in addition to the glib-2.0/schemas subdirectories of the XDG system data dirs when looking for compiled schemas for GSettings.
Cependant, pour pouvoir utiliser GSETTINGS_SCHEMA_DIR
, il faut que l'utilisateur la définisse lui-même car elle ne l'est pas par défaut - du moins sous Fedora 17, définition à effectuer dans le fichier .profile
par exemple :
# GSettings local dir
GSETTINGS_SCHEMA_DIR=$HOME/.local/share/glib-2.0/schemas/
export GSETTINGS_SCHEMA_DIR
~/.profile
.Il faudra ensuite copier le schéma de configuration dans ce répertoire et lancer sa compilation :
import os
def schema_to_local(schema_file):
'''
Copier et compiler un schéma GSettings dans le répertoire local
@param schema_dir: chemin du schéma
@return: True si la compilation a été effectuée, False sinon.
'''
local_schema_dir = os.environ.get('GSETTINGS_SCHEMA_DIR')
if local_schema_dir is not None:
cmd_copy = 'cp ' schema_file + ' ' + local_schema_dir
cmd_compil = 'glib-compile-schemas ' + local_schema_dir
os.system(cmd_copy)
os.system(cmd_compil)
return True
return False
# Copier et compiler un schéma Gsettings local.
# param $1: chemin du schéma
schema_to_local() {
if [[ $1 ]]; then
if [[ $GSETTINGS_SCHEMA_DIR ]]; then
cp $1 $GSETTINGS_SCHEMA_DIR
glib-compile-schemas $GSETTINGS_SCHEMA_DIR
else
echo "GSETTINGS_SCHEMA_DIR is not defined"
fi
fi
}
Obliger l'utilisateur à définir une variable d'environnement pour avoir à disposition la configuration d'un plugin Gedit ou d'une extension gnome-shell est tout de même contraignant, moins que de devoir installer des fichiers avec des droits administrateurs mais contraignant tout de même. La compilation dans le plugin reste donc la solution la plus acceptable, en attendant que GSETTINGS_SCHEMA_DIR
soit définie par les systèmes d'exploitation.
Chargement du schéma
Nous pouvons maintenant charger un objet Gsettings dans notre code en partant du principe que le schéma à utiliser sera éventuellement installé localement :
from gi.repository import Gio
import os
def get_settings(schema_name, schema_dir = None):
'''
Charger un objet de configuration Gio.Settings.
La méthode cherche d'abord si la configuration demandée est chargé globalement.
Si ce n'est pas le cas, elle tentera de charger un schéma 'local', présent
dans le répertoire 'schema_dir'.
@param schema_name: Nom du schéma.
@param schema_dir: répertoire du schéma.
@return: Gio.Settings ou None
'''
schemas = Gio.Settings.list_schemas()
if schema_name in schemas:
return Gio.Settings.new(schema_name)
if schema_dir is not None and os.path.isdir(schema_dir):
schema_source = Gio.SettingsSchemaSource.new_from_directory(
schema_dir,
Gio.SettingsSchemaSource.get_default(),
False
)
schema = schema_source.lookup(schema_name, False)
if schema is not None:
return Gio.Settings.new_full(schema, None, None)
return None
const Gio = imports.gi.Gio;
/**
* Charger un objet de configuration Gio.Settings.
*
* La méthode cherche d'abord si la configuration demandée est chargé globalement.
* Si ce n'est pas le cas, elle tentera de charger un schéma 'local', présent
* dans le répertoire 'schema_dir'.
*
* @param {String} schema_name Nom du schéma.
* @param {String} [schema_dir] Répertoire local du schéma.
* @return {Gio.Settings|null}
*/
const get_settings = function (schema_name, schema_dir) {
let schemas = Gio.Settings.list_schemas(),
schema_source,
schema;
if (schema_name in schemas) {
return new Gio.Settings({ settings_schema: schemas[schema_name] });
}
if (schema_dir) {
let dir = Gio.File.new_for_path(schema_dir);
if (!dir.query_exists(null)) {
return null;
}
}
schema_source = Gio.SettingsSchemaSource.new_from_directory(
schema_dir,
Gio.SettingsSchemaSource.get_default(),
false
);
schema = schema_source.lookup(schema_name, false);
if (schema) {
return new Gio.Settings({ settings_schema: schema });
}
return null;
}
GSettings : API
Il n'est pas question ici de faire un tour complet de l'API de GSettings ; la documentation Gnome le fait très bien. Ne sont abordés ici que 2 points essentiels : la lecture / écriture des clés d'un schéma et l'écoute des changements de valeur de ces clés. Un troisième point, la liaison entre une clé et un composant graphique, est traité en détail dans le chapitre suivant.
Les exemples de code partent du principe que la méthode d'acquisition d'un objet Gsettings (get_settings
) décrite précédemment.
Lecture / écriture de clé
settings = get_settings('org.myapp', '/path/to/myapp/data/schema')
# Lecture de valeurs
strvalue = settings.get_string('strkey')
boolvalue = settings.get_boolean('boolkey')
intvalue = settings.get_int('intkey')
# Nouvelles valeurs
settings.set_string('strkey', 'new string')
settings.set_boolean('boolkey', False)
settings.set_int('intkey', 10)
var settings = get_settings('org.myapp', '/path/to/myapp/data/schema');
// Lecture de valeurs
var strvalue = settings.get_string('strkey');
var boolvalue = settings.get_boolean('boolkey');
var intvalue = settings.get_int('intkey');
// Nouvelles valeurs
settings.set_string('strkey', 'new string');
settings.set_boolean('boolkey', false);
settings.set_int('intkey', 10);
Ecoute des modifications
L'événement changed
est émis lors de la modification d'une clé de l'objet GSettings. Pour être uniquement à l'écoute des modifications d'une clé particulière « x », il faut plutôt écouter l'événement changed::x
.
def on_settings_changed(settings, key, data = None):
print key + ' has changed'
def on_strkey_changed(settings, key, data = None):
print 'strkey has changed'
settings = get_settings('org.myapp', '/path/to/myapp/data/schema')
settings.connect('changed', on_settings_changed)
settings.connect('changed::strkey', on_strkey_changed)
changed
en python.var on_settings_changed = function (settings, key, data) {
print(key + ' has changed');
};
var on_strkey_changed = function (settings, key, data) {
print('strkey has changed');
};
var settings = get_settings('org.myapp', '/path/to/myapp/data/schema');
settings.connect('changed', on_settings_changed);
settings.connect('changed::strkey', on_strkey_changed);
changed
en javascript.écran de configuration
Nous allons illustrer l'utilisation de GSettings avec l'écriture d'un écran de configuration de 2 applications écrites avec 2 languages de programmation différents : une extension gnome-shell écrite en javascript et un plugin Gedit écrit en python.
Liaison entre composant graphique et clé GSettings
La liaison entre un composant graphique et une clé GSettings se fait par l'intermédiaire de la méthode bind
d'un objet GSettings :
method : bind param : {String} key Nom de la clé du schéma param : {Object} object Composant graphique lié à la clé. param : {String} property Propriété de l'objet liée à la clé. param : {GSettingsBindingFlags} flag Flag
L'objet en question sera un composant graphique GTK adpaté au type de la clé, par exemple :
Gtk.Entry
pour une chaîne de caractères.Gtk.SpinButton
pour une entier.Gtk.Switch
pour un booléen.
On pourra aussi gérer certains cas particuliers : une clé String
ayant une liste de choix pourra être liée à une Gtk.ComboBoxText
.
Le module settings
J'ai créé un module settings
en javascript et en python à la fois pour faciliter l'utilisation de schémas Gsettings en « embarqué » dans une application mais aussi pour automatiser la création d'une interface graphique de configuration basée sur les clés d'un schéma Gsettings.
method : get com : Récupérer un objet GSettings. param : {String} schema_name Nom du chemin param : {String} [schema_dir] Chemin local éventuel return : {Gio.Settings}
La méthode get
reprend purement et simplement le code de la méthode get_settings
vue dans l'article expliquant comment utiliser un schéma GSettings.
method : get_details com : Parser le fichier xml décrivant le schéma GSettings pour récupérer les informations concernant les clés param : {String} schema_path Chemin du fichier GSettings. param : {String} schema_name Identifiant du schéma. return : {Object}
The schemas are compiled in a database, that will only contain the default values and potential constraints about the values for a key. So if an application wants to access the description of a key (which is the exception, not the usual case), it will have to parse the XML files for this.
Les schémas GSettings sont utilisés sous forme compilée et cette compilation ne stocke que les valeurs des clés, pas les autres informations qui pourraient être utiles lors de la création d'un écran de configuration, comme le résumé ou la description des clés. Pour récupérer toutes les informations des clés, il faut donc lire le fichier xml du schéma, ce que la méthode get_details
fait.
class : Gui com : Classe de génération d'interface graphique pour un fichier de configuration. param : {Gio.Settings} settings Objet param : {Object} cfg Objet de configuration. param : {String} cfg.ui_path Chemin du fichier Glade construisant l'interface graphique. param : {String[]} cfg.ids Liste des identifiants des composants graphiques à lier.
Décrire l'utilisation de Gui.
Utilisation dans une application
On peut utiliser le module settings
dans une application GTK : nous allons en créé une un peu particulière
- myapp
- commongjs
- settings.js
- utils.js
- commonpy
- settings.py
- utils.py
- data
- schema
- gschema.compiled
- org.myapp.gschema.xml
- ui
- config.glade
- schema
- myapp.js
- myapp.py
- commongjs
Cas des plugins Gedit
Gedit permet la définition de l'écran de configuration d'un plugin si celui-ci implémente l'interface PeasGtk.Configurable
. La méthode do_create_configure_widget
doit alors retourner le contenu qui sera placé dans la fenêtre de configuration.
- gedit-plugin
- cfgplugin
- common
- settings.py
- data
- schema
- gschemas.compiled
- org.gnome.gedit.plugins.cfgplugin.gschema.xml
- ui
- config.glade
- schema
- __init__.py
- common
- cfgplugin.plugin
- cfgplugin
# -*- coding: utf-8 -*-
import os
from gi.repository import GObject, Gtk, Gedit, PeasGtk, Gio
from common.settings import Settings
cdir = os.path.dirname(__file__)
schema_dir = os.path.join(cdir, 'data', 'schema')
schema_id = 'org.gnome.gedit.plugins.cfgplugin'
schema_path = os.path.join(schema_dir, schema_id + '.gschema.xml')
ui_path = os.path.join(cdir, 'data', 'ui', 'config.glade')
ui_ids = ['config', 'intkey_adj']
cfg_prefs = ['strkey', 'boolkey', 'intkey', 'choicekey']
# Gio.Settings
sets = Settings.get(schema_id, schema_dir)
class CfgPlugin(GObject.Object, Gedit.WindowActivatable, PeasGtk.Configurable):
__gtype_name__ = 'CfgPlugin'
window = GObject.property(type=Gedit.Window)
def __init__(self):
GObject.Object.__init__(self)
def do_create_configure_widget(self):
gui = Settings.Gui(sets, {
'uipath' : ui_path,
'schemapath': schema_path,
'ids' : ui_ids,
'prefs' : cfg_prefs
})
return gui.get_widget()
def do_activate(self):
pass
def do_deactivate(self):
pass
def do_update_state(self):
pass
Ressources et références
- Titre
- GIO Reference Manual
- chapter
- GSettings
- Éditeur
- Gnome Dev Center
- Titre
- GIO Reference Manual
- chapter
- Running GIO applications
- Éditeur
- Gnome Dev Center
- Titre
- XDG Base Directory Specification
- Auteurs
- [[[[Waldo Bastian
- Ryan Lortie
- Lennart Poettering]]]]
- Éditeur
- freedesktop.org
- Titre
- GConf Reference Manual
- Éditeur
- Gnome Dev Center
- Titre
- Using GSettings with Python/PyGObject
- Auteurs
- [[[[Micah Carrick]]]]
- Éditeur
- micahcarrick.com
- Date
- Titre
- Extensions
- chapter
- Extension Preferences
- Éditeur
- Gnome Live
- Date
Notes
- 1
En fait, c'est un peu le même principe qui prévalait avec GConf : on n'attaquait pas directement le système de stockage mais on utilisait plutôt un « intermédiaire » ou client fournit par la méthode
client_get_default
.
- 3
~/.local/share/glib-2.0/schemas/