changeset 1:2f04ec9e0506

Add preferences application Split settings into separate file.
author Guido Berhoerster <guido+pui@berhoerster.name>
date Fri, 08 Jun 2018 08:38:42 +0200
parents 6884bb8130ca
children 7172a0db97af
files Makefile org.guido-berhoerster.code.package-update-indicator.preferences.desktop.in package-update-indicator-prefs.1.xml package-update-indicator-prefs.c package-update-indicator.1.xml po/POTFILES.in pui-application.c pui-prefs-application.c pui-prefs-application.h pui-prefs-window.ui pui-prefs.gresource.xml pui-settings.c pui-settings.h
diffstat 13 files changed, 737 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Sun May 20 11:32:57 2018 +0200
+++ b/Makefile	Fri Jun 08 08:38:42 2018 +0200
@@ -23,6 +23,7 @@
 
 PACKAGE =	package-update-indicator
 APPLICATION_ID = org.guido-berhoerster.code.package-update-indicator
+PREFS_APPLICATION_ID = org.guido-berhoerster.code.package-update-indicator.preferences
 VERSION =	1
 DISTNAME =	$(PACKAGE)-$(VERSION)
 AUTHOR =	Guido Berhoerster
@@ -48,6 +49,7 @@
 GZIP :=		gzip
 SED :=		sed
 GLIB_COMPILE_SCHEMAS := $(shell pkg-config --variable=glib_compile_schemas gio-2.0)
+GLIB_COMPILE_RESOURCES := $(shell pkg-config --variable=glib_compile_resources gio-2.0)
 XSLTPROC :=	xsltproc
 DOCBOOK5_MANPAGES_STYLESHEET = http://docbook.sourceforge.net/release/xsl-ns/current/manpages/docbook.xsl
 DOCBOOK5_MANPAGES_FLAGS = --stringparam man.authors.section.enabled 0 \
@@ -91,32 +93,45 @@
 localedir ?=	$(datadir)/locale
 sysconfdir ?=	/etc
 xdgautostartdir ?= $(sysconfdir)/xdg/autostart
+xdgapplicationsdir ?= $(datadir)/applications
 
 OS_NAME :=	$(shell uname -s)
 OS_RELEASE :=	$(shell uname -r)
 
-OBJS =		package-update-indicator.o \
+$(PACKAGE)_OBJS = package-update-indicator.o \
 		pui-application.o \
 		pui-backend.o \
 		pui-get-updates.o \
+		pui-settings.o \
 		pui-types.o
 
+$(PACKAGE)-prefs_OBJS = package-update-indicator-prefs.o \
+		pui-prefs-application.o \
+		pui-settings.o \
+		pui-prefs-resources.o
+
+OBJS = $($(PACKAGE)_OBJS) $($(PACKAGE)-prefs_OBJS)
+
 GSETTINGS_SCHEMAS = $(APPLICATION_ID).gschema.xml
 
-AUTOSTART_FILES = $(APPLICATION_ID).desktop
+GRESOURCE_FILES = pui-prefs.gresource.xml
+
+AUTOSTART_FILE = $(APPLICATION_ID).desktop
+
+DESKTOP_FILES = $(PREFS_APPLICATION_ID).desktop
 
 LINGUAS :=	$(shell sed 's/\#.*//' po/LINGUAS)
 MOFILES :=	$(patsubst %,po/%.mo,$(LINGUAS))
 POTFILES_IN :=	$(shell sed 's/\#.*//' po/POTFILES.in)
 POTFILE =	po/$(PACKAGE).pot
 
-MANPAGES =	$(PACKAGE).1
+MANPAGES =	$(PACKAGE).1 $(PACKAGE)-prefs.1
 
 .DEFAULT_TARGET = all
 
 .PHONY: all pot update-po clean clobber dist install
 
-all: $(PACKAGE) $(AUTOSTART_FILES) $(MOFILES) $(MANPAGES)
+all: $(PACKAGE) $(PACKAGE)-prefs $(AUTOSTART_FILE) $(DESKTOP_FILES) $(MOFILES) $(MANPAGES)
 
 $(PACKAGE): XCPPFLAGS =	-DPACKAGE=\"$(PACKAGE)\" \
 			-DAPPLICATION_ID=\"$(APPLICATION_ID)\" \
@@ -124,12 +139,23 @@
 			-DG_LOG_DOMAIN=\"$(PACKAGE)\" \
 			-DPACKAGE_LOCALE_DIR="\"$(localedir)\"" \
 			-DGETTEXT_PACKAGE=\"$(PACKAGE)\" \
+			-DSETTINGS_SCHEMA_ID=\"$(APPLICATION_ID)\" \
 			-DI_KNOW_THE_PACKAGEKIT_GLIB2_API_IS_SUBJECT_TO_CHANGE
 $(PACKAGE): XCFLAGS =	$(shell pkg-config --cflags gtk+-3.0 \
 			    appindicator3-0.1 packagekit-glib2)
 $(PACKAGE): LDLIBS =	$(shell pkg-config --libs gtk+-3.0 \
 			    appindicator3-0.1 packagekit-glib2)
 
+$(PACKAGE)-prefs: XCPPFLAGS = -DPACKAGE=\"$(PACKAGE)\" \
+			-DAPPLICATION_ID=\"$(PREFS_APPLICATION_ID)\" \
+			-DVERSION=\"$(VERSION)\" \
+			-DG_LOG_DOMAIN=\"$(PACKAGE)\" \
+			-DPACKAGE_LOCALE_DIR="\"$(localedir)\"" \
+			-DGETTEXT_PACKAGE=\"$(PACKAGE)\" \
+			-DSETTINGS_SCHEMA_ID=\"$(APPLICATION_ID)\"
+$(PACKAGE)-prefs: XCFLAGS = $(shell pkg-config --cflags gtk+-3.0)
+$(PACKAGE)-prefs: LDLIBS = $(shell pkg-config --libs gtk+-3.0)
+
 ifneq ($(findstring $(OS_NAME),FreeBSD DragonFly OpenBSD),)
   $(PACKAGE): XCPPFLAGS +=	-I/usr/local/include
   $(PACKAGE): XLDFLAGS +=	-L/usr/local/lib
@@ -141,9 +167,19 @@
   $(PACKAGE): XCPPFLAGS +=	-D_XOPEN_SOURCE=600
 endif
 
-$(PACKAGE): $(OBJS)
+$(PACKAGE): $($(PACKAGE)_OBJS)
+	$(LINK.o) $^ $(LDLIBS) -o $@
+
+$(PACKAGE)-prefs: $($(PACKAGE)-prefs_OBJS)
 	$(LINK.o) $^ $(LDLIBS) -o $@
 
+%-resources.c: %.gresource.xml
+	$(GLIB_COMPILE_RESOURCES) --generate-dependencies $< | \
+	    while read -r resource_file; do \
+	        printf '%s: %s\n' $@ "$${resource_file}"; \
+	    done
+	$(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=. --generate-source $<
+
 $(POTFILE): po/POTFILES.in $(POTFILES_IN)
 	$(XGETTEXT) $(XGETTEXT_OPTIONS) --files-from $< --output $@
 
@@ -168,15 +204,19 @@
 
 install: all
 	$(INSTALL.exec) $(PACKAGE) "$(DESTDIR)$(bindir)/$(PACKAGE)"
+	$(INSTALL.exec) $(PACKAGE)-prefs \
+	    "$(DESTDIR)$(bindir)/$(PACKAGE)-prefs"
 	for schema in $(GSETTINGS_SCHEMAS); do \
 	    $(INSTALL.data) $${schema} \
 	        $(DESTDIR)$(datadir)/glib-2.0/schemas/$${schema}; \
 	done
 	[ -n "$(GSETTINGS_SCHEMAS)" ] && \
 	    $(GLIB_COMPILE_SCHEMAS) $(datadir)/glib-2.0/schemas
-	for desktop_file in $(AUTOSTART_FILES); do \
+	$(INSTALL.data) $(AUTOSTART_FILE) \
+	    $(DESTDIR)$(xdgautostartdir)/$(AUTOSTART_FILE)
+	for desktop_file in $(DESKTOP_FILES); do \
 	    $(INSTALL.data) $${desktop_file} \
-	        $(DESTDIR)$(xdgautostartdir)/$${desktop_file}; \
+	        $(DESTDIR)$(xdgapplicationsdir)/$${desktop_file}; \
 	done
 	for lang in $(LINGUAS); do \
 	   $(INSTALL.data) po/$${lang}.mo \
@@ -188,8 +228,8 @@
 	done
 
 clean:
-	rm -f $(PACKAGE) $(OBJS) $(AUTOSTART_FILES) $(POTFILE) $(MOFILES) \
-	    $(MANPAGES)
+	rm -f $(PACKAGE) $(OBJS) $(AUTOSTART_FILE) $(DESKTOP_FILES) $(POTFILE) \
+	    $(MOFILES) $(MANPAGES)
 
 clobber: clean
 	rm -f $(patsubst %.o,%.d,$(OBJS))
@@ -202,3 +242,4 @@
 -include local.mk
 
 -include $(patsubst %.o,%.d,$(OBJS))
+-include $(patsubst %.gresource.xml,%.gresource.d,$(GRESOURCE_FILES))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.guido-berhoerster.code.package-update-indicator.preferences.desktop.in	Fri Jun 08 08:38:42 2018 +0200
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Name=Package Update Indicator Preferences
+GenericName=Package Update Indicator Preferences
+Comment=Edit the Preferences for Package Update Indicator
+Icon=preferences-other
+Exec=package-update-indicator-prefs
+Terminal=false
+Type=Application
+Categories=Settings;PackageManager;GTK;
+NotShowIn=GNOME;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/package-update-indicator-prefs.1.xml	Fri Jun 08 08:38:42 2018 +0200
@@ -0,0 +1,106 @@
+<?xml version="1.0"?>
+<!--
+
+Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-->
+<refentry xmlns="http://docbook.org/ns/docbook" xml:lang="en">
+  <info>
+    <author>
+      <personname>
+        <firstname>Guido</firstname>
+        <surname>Berhoerster</surname>
+      </personname>
+      <email>guido+pui@berhoerster.name</email>
+      <personblurb/>
+    </author>
+    <date>5 June, 2018</date>
+  </info>
+  <refmeta>
+    <refentrytitle>package-update-indicator-prefs</refentrytitle>
+    <manvolnum>1</manvolnum>
+    <refmiscinfo class="source"/>
+    <refmiscinfo class="version"/>
+    <refmiscinfo class="manual">User Commands</refmiscinfo>
+  </refmeta>
+  <refnamediv>
+    <refname>package-update-indicator-prefs</refname>
+    <refpurpose>edit preferences for package-update-indicator</refpurpose>
+  </refnamediv>
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>package-update-indicator-prefs</command>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+  <refsect1>
+    <title>Description</title>
+    <para>The <command>package-update-indicator-prefs</command> utility can be
+    used to edit the preferences for
+    <command>package-update-indicator</command>.</para>
+  </refsect1>
+  <refsect1>
+    <title>Preferences</title>
+    <variablelist>
+      <varlistentry>
+        <term>Cache refresh interval</term>
+        <listitem>
+          <para>The interval in which package metadata is refreshed.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>Update installer command</term>
+        <listitem>
+          <para>The command for installing updates.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+  <refsect1>
+    <title>Exit Status</title>
+    <para>The following exit values are returned:</para>
+    <variablelist>
+      <varlistentry>
+        <term>0</term>
+        <listitem>
+          <para>Command successfully executed.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>1</term>
+        <listitem>
+          <para>An unspecified error has occured.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>2</term>
+        <listitem>
+          <para>Invalid command line options were specified.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+  <refsect1>
+    <title>See Also</title>
+    <para><citerefentry><refentrytitle>package-update-indicator</refentrytitle>
+      <manvolnum>1</manvolnum></citerefentry></para>
+  </refsect1>
+</refentry>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/package-update-indicator-prefs.c	Fri Jun 08 08:38:42 2018 +0200
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <locale.h>
+#include <stdlib.h>
+
+#include "pui-prefs-application.h"
+
+int
+main(int argc, char *argv[])
+{
+	int		status;
+	PuiPrefsApplication *application;
+
+	bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
+	setlocale(LC_ALL, "");
+	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+	textdomain(GETTEXT_PACKAGE);
+
+	g_set_application_name(_("Package Update Indicator Preferences"));
+
+	gtk_init(&argc, &argv);
+
+	application = pui_prefs_application_new();
+	status = g_application_run(G_APPLICATION(application), argc, argv);
+	g_object_unref(application);
+
+	exit(status);
+}
--- a/package-update-indicator.1.xml	Sun May 20 11:32:57 2018 +0200
+++ b/package-update-indicator.1.xml	Fri Jun 08 08:38:42 2018 +0200
@@ -33,7 +33,7 @@
       <email>guido+pui@berhoerster.name</email>
       <personblurb/>
     </author>
-    <date>25 May, 2018</date>
+    <date>5 June, 2018</date>
   </info>
   <refmeta>
     <refentrytitle>package-update-indicator</refentrytitle>
@@ -127,4 +127,10 @@
       </varlistentry>
     </variablelist>
   </refsect1>
+  <refsect1>
+    <title>See Also</title>
+    <para><citerefentry>
+      <refentrytitle>package-update-indicator-prefs</refentrytitle>
+      <manvolnum>1</manvolnum></citerefentry></para>
+  </refsect1>
 </refentry>
--- a/po/POTFILES.in	Sun May 20 11:32:57 2018 +0200
+++ b/po/POTFILES.in	Fri Jun 08 08:38:42 2018 +0200
@@ -1,7 +1,12 @@
 org.guido-berhoerster.code.package-update-indicator.desktop.in
 org.guido-berhoerster.code.package-update-indicator.gschema.xml
+org.guido-berhoerster.code.package-update-indicator.preferences.desktop.in
+package-update-indicator-prefs.c
 package-update-indicator.c
 pui-application.c
 pui-backend.c
 pui-get-updates.c
+pui-prefs-application.c
+pui-prefs-window.ui
+pui-settings.c
 pui-types.c
--- a/pui-application.c	Sun May 20 11:32:57 2018 +0200
+++ b/pui-application.c	Fri Jun 08 08:38:42 2018 +0200
@@ -21,21 +21,16 @@
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  */
 
-#define	G_SETTINGS_ENABLE_BACKEND
-#include <gio/gsettingsbackend.h>
 #include <glib/gi18n.h>
 #include <libappindicator/app-indicator.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "pui-common.h"
 #include "pui-application.h"
-#include "pui-types.h"
 #include "pui-backend.h"
-
-#define	SETTINGS_ROOT_PATH \
-    "/org/guido-berhoerster/code/package-update-indicator/"
-#define	SETTINGS_ROOT_GROUP	"General"
+#include "pui-common.h"
+#include "pui-settings.h"
+#include "pui-types.h"
 
 struct _PuiApplication {
 	GApplication	parent_instance;
@@ -410,8 +405,6 @@
 {
 	PuiApplication	*self = PUI_APPLICATION(application);
 	gsize		i;
-	gchar		*settings_filename;
-	GSettingsBackend *settings_backend;
 	GtkWidget	*menu;
 	GtkWidget	*menu_item;
 
@@ -428,12 +421,7 @@
 	}
 
 	/* create settings */
-	settings_filename = g_build_filename(g_get_user_config_dir(), PACKAGE,
-	    PACKAGE ".conf", NULL);
-	settings_backend = g_keyfile_settings_backend_new(settings_filename,
-	    SETTINGS_ROOT_PATH, SETTINGS_ROOT_GROUP);
-	self->settings = g_settings_new_with_backend(APPLICATION_ID,
-	    settings_backend);
+	self->settings = pui_settings_new();
 	g_settings_bind(self->settings, "update-command", self,
 	    "update-command", G_SETTINGS_BIND_GET);
 
@@ -468,9 +456,6 @@
 
 	/* keep GApplication running */
 	g_application_hold(application);
-
-	g_object_unref(settings_backend);
-	g_free(settings_filename);
 }
 
 static void
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pui-prefs-application.c	Fri Jun 08 08:38:42 2018 +0200
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#include "pui-prefs-application.h"
+#include "pui-settings.h"
+
+#define	COLUMN_REFRESH_INTERVAL	2
+
+struct _PuiPrefsApplication {
+	GtkApplication	parent_instance;
+	GSettings	*settings;
+};
+
+G_DEFINE_TYPE(PuiPrefsApplication, pui_prefs_application, GTK_TYPE_APPLICATION)
+
+static void	pui_prefs_application_quit(GSimpleAction *, GVariant *,
+    gpointer);
+
+static const GActionEntry pui_prefs_application_actions[] = {
+    { "quit", pui_prefs_application_quit }
+};
+
+static void
+pui_prefs_application_quit(GSimpleAction *simple, GVariant *parameter,
+    gpointer user_data)
+{
+	PuiPrefsApplication	*self = user_data;
+
+	g_application_quit(G_APPLICATION(self));
+}
+
+static gboolean
+map_refresh_interval_to_index(GValue *value, GVariant *variant,
+    gpointer user_data)
+{
+	GtkTreeModel	*tree_model = user_data;
+	guint32		setting_interval;
+	gint		index;
+	gboolean	iter_continue;
+	GtkTreeIter	iter = { 0 };
+	GValue		model_value = G_VALUE_INIT;
+	guint		model_interval;
+
+	setting_interval = g_variant_get_uint32(variant);
+
+	/* try to find a matching entry in the list */
+	for (iter_continue = gtk_tree_model_get_iter_first(tree_model, &iter),
+	    index = 0; iter_continue;
+	    iter_continue = gtk_tree_model_iter_next(tree_model, &iter),
+	    index++) {
+		gtk_tree_model_get_value(tree_model, &iter,
+		    COLUMN_REFRESH_INTERVAL, &model_value);
+		model_interval = g_value_get_uint(&model_value);
+		g_value_unset(&model_value);
+		if (setting_interval == model_interval) {
+			g_debug("mapping refresh-interval %" G_GUINT32_FORMAT
+			    " to index %d", setting_interval, index);
+			g_value_set_int(value, index);
+
+			return (TRUE);
+		}
+	}
+
+	g_debug("mapping refresh-interval %" G_GUINT32_FORMAT " to index -1",
+	    setting_interval);
+	g_value_set_int(value, -1);
+
+	return (TRUE);
+}
+
+static GVariant *
+map_index_to_refresh_interval(const GValue *value,
+    const GVariantType *expected_type, gpointer user_data)
+{
+	GtkTreeModel	*tree_model = GTK_TREE_MODEL(user_data);
+	gint		index;
+	GtkTreeIter	iter = { 0 };
+	GValue		model_value = G_VALUE_INIT;
+	guint		model_interval;
+
+	index = g_value_get_int(value);
+	if (!gtk_tree_model_iter_nth_child(tree_model, &iter, NULL, index)) {
+		return (NULL);
+	}
+
+	gtk_tree_model_get_value(tree_model, &iter, COLUMN_REFRESH_INTERVAL,
+	    &model_value);
+	model_interval = g_value_get_uint(&model_value);
+	g_debug("mapping index %d to refresh-interval value %" G_GUINT32_FORMAT,
+	    index, model_interval);
+	g_value_unset(&model_value);
+
+	return (g_variant_new_uint32(model_interval));
+}
+
+static void
+pui_prefs_application_startup(GApplication *application)
+{
+	PuiPrefsApplication *self = PUI_PREFS_APPLICATION(application);
+	GApplicationClass *application_class =
+	    G_APPLICATION_CLASS(pui_prefs_application_parent_class);
+	GtkBuilder	*builder;
+	GtkWidget	*window;
+	GtkTreeModel	*tree_model;
+	GtkWidget	*update_command_entry;
+	GtkWidget	*refresh_interval_combo_box;
+
+	application_class->startup(application);
+
+	/* create actions */
+	g_action_map_add_action_entries(G_ACTION_MAP(self),
+	    pui_prefs_application_actions,
+	    G_N_ELEMENTS(pui_prefs_application_actions), self);
+
+	/* get widgets from builder */
+	builder = gtk_builder_new_from_resource("/org/guido-berhoerster/code/"
+	    "package-update-indicator/preferences/pui-prefs-window.ui");
+	window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
+	gtk_application_add_window(GTK_APPLICATION(self), GTK_WINDOW(window));
+	update_command_entry = GTK_WIDGET(gtk_builder_get_object(builder,
+	    "update-command"));
+	refresh_interval_combo_box = GTK_WIDGET(gtk_builder_get_object(builder,
+	    "refresh-interval"));
+	tree_model =
+	    gtk_combo_box_get_model(GTK_COMBO_BOX(refresh_interval_combo_box));
+
+	/* bind settings to widgets */
+	self->settings = pui_settings_new();
+	g_settings_bind(self->settings, "update-command",
+	    update_command_entry, "text", G_SETTINGS_BIND_DEFAULT);
+	g_settings_bind_with_mapping(self->settings, "refresh-interval",
+	    refresh_interval_combo_box, "active", G_SETTINGS_BIND_DEFAULT,
+	    map_refresh_interval_to_index, map_index_to_refresh_interval,
+	    tree_model, NULL);
+
+	/* show window */
+	gtk_widget_show(window);
+	gtk_window_present(GTK_WINDOW(window));
+
+	g_object_unref(builder);
+}
+
+static void
+pui_prefs_application_activate(GApplication *application) {
+	GtkApplication	*gtk_application = GTK_APPLICATION(application);
+	GApplicationClass *application_class =
+	    G_APPLICATION_CLASS(pui_prefs_application_parent_class);
+
+	/* raise window when activated */
+	gtk_window_present(gtk_application_get_active_window(gtk_application));
+
+	application_class->activate(application);
+}
+
+static void
+pui_prefs_application_dispose(GObject *object)
+{
+	PuiPrefsApplication *self = PUI_PREFS_APPLICATION(object);
+
+	if (self->settings != NULL) {
+		g_clear_object(&self->settings);
+	}
+
+	G_OBJECT_CLASS(pui_prefs_application_parent_class)->dispose(object);
+}
+
+static void
+pui_prefs_application_class_init(PuiPrefsApplicationClass *klass)
+{
+	GObjectClass	*object_class = G_OBJECT_CLASS(klass);
+	GApplicationClass *application_class = G_APPLICATION_CLASS(klass);
+
+	object_class->dispose = pui_prefs_application_dispose;
+
+	application_class->startup = pui_prefs_application_startup;
+	application_class->activate = pui_prefs_application_activate;
+}
+
+static void
+pui_prefs_application_init(PuiPrefsApplication *self)
+{
+	/* do nothing, implementation required */
+}
+
+PuiPrefsApplication *
+pui_prefs_application_new(void)
+{
+	return (g_object_new(PUI_TYPE_PREFS_APPLICATION,
+	    "application-id", APPLICATION_ID, NULL));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pui-prefs-application.h	Fri Jun 08 08:38:42 2018 +0200
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef	PUI_PREFS_APPLICATION_H
+#define	PUI_PREFS_APPLICATION_H
+
+#include <gtk/gtk.h>
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define	PUI_TYPE_PREFS_APPLICATION	(pui_prefs_application_get_type())
+
+G_DECLARE_FINAL_TYPE(PuiPrefsApplication, pui_prefs_application, PUI,
+    PREFS_APPLICATION, GtkApplication)
+
+PuiPrefsApplication *	pui_prefs_application_new(void);
+
+G_END_DECLS
+
+#endif /* !PUI_PREFS_APPLICATION_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pui-prefs-window.ui	Fri Jun 08 08:38:42 2018 +0200
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkListStore" id="refresh-interval-list">
+    <columns>
+      <!-- column-name id -->
+      <column type="gchararray"/>
+      <!-- column-name title -->
+      <column type="gchararray"/>
+      <!-- column-name interval -->
+      <column type="guint"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0">always</col>
+        <col id="1" translatable="yes">always</col>
+        <col id="2">0</col>
+      </row>
+      <row>
+        <col id="0">one-hour</col>
+        <col id="1" translatable="yes">every hour</col>
+        <col id="2">3600</col>
+      </row>
+      <row>
+        <col id="0">eight-hours</col>
+        <col id="1" translatable="yes">every 8 hours</col>
+        <col id="2">28800</col>
+      </row>
+      <row>
+        <col id="0">twelve-hours</col>
+        <col id="1" translatable="yes">twice a day</col>
+        <col id="2">43200</col>
+      </row>
+      <row>
+        <col id="0">one-day</col>
+        <col id="1" translatable="yes">daily</col>
+        <col id="2">86400</col>
+      </row>
+      <row>
+        <col id="0">one-week</col>
+        <col id="1" translatable="yes">weekly</col>
+        <col id="2">604800</col>
+      </row>
+      <row>
+        <col id="0">never</col>
+        <col id="1" translatable="yes">never</col>
+        <col id="2">4294967295</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkWindow" id="window">
+    <property name="can_focus">False</property>
+    <property name="resizable">False</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin_left">18</property>
+        <property name="margin_right">18</property>
+        <property name="margin_top">18</property>
+        <property name="margin_bottom">18</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">18</property>
+        <child>
+          <object class="GtkGrid">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="row_spacing">18</property>
+            <property name="column_spacing">12</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">end</property>
+                <property name="label" translatable="yes">_Refresh cache:</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">end</property>
+                <property name="label" translatable="yes">_Command for installing updates:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">update-command</property>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="update-command">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="width_chars">32</property>
+                <signal name="changed" handler="on_command_changed" swapped="no"/>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBox" id="refresh-interval">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="model">refresh-interval-list</property>
+                <property name="id_column">0</property>
+                <child>
+                  <object class="GtkCellRendererText"/>
+                  <attributes>
+                    <attribute name="text">1</attribute>
+                  </attributes>
+                </child>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButtonBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton">
+                <property name="label">gtk-close</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="action_name">app.quit</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pui-prefs.gresource.xml	Fri Jun 08 08:38:42 2018 +0200
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/guido-berhoerster/code/package-update-indicator/preferences">
+    <file compressed="true">pui-prefs-window.ui</file>
+  </gresource>
+</gresources>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pui-settings.c	Fri Jun 08 08:38:42 2018 +0200
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#define	G_SETTINGS_ENABLE_BACKEND
+#include <gio/gsettingsbackend.h>
+
+#include "pui-settings.h"
+
+#define	SETTINGS_ROOT_PATH \
+    "/org/guido-berhoerster/code/package-update-indicator/"
+#define	SETTINGS_ROOT_GROUP	"General"
+
+GSettings *
+pui_settings_new(void)
+{
+	gchar		*settings_filename;
+	GSettingsBackend *settings_backend;
+
+	settings_filename = g_build_filename(g_get_user_config_dir(), PACKAGE,
+	    PACKAGE ".conf", NULL);
+	settings_backend = g_keyfile_settings_backend_new(settings_filename,
+	    SETTINGS_ROOT_PATH, SETTINGS_ROOT_GROUP);
+
+	return (g_settings_new_with_backend(SETTINGS_SCHEMA_ID,
+	    settings_backend));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pui-settings.h	Fri Jun 08 08:38:42 2018 +0200
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 Guido Berhoerster <guido+pui@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef	PUI_SETTINGS_H
+#define	PUI_SETTINGS_H
+
+#include <gio/gio.h>
+
+GSettings *	pui_settings_new(void);
+
+#endif /* !PUI_SETTINGS_H */