Springe zum Hauptinhalt

Das Konfigurationssystem GSettings

Das GNOME-eigene Konfigurationssystem GSettings

GSettings ist GNOMEs zentrales Konfigurationssystem für Anwendungen. Es ist die Schnittstelle für verschiedenmögliche Backends, gemeinhin ist dies dconf.

Mittels grafischem (dconf-editor) oder Kommandozeilentool (gsettings) lassen sich Konfigurationen abfragen und manipulieren.

Das folgende Beispiel kann Hintergrundbilder laden, festlegen und bookmarken/favorisieren.

/images/17_gsettings.thumbnail.png

Schemas

Um eine Konfiguration für eine Anwendung zu erstellen, muss diese in einer Schema-Datei definiert werden. Diese Datei ist eine XML-formatierte Datei, die anschließend in sein maschinenlesbares Äquivalent überführt werden muss.

Ein Beispiel für eine Schema-Datei mit einer festzulegenden Eigenschaft (key) wäre etwa:

<schemalist>
  <schema id="org.gtk.Test" path="/org/gtk/Test/">

    <key name="string-key" type="s">
      <default>""</default>
      <summary>A string</summary>
      <description>
        Configuration key defined for a string. Default value is set to an empty string.
      </description>
    </key>

  </schema>
</schemalist>

Die Dateibenennung folgt der Vorgabe "schema.id.gschema.xml". Das Standardinstallationsverzeichnis für Schema-Dateien ist /usr/share/glib-2.0/schemas. Schema-Dateien können auch außerhalb dieses Verzeichnisses genutzt werden (z.B. lokal, zu Testzwecken), sie werden dann aber nicht vom dconf-editor angezeigt.

Die erforderliche Kompilierung erfolgt mit

$ glib-compile-schemas /path/to/schema/files/
$ # default directory
$ glib-compile-schemas /usr/share/glib-2.0/schemas/

Die kompilierte und nun von GSettings verwendete Datei ist gschemas.compiled.

Glade

Das Beispiel soll Bilder anzeigen, dafür wird das Widget GtkImage benötigt. Alle Steuerungselemente werden in der Headerbar untergebracht:

  • "Open File"-Button: öffnet einen FileChooserDialog

  • Switch: schaltet Desktop-Icons an oder ab

  • "Fav"-Togglebutton: bookmarkt angezeigte Dateien, zeigt an, ob angezeigte Datei als Favorit markiert ist

  • "Set as wallpaper"-Button: angezeigte Datei als Hintergrundbild verwenden

  • MenuButton: unterhalb des Buttons wird eine Liste der favorisierten Dateien angezeigt, die von dort aus aufgerufen werden können

Python

Globales Schema laden

Eine bestehende Konfiguration zu laden, geschieht einfach per

setting = Gio.Settings.new("full.schema.path")
# load desktop background configuration
setting = Gio.Settings.new("org.gnome.desktop.background")

Lokales Schema laden

Bei einem lokal gespeicherten Schema muss der Ort der schemas.compiled angegeben werden, bevor die Konfiguration geladen werden kann:

schema_source = Gio.SettingsSchemaSource.new_from_directory(os.getcwd(),
            Gio.SettingsSchemaSource.get_default(), False)
schema = Gio.SettingsSchemaSource.lookup(schema_source, "org.example.wallpaper-changer", False)
setting = Gio.Settings.new_full(schema, None, None)

Widget verknüpfen

Es ist möglich, GSettings-Eigenschaften direkt an Widgets zu binden. Diese können dann bidirektional Zustände anzeigen bzw. man kann Änderungen an ihnen vornehmen:

setting.bind("setting-key", widget, property, Gio.SettingsBindFlags...)

Im Beispiel wäre dies also

self.bg_setting.bind("show-desktop-icons", self.obj("switch"), "active", Gio.SettingsBindFlags.DEFAULT)

Der Schalter zeigt beim Start die aktuelle Einstellung an. Eine Änderung des Status ist sofort wirksam.

Werte abrufen und festlegen

Eigenschaften können mit get_"type" und set_"type" ermittelt und festgelegt werden. Die relevante Funktion ist vom festgelegten Schlüsseltyp abhängig, also get_string und set_string für Zeichenketten, get_int und set_int für Ganzzahlen usw. (siehe PyGObject API Reference).

Wird der Wert einer Eigenschaft per get_value(key) abgefragt, wird dies immer als Wert des Typs GLib.Variant zurückgegeben. Entsprechend erwartet die Funktion set_value(key) ebenfalls diesen Typ.

Die Inhalte dieser Werte lassen sich einfach in simple Datentypen konvertieren, z.B.

# return string
setting.get_value(key).get_string()
# return anything (list, string, bool etc.)
setting.get_value(key).unpack()

Umgekehrt lassen sich reguläre Datentypen nach folgendem Muster als GLib.Variant-Typ ausdrücken und an GSettings übergeben:

setting.set_value(key, GLib.Variant(string_type, value)

Eine Liste der verfügbaren Stringtypen finden sich in der GNOME Developer-Dokumentation.

Im Beispiel wird auf diese Art die Favoritenliste aktualisiert:

app_setting.set_value("favourites", GLib.Variant("as", fav_list))

Listings

Schema

org.example.wallpaper-changer.gschema.xml (Source)

<?xml version="1.0" encoding="utf-8"?>

<schemalist>

  <schema path="/org/example/wallpaper-changer/" id="org.example.wallpaper-changer">

    <key name="favourites" type="as">
      <default>[]</default>
      <summary>List of favourite wallpapers</summary>
      <description>
        Add or remove entry by pressing the 'fav' toggle button.
      </description>
    </key>

  </schema>

</schemalist>

Python

17_gsettings.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import os
import sys

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GLib, GdkPixbuf


class Handler:

    def on_window_destroy(self, window):
        window.close()

    def on_dialog_close(self,widget, *event):
        widget.hide_on_delete()
        return True

    def on_filechooser_dialog_response(self, widget, response):
        if response == 1:
            self.on_dialog_close(widget)
        elif response == 0:
            app.uri = widget.get_filename()
            app.draw_pixbuf(app.uri)
            app.handle_fav(app.uri)
            self.on_dialog_close(widget)

    def on_filechooser_dialog_file_activated(self, widget):
        self.on_filechooser_dialog_response(widget, 0)

    def on_open_button_clicked(self, widget):
        app.obj("filechooser_dialog").show_all()

    def on_setwp_button_clicked(self, widget):
        app.bg_setting.set_string("picture-uri", "file://{}".format(app.uri))

    def on_window_size_allocate(self, widget, size):
        app.draw_pixbuf(app.uri)

    def on_filechooser_dialog_update_preview(self, widget):
        if widget.get_filename() != None and os.path.isfile(widget.get_filename()):
            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(widget.get_filename(),200, 200, True)
            app.obj("preview").set_from_pixbuf(pixbuf)

    def on_fav_button_toggled(self,widget):
        if widget.get_active():
            #add file to fav_list if not in list
            if app.uri not in app.fav_list:
                app.fav_list.append(app.uri)
        else:
            #remove file from fav_list if in list
            if app.uri in app.fav_list:
                app.fav_list.remove(app.uri)
        #update GSettings entry for favourites
        app.app_setting.set_value("favourites", GLib.Variant("as", app.fav_list))
        #update fav list in popup menu
        popup = app.obj("menu")
        #remove all items
        for i in popup.get_children():
            popup.remove(i)
        #reload all items from fav_list
        for fav in app.fav_list:
            #only label menuitem with filename instead of path
            item = Gtk.MenuItem(os.path.split(fav)[1])
            item.connect("activate", self.on_choose_fav_from_menu, fav)
            popup.append(item)
        popup.show_all()

    def on_choose_fav_from_menu(self, widget, filename):
        app.uri = filename
        app.draw_pixbuf(filename)
        app.handle_fav(filename)

class ExampleApp:

    def __init__(self):

        self.app = Gtk.Application.new("org.application.test", Gio.ApplicationFlags(0))
        self.app.connect("activate", self.on_app_activate)
        self.app.connect("shutdown", self.on_app_shutdown)

    def on_app_activate(self, app):
        builder = Gtk.Builder()
        builder.add_from_file("17_gsettings.glade")
        builder.connect_signals(Handler())
        self.obj = builder.get_object

        #load existing GSettings application config
        self.bg_setting = Gio.Settings.new("org.gnome.desktop.background")
        #get_value returns Gio formatted file path
        file = self.bg_setting.get_value("picture-uri")
        #convert path into string
        self.uri = file.get_string()[7:]
        #bind GSettings key to GTK+ object
        self.bg_setting.bind("show-desktop-icons", self.obj("switch"), "active", Gio.SettingsBindFlags.DEFAULT)

        #add GSettings schema from compiled XML file located in current directory (only recommended for test use, standard location: /usr/share/glib-2.0/schemas/)
        schema_source = Gio.SettingsSchemaSource.new_from_directory(os.getcwd(),
                Gio.SettingsSchemaSource.get_default(), False)
        schema = Gio.SettingsSchemaSource.lookup(schema_source,"org.example.wallpaper-changer", False)
        self.app_setting = Gio.Settings.new_full(schema, None, None)
        #convert value (GLib.Variant) into native list
        self.fav_list = self.app_setting.get_value("favourites").unpack()

        self.obj("window").set_application(app)
        self.obj("window").show_all()

        self.draw_pixbuf(self.uri)
        self.handle_fav(self.uri)

    def draw_pixbuf(self,file):
        size=self.obj("image_area").get_allocation()
        pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(file, size.width, size.height, True)
        self.obj("image_area").set_from_pixbuf(pixbuf)

    def handle_fav(self,uri):
        #set toggle button to correct state
        if uri in self.fav_list:
            self.obj("fav_button").set_active(True)
        else:
            self.obj("fav_button").set_active(False)

    def on_app_shutdown(self, app):
        self.app.quit()

    def run(self, argv):
        self.app.run(argv)


app = ExampleApp()
app.run(sys.argv)

Glade

17_gsettings.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkFileFilter" id="filefilter">
    <mime-types>
      <mime-type>image/*</mime-type>
    </mime-types>
  </object>
  <object class="GtkImage" id="image1">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="icon_name">emblem-favorite</property>
  </object>
  <object class="GtkMenu" id="menu">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
  </object>
  <object class="GtkApplicationWindow" id="window">
    <property name="can_focus">False</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <signal name="size-allocate" handler="on_window_size_allocate" swapped="no"/>
    <child>
      <object class="GtkImage" id="image_area">
        <property name="width_request">400</property>
        <property name="height_request">300</property>
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="stock">gtk-missing-image</property>
      </object>
    </child>
    <child type="titlebar">
      <object class="GtkHeaderBar">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="title">Wallpaper changer</property>
        <property name="has_subtitle">False</property>
        <property name="show_close_button">True</property>
        <child>
          <object class="GtkButton" id="open_button">
            <property name="label" translatable="yes">Open file...</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_open_button_clicked" swapped="no"/>
          </object>
        </child>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkLabel">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">Desktop icons</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkSwitch" id="switch">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="position">2</property>
          </packing>
        </child>
        <child>
          <object class="GtkMenuButton" id="fav_menu">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="popup">menu</property>
            <child>
              <placeholder/>
            </child>
          </object>
          <packing>
            <property name="pack_type">end</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="setwp_button">
            <property name="label" translatable="yes">Set as wallpaper</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_setwp_button_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="pack_type">end</property>
            <property name="position">3</property>
          </packing>
        </child>
        <child>
          <object class="GtkToggleButton" id="fav_button">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="image">image1</property>
            <property name="always_show_image">True</property>
            <signal name="toggled" handler="on_fav_button_toggled" swapped="no"/>
          </object>
          <packing>
            <property name="pack_type">end</property>
            <property name="position">3</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
  <object class="GtkImage" id="preview">
    <property name="width_request">200</property>
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <property name="margin_right">5</property>
  </object>
  <object class="GtkFileChooserDialog" id="filechooser_dialog">
    <property name="width_request">800</property>
    <property name="height_request">600</property>
    <property name="can_focus">False</property>
    <property name="type_hint">dialog</property>
    <property name="transient_for">window</property>
    <property name="attached_to">window</property>
    <property name="filter">filefilter</property>
    <property name="preview_widget">preview</property>
    <property name="use_preview_label">False</property>
    <signal name="delete-event" handler="on_dialog_close" swapped="no"/>
    <signal name="file-activated" handler="on_filechooser_dialog_file_activated" swapped="no"/>
    <signal name="response" handler="on_filechooser_dialog_response" swapped="no"/>
    <signal name="update-preview" handler="on_filechooser_dialog_update_preview" swapped="no"/>
    <child internal-child="vbox">
      <object class="GtkBox" id="fcbox">
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox">
            <property name="can_focus">False</property>
            <child>
              <placeholder/>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
    <child type="titlebar">
      <object class="GtkHeaderBar">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="title">Choose image file</property>
        <property name="show_close_button">True</property>
      </object>
    </child>
  </object>
</interface>

Kommentare

Comments powered by Disqus