summaryrefslogtreecommitdiffstats
path: root/plugins/xsettings
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 14:51:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 14:51:55 +0000
commit86b7f1a83d7db9c912f32b29c32e1124c0a6454d (patch)
tree42a7ff7c6885e99e0669d07b104df11b2bf387b6 /plugins/xsettings
parentInitial commit. (diff)
downloadgnome-settings-daemon-86b7f1a83d7db9c912f32b29c32e1124c0a6454d.tar.xz
gnome-settings-daemon-86b7f1a83d7db9c912f32b29c32e1124c0a6454d.zip
Adding upstream version 3.38.2.upstream/3.38.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/xsettings')
-rwxr-xr-xplugins/xsettings/00-xrdb9
-rw-r--r--plugins/xsettings/README.xsettings35
-rw-r--r--plugins/xsettings/fc-monitor.c317
-rw-r--r--plugins/xsettings/fc-monitor.h36
-rw-r--r--plugins/xsettings/fontconfig-test/fonts.conf8
-rw-r--r--plugins/xsettings/gsd-remote-display-manager.h28
-rw-r--r--plugins/xsettings/gsd-xsettings-gtk.c384
-rw-r--r--plugins/xsettings/gsd-xsettings-gtk.h39
-rw-r--r--plugins/xsettings/gsd-xsettings-manager.c1588
-rw-r--r--plugins/xsettings/gsd-xsettings-manager.h38
-rw-r--r--plugins/xsettings/gtk-modules-test/canberra-gtk-module.desktop6
-rw-r--r--plugins/xsettings/gtk-modules-test/pk-gtk-module.desktop4
-rw-r--r--plugins/xsettings/main.c8
-rw-r--r--plugins/xsettings/meson.build66
-rw-r--r--plugins/xsettings/test-gtk-modules.c31
-rw-r--r--plugins/xsettings/test-wm-button-layout-translations.c54
-rwxr-xr-xplugins/xsettings/test.py209
-rw-r--r--plugins/xsettings/wm-button-layout-translation.c88
-rw-r--r--plugins/xsettings/wm-button-layout-translation.h26
-rw-r--r--plugins/xsettings/xsettings-common.c112
-rw-r--r--plugins/xsettings/xsettings-common.h65
-rw-r--r--plugins/xsettings/xsettings-manager.c393
-rw-r--r--plugins/xsettings/xsettings-manager.h58
23 files changed, 3602 insertions, 0 deletions
diff --git a/plugins/xsettings/00-xrdb b/plugins/xsettings/00-xrdb
new file mode 100755
index 0000000..a047e8b
--- /dev/null
+++ b/plugins/xsettings/00-xrdb
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+userresources=$HOME/.Xresources
+sysresources=/etc/X11/Xresources
+
+# merge in defaults
+[ -r "$sysresources" ] && xrdb -nocpp -merge "$sysresources"
+[ -r "$userresources" ] && xrdb -merge "$userresources"
+
diff --git a/plugins/xsettings/README.xsettings b/plugins/xsettings/README.xsettings
new file mode 100644
index 0000000..624ccab
--- /dev/null
+++ b/plugins/xsettings/README.xsettings
@@ -0,0 +1,35 @@
+This is very simple documentation for the 'override' GSettings key for
+gnome-setting-daemon's xsettings plugin.
+
+The override is given as a dictionary of overrides to be applied on top
+of the usual values that are exported to the X server as XSETTINGS. The
+intent of this is to allow users to override values of programmatically
+determined settings (such as 'Gtk/ShellShowsAppMenu') and to allow
+developers to introduce new XSETTINGS for testing (without having to kill the
+gnome-settings-daemon running in the session and run their own patched
+version).
+
+The type of the overrides is 'a{sv}'.
+
+The key gives the full XSETTINGS setting name to override (for example,
+'Gtk/ShellShowsAppMenu'). The value is one of the following:
+
+ - a string ('s') for the case of a string XSETTING
+
+ - an int32 ('i') for the case of an integer XSETTING
+
+ - a 4-tuple of uint16s ('(qqqq)') for the case of a color XSETTING
+
+Dictionary items with a value that is not one of the above types will be
+ignored. Specifically note that XSETTINGS does not have a concept of
+booleans -- you must use an integer that is either 0 or 1.
+
+An example setting for this key (as expressed in GVariant text format)
+might be:
+
+ { 'Gtk/ShellShowsAppMenu': < 0 >, 'Xft/DPI': < 98304 > }
+
+Noting that variants must be specified in the usual way (wrapped in <>).
+
+Note also that DPI in the above example is expressed in 1024ths of an
+inch.
diff --git a/plugins/xsettings/fc-monitor.c b/plugins/xsettings/fc-monitor.c
new file mode 100644
index 0000000..63e8712
--- /dev/null
+++ b/plugins/xsettings/fc-monitor.c
@@ -0,0 +1,317 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ * Copyright (C) 2017 Jan Alexander Steffens (heftig) <jan.steffens@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Behdad Esfahbod, Red Hat, Inc.
+ */
+
+#include "fc-monitor.h"
+
+#include <gio/gio.h>
+#include <fontconfig/fontconfig.h>
+
+#define TIMEOUT_MILLISECONDS 1000
+
+static void
+fontconfig_cache_update_thread (GTask *task,
+ gpointer source_object G_GNUC_UNUSED,
+ gpointer task_data G_GNUC_UNUSED,
+ GCancellable *cancellable G_GNUC_UNUSED)
+{
+ if (FcConfigUptoDate (NULL)) {
+ g_task_return_boolean (task, FALSE);
+ return;
+ }
+
+ if (!FcInitReinitialize ()) {
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "FcInitReinitialize failed");
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+fontconfig_cache_update_async (GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task = g_task_new (NULL, NULL, callback, user_data);
+ g_task_run_in_thread (task, fontconfig_cache_update_thread);
+ g_object_unref (task);
+}
+
+static gboolean
+fontconfig_cache_update_finish (GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+typedef enum {
+ UPDATE_IDLE,
+ UPDATE_PENDING,
+ UPDATE_RUNNING,
+ UPDATE_RESTART,
+} UpdateState;
+
+struct _FcMonitor {
+ GObject parent_instance;
+
+ GPtrArray *monitors;
+
+ guint timeout;
+ UpdateState state;
+ gboolean notify;
+};
+
+enum {
+ SIGNAL_UPDATED,
+
+ N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0, };
+
+static void fc_monitor_finalize (GObject *object);
+static void monitor_files (FcMonitor *self, FcStrList *list);
+static void stuff_changed (GFileMonitor *monitor, GFile *file, GFile *other_file,
+ GFileMonitorEvent event_type, gpointer data);
+static void start_timeout (FcMonitor *self);
+static gboolean start_update (gpointer data);
+static void update_done (GObject *source_object, GAsyncResult *result, gpointer user_data);
+
+G_DEFINE_TYPE (FcMonitor, fc_monitor, G_TYPE_OBJECT);
+
+static void
+fc_monitor_class_init (FcMonitorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = fc_monitor_finalize;
+
+ signals[SIGNAL_UPDATED] = g_signal_new ("updated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0);
+}
+
+FcMonitor *
+fc_monitor_new (void)
+{
+ return g_object_new (FC_TYPE_MONITOR, NULL);
+}
+
+static void
+fc_monitor_init (FcMonitor *self G_GNUC_UNUSED)
+{
+ FcInit ();
+}
+
+static void
+fc_monitor_finalize (GObject *object)
+{
+ FcMonitor *self = FC_MONITOR (object);
+
+ if (self->timeout)
+ g_source_remove (self->timeout);
+ self->timeout = 0;
+
+ g_clear_pointer (&self->monitors, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (fc_monitor_parent_class)->finalize (object);
+}
+
+void
+fc_monitor_start (FcMonitor *self)
+{
+ g_return_if_fail (FC_IS_MONITOR (self));
+ g_return_if_fail (self->monitors == NULL);
+
+ self->monitors = g_ptr_array_new_with_free_func (g_object_unref);
+
+ monitor_files (self, FcConfigGetConfigFiles (NULL));
+ monitor_files (self, FcConfigGetFontDirs (NULL));
+}
+
+void
+fc_monitor_stop (FcMonitor *self)
+{
+ g_return_if_fail (FC_IS_MONITOR (self));
+ g_clear_pointer (&self->monitors, g_ptr_array_unref);
+}
+
+static void
+monitor_files (FcMonitor *self,
+ FcStrList *list)
+{
+ const char *str;
+
+ while ((str = (const char *) FcStrListNext (list))) {
+ GFile *file;
+ GFileMonitor *monitor;
+
+ file = g_file_new_for_path (str);
+
+ monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, NULL, NULL);
+
+ g_object_unref (file);
+
+ if (!monitor)
+ continue;
+
+ g_signal_connect (monitor, "changed", G_CALLBACK (stuff_changed), self);
+
+ g_ptr_array_add (self->monitors, monitor);
+ }
+
+ FcStrListDone (list);
+}
+
+static const gchar *
+get_name (GType enum_type,
+ gint enum_value)
+{
+ GEnumClass *klass = g_type_class_ref (enum_type);
+ GEnumValue *value = g_enum_get_value (klass, enum_value);
+ const gchar *name = value ? value->value_name : "(unknown)";
+ g_type_class_unref (klass);
+ return name;
+}
+
+static void
+stuff_changed (GFileMonitor *monitor G_GNUC_UNUSED,
+ GFile *file G_GNUC_UNUSED,
+ GFile *other_file G_GNUC_UNUSED,
+ GFileMonitorEvent event_type,
+ gpointer data)
+{
+ FcMonitor *self = FC_MONITOR (data);
+ const gchar *event_name = get_name (G_TYPE_FILE_MONITOR_EVENT, event_type);
+
+ switch (self->state) {
+ case UPDATE_IDLE:
+ g_debug ("Got %-38s: starting fontconfig update timeout", event_name);
+ start_timeout (self);
+ break;
+
+ case UPDATE_PENDING:
+ /* wait for quiescence */
+ g_debug ("Got %-38s: restarting fontconfig update timeout", event_name);
+ g_source_remove (self->timeout);
+ start_timeout (self);
+ break;
+
+ case UPDATE_RUNNING:
+ g_debug ("Got %-38s: restarting fontconfig update", event_name);
+ self->state = UPDATE_RESTART;
+ break;
+
+ case UPDATE_RESTART:
+ g_debug ("Got %-38s: waiting on fontconfig update", event_name);
+ break;
+ }
+}
+
+static void
+start_timeout (FcMonitor *self)
+{
+ self->state = UPDATE_PENDING;
+ self->timeout = g_timeout_add (TIMEOUT_MILLISECONDS, start_update, self);
+ g_source_set_name_by_id (self->timeout, "[gnome-settings-daemon] update");
+}
+
+static gboolean
+start_update (gpointer data)
+{
+ FcMonitor *self = FC_MONITOR (data);
+
+ self->state = UPDATE_RUNNING;
+ self->timeout = 0;
+
+ g_debug ("Timeout completed: starting fontconfig update");
+ fontconfig_cache_update_async (update_done, g_object_ref (self));
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+update_done (GObject *source_object G_GNUC_UNUSED,
+ GAsyncResult *result,
+ gpointer data)
+{
+ FcMonitor *self = FC_MONITOR (data);
+ gboolean restart = self->state == UPDATE_RESTART;
+ GError *error = NULL;
+
+ self->state = UPDATE_IDLE;
+
+ if (fontconfig_cache_update_finish (result, &error)) {
+ g_debug ("Fontconfig update successful");
+ /* Remember we had a successful update even if we have to restart it */
+ self->notify = TRUE;
+ } else if (error) {
+ g_warning ("Fontconfig update failed: %s", error->message);
+ g_error_free (error);
+ } else
+ g_debug ("Fontconfig update was unnecessary");
+
+ if (restart) {
+ g_debug ("Concurrent change: restarting fontconfig update timeout");
+ start_timeout (self);
+ } else if (self->notify) {
+ self->notify = FALSE;
+
+ if (self->monitors) {
+ fc_monitor_stop (self);
+ fc_monitor_start (self);
+ }
+
+ /* we finish modifying self before emitting the signal,
+ * allowing the callback to stop us if it decides to. */
+ g_signal_emit (self, signals[SIGNAL_UPDATED], 0);
+ }
+
+ /* release ref taken in start_update */
+ g_object_unref (self);
+}
+
+#ifdef FONTCONFIG_MONITOR_TEST
+static void
+yay (void)
+{
+ g_message ("yay");
+}
+
+int
+main (void)
+{
+ GMainLoop *loop = g_main_loop_new (NULL, TRUE);
+ FcMonitor *monitor = fc_monitor_new ();
+
+ fc_monitor_start (monitor);
+ g_signal_connect (monitor, "updated", G_CALLBACK (yay), NULL);
+
+ g_main_loop_run (loop);
+ return 0;
+}
+#endif
diff --git a/plugins/xsettings/fc-monitor.h b/plugins/xsettings/fc-monitor.h
new file mode 100644
index 0000000..4b564f8
--- /dev/null
+++ b/plugins/xsettings/fc-monitor.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Jan Alexander Steffens (heftig) <jan.steffens@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#ifndef FC_MONITOR_H
+#define FC_MONITOR_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define FC_TYPE_MONITOR (fc_monitor_get_type ())
+G_DECLARE_FINAL_TYPE (FcMonitor, fc_monitor, FC, MONITOR, GObject)
+
+FcMonitor *fc_monitor_new (void);
+
+void fc_monitor_start (FcMonitor *monitor);
+void fc_monitor_stop (FcMonitor *monitor);
+
+G_END_DECLS
+
+#endif /* FC_MONITOR_H */
diff --git a/plugins/xsettings/fontconfig-test/fonts.conf b/plugins/xsettings/fontconfig-test/fonts.conf
new file mode 100644
index 0000000..f9236ea
--- /dev/null
+++ b/plugins/xsettings/fontconfig-test/fonts.conf
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+<!-- /etc/fonts/fonts.conf file to configure system font access -->
+<fontconfig>
+
+<!-- Font directory list -->
+ <dir>/usr/share/fonts</dir>
+</fontconfig>
diff --git a/plugins/xsettings/gsd-remote-display-manager.h b/plugins/xsettings/gsd-remote-display-manager.h
new file mode 100644
index 0000000..3c73ab6
--- /dev/null
+++ b/plugins/xsettings/gsd-remote-display-manager.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Bastien Nocera <hadess@hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#define GSD_TYPE_REMOTE_DISPLAY_MANAGER (gsd_remote_display_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdRemoteDisplayManager, gsd_remote_display_manager, GSD, REMOTE_DISPLAY_MANAGER, GObject)
+
+GsdRemoteDisplayManager * gsd_remote_display_manager_new (void);
diff --git a/plugins/xsettings/gsd-xsettings-gtk.c b/plugins/xsettings/gsd-xsettings-gtk.c
new file mode 100644
index 0000000..40baf41
--- /dev/null
+++ b/plugins/xsettings/gsd-xsettings-gtk.c
@@ -0,0 +1,384 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#include "gsd-xsettings-gtk.h"
+
+#define XSETTINGS_PLUGIN_SCHEMA "org.gnome.settings-daemon.plugins.xsettings"
+
+#define GTK_MODULES_DISABLED_KEY "disabled-gtk-modules"
+#define GTK_MODULES_ENABLED_KEY "enabled-gtk-modules"
+
+static const char *modules_path = NULL;
+
+enum {
+ PROP_0,
+ PROP_GTK_MODULES
+};
+
+struct _GsdXSettingsGtk {
+ GObject parent;
+
+ char *modules;
+ GHashTable *dir_modules;
+
+ GSettings *settings;
+
+ guint64 dir_mtime;
+ GFileMonitor *monitor;
+ GList *cond_settings;
+};
+
+G_DEFINE_TYPE(GsdXSettingsGtk, gsd_xsettings_gtk, G_TYPE_OBJECT)
+
+static void update_gtk_modules (GsdXSettingsGtk *gtk);
+
+static void
+empty_cond_settings_list (GsdXSettingsGtk *gtk)
+{
+ if (gtk->cond_settings == NULL)
+ return;
+
+ /* Empty the list of settings */
+ g_list_foreach (gtk->cond_settings, (GFunc) g_object_unref, NULL);
+ g_list_free (gtk->cond_settings);
+ gtk->cond_settings = NULL;
+}
+
+static void
+cond_setting_changed (GSettings *settings,
+ const char *key,
+ GsdXSettingsGtk *gtk)
+{
+ gboolean enabled;
+ const char *module_name;
+
+ module_name = g_object_get_data (G_OBJECT (settings), "module-name");
+
+ enabled = g_settings_get_boolean (settings, key);
+ if (enabled != FALSE) {
+ if (gtk->dir_modules == NULL)
+ gtk->dir_modules = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ g_hash_table_insert (gtk->dir_modules, g_strdup (module_name), NULL);
+ } else if (gtk->dir_modules != NULL) {
+ g_hash_table_remove (gtk->dir_modules, module_name);
+ }
+
+ update_gtk_modules (gtk);
+}
+
+static char *
+process_desktop_file (const char *path,
+ GsdXSettingsGtk *gtk)
+{
+ GKeyFile *keyfile;
+ char *retval;
+ char *module_name;
+
+ retval = NULL;
+
+ if (g_str_has_suffix (path, ".desktop") == FALSE &&
+ g_str_has_suffix (path, ".gtk-module") == FALSE)
+ return retval;
+
+ keyfile = g_key_file_new ();
+ if (g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL) == FALSE)
+ goto bail;
+
+ if (g_key_file_has_group (keyfile, "GTK Module") == FALSE)
+ goto bail;
+
+ module_name = g_key_file_get_string (keyfile, "GTK Module", "X-GTK-Module-Name", NULL);
+ if (module_name == NULL)
+ goto bail;
+
+ if (g_key_file_has_key (keyfile, "GTK Module", "X-GTK-Module-Enabled-Schema", NULL) != FALSE) {
+ char *schema;
+ char *key;
+ gboolean enabled;
+ GSettings *settings;
+ char *signal;
+
+ schema = g_key_file_get_string (keyfile, "GTK Module", "X-GTK-Module-Enabled-Schema", NULL);
+ key = g_key_file_get_string (keyfile, "GTK Module", "X-GTK-Module-Enabled-Key", NULL);
+
+ settings = g_settings_new (schema);
+
+ gtk->cond_settings = g_list_prepend (gtk->cond_settings, settings);
+
+ g_object_set_data_full (G_OBJECT (settings), "module-name", g_strdup (module_name), (GDestroyNotify) g_free);
+
+ signal = g_strdup_printf ("changed::%s", key);
+ g_signal_connect_object (G_OBJECT (settings), signal, G_CALLBACK (cond_setting_changed), gtk, 0);
+ enabled = g_settings_get_boolean (settings, key);
+ g_free (signal);
+ g_free (schema);
+ g_free (key);
+
+ if (enabled != FALSE)
+ retval = g_strdup (module_name);
+ } else {
+ retval = g_strdup (module_name);
+ }
+
+ g_free (module_name);
+
+bail:
+ g_key_file_free (keyfile);
+ return retval;
+}
+
+static void
+get_gtk_modules_from_dir (GsdXSettingsGtk *gtk)
+{
+ GFile *file;
+ GFileInfo *info;
+ GHashTable *ht;
+
+ file = g_file_new_for_path (modules_path);
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_TIME_MODIFIED,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+ if (info != NULL) {
+ guint64 dir_mtime;
+
+ dir_mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ if (gtk->dir_mtime == 0 ||
+ dir_mtime > gtk->dir_mtime) {
+ GDir *dir;
+ const char *name;
+
+ empty_cond_settings_list (gtk);
+
+ gtk->dir_mtime = dir_mtime;
+
+ if (gtk->dir_modules != NULL) {
+ g_hash_table_destroy (gtk->dir_modules);
+ gtk->dir_modules = NULL;
+ }
+
+ dir = g_dir_open (modules_path, 0, NULL);
+ if (dir == NULL)
+ goto bail;
+
+ ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ while ((name = g_dir_read_name (dir)) != NULL) {
+ char *path;
+ char *module;
+
+ path = g_build_filename (modules_path, name, NULL);
+ module = process_desktop_file (path, gtk);
+ if (module != NULL)
+ g_hash_table_insert (ht, module, NULL);
+ g_free (path);
+ }
+ g_dir_close (dir);
+
+ gtk->dir_modules = ht;
+ }
+ g_object_unref (info);
+ } else {
+ empty_cond_settings_list (gtk);
+ }
+
+bail:
+ g_object_unref (file);
+}
+
+static void
+stringify_gtk_modules (gpointer key,
+ gpointer value,
+ GString *str)
+{
+ if (str->len != 0)
+ g_string_append_c (str, ':');
+ g_string_append (str, key);
+}
+
+static void
+update_gtk_modules (GsdXSettingsGtk *gtk)
+{
+ char **enabled, **disabled;
+ GHashTable *ht;
+ guint i;
+ GString *str;
+ char *modules;
+
+ enabled = g_settings_get_strv (gtk->settings, GTK_MODULES_ENABLED_KEY);
+ disabled = g_settings_get_strv (gtk->settings, GTK_MODULES_DISABLED_KEY);
+
+ ht = g_hash_table_new (g_str_hash, g_str_equal);
+
+ if (gtk->dir_modules != NULL) {
+ GList *list, *l;
+
+ list = g_hash_table_get_keys (gtk->dir_modules);
+ for (l = list; l != NULL; l = l->next) {
+ g_hash_table_insert (ht, l->data, NULL);
+ }
+ g_list_free (list);
+ }
+
+ for (i = 0; enabled[i] != NULL; i++)
+ g_hash_table_insert (ht, enabled[i], NULL);
+
+ for (i = 0; disabled[i] != NULL; i++)
+ g_hash_table_remove (ht, disabled[i]);
+
+ str = g_string_new (NULL);
+ g_hash_table_foreach (ht, (GHFunc) stringify_gtk_modules, str);
+ g_hash_table_destroy (ht);
+
+ modules = g_string_free (str, FALSE);
+
+ if (modules == NULL ||
+ gtk->modules == NULL ||
+ g_str_equal (modules, gtk->modules) == FALSE) {
+ g_free (gtk->modules);
+ gtk->modules = modules;
+ g_object_notify (G_OBJECT (gtk), "gtk-modules");
+ } else {
+ g_free (modules);
+ }
+
+ g_strfreev (enabled);
+ g_strfreev (disabled);
+}
+
+static void
+gtk_modules_dir_changed_cb (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ GsdXSettingsGtk *gtk)
+{
+ get_gtk_modules_from_dir (gtk);
+ update_gtk_modules (gtk);
+}
+
+static void
+gsd_xsettings_gtk_init (GsdXSettingsGtk *gtk)
+{
+ GFile *file;
+
+ g_debug ("GsdXSettingsGtk initializing");
+
+ gtk->settings = g_settings_new (XSETTINGS_PLUGIN_SCHEMA);
+
+ modules_path = g_getenv ("GSD_gtk_modules_dir");
+ if (modules_path == NULL)
+ modules_path = GTK_MODULES_DIRECTORY;
+
+ get_gtk_modules_from_dir (gtk);
+
+ file = g_file_new_for_path (modules_path);
+ gtk->monitor = g_file_monitor (file,
+ G_FILE_MONITOR_NONE,
+ NULL,
+ NULL);
+ g_signal_connect (G_OBJECT (gtk->monitor), "changed",
+ G_CALLBACK (gtk_modules_dir_changed_cb), gtk);
+ g_object_unref (file);
+
+ update_gtk_modules (gtk);
+}
+
+static void
+gsd_xsettings_gtk_finalize (GObject *object)
+{
+ GsdXSettingsGtk *gtk;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSD_IS_XSETTINGS_GTK (object));
+
+ g_debug ("GsdXSettingsGtk finalizing");
+
+ gtk = GSD_XSETTINGS_GTK (object);
+
+ g_return_if_fail (gtk != NULL);
+
+ g_free (gtk->modules);
+ gtk->modules = NULL;
+
+ if (gtk->dir_modules != NULL) {
+ g_hash_table_destroy (gtk->dir_modules);
+ gtk->dir_modules = NULL;
+ }
+
+ g_object_unref (gtk->settings);
+
+ if (gtk->monitor != NULL)
+ g_object_unref (gtk->monitor);
+
+ empty_cond_settings_list (gtk);
+
+ G_OBJECT_CLASS (gsd_xsettings_gtk_parent_class)->finalize (object);
+}
+
+static void
+gsd_xsettings_gtk_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GsdXSettingsGtk *self;
+
+ self = GSD_XSETTINGS_GTK (object);
+
+ switch (prop_id) {
+ case PROP_GTK_MODULES:
+ g_value_set_string (value, self->modules);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gsd_xsettings_gtk_class_init (GsdXSettingsGtkClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = gsd_xsettings_gtk_get_property;
+ object_class->finalize = gsd_xsettings_gtk_finalize;
+
+ g_object_class_install_property (object_class, PROP_GTK_MODULES,
+ g_param_spec_string ("gtk-modules", NULL, NULL,
+ NULL, G_PARAM_READABLE));
+}
+
+GsdXSettingsGtk *
+gsd_xsettings_gtk_new (void)
+{
+ return GSD_XSETTINGS_GTK (g_object_new (GSD_TYPE_XSETTINGS_GTK, NULL));
+}
+
+const char *
+gsd_xsettings_gtk_get_modules (GsdXSettingsGtk *gtk)
+{
+ return gtk->modules;
+}
diff --git a/plugins/xsettings/gsd-xsettings-gtk.h b/plugins/xsettings/gsd-xsettings-gtk.h
new file mode 100644
index 0000000..13f6b88
--- /dev/null
+++ b/plugins/xsettings/gsd-xsettings-gtk.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Bastien Nocera <hadess@hadess.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_XSETTINGS_GTK_H__
+#define __GSD_XSETTINGS_GTK_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gmodule.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_XSETTINGS_GTK (gsd_xsettings_gtk_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdXSettingsGtk, gsd_xsettings_gtk, GSD, XSETTINGS_GTK, GObject)
+
+GsdXSettingsGtk *gsd_xsettings_gtk_new (void);
+
+const char * gsd_xsettings_gtk_get_modules (GsdXSettingsGtk *gtk);
+
+G_END_DECLS
+
+#endif /* __GSD_XSETTINGS_GTK_H__ */
diff --git a/plugins/xsettings/gsd-xsettings-manager.c b/plugins/xsettings/gsd-xsettings-manager.c
new file mode 100644
index 0000000..7b6fe43
--- /dev/null
+++ b/plugins/xsettings/gsd-xsettings-manager.c
@@ -0,0 +1,1588 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 Rodrigo Moya
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+
+#include <X11/Xatom.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "gnome-settings-profile.h"
+#include "gsd-enums.h"
+#include "gsd-xsettings-manager.h"
+#include "gsd-xsettings-gtk.h"
+#include "gnome-settings-bus.h"
+#include "xsettings-manager.h"
+#include "fc-monitor.h"
+#include "gsd-remote-display-manager.h"
+#include "wm-button-layout-translation.h"
+
+#define MOUSE_SETTINGS_SCHEMA "org.gnome.settings-daemon.peripherals.mouse"
+#define BACKGROUND_SETTINGS_SCHEMA "org.gnome.desktop.background"
+#define INTERFACE_SETTINGS_SCHEMA "org.gnome.desktop.interface"
+#define SOUND_SETTINGS_SCHEMA "org.gnome.desktop.sound"
+#define PRIVACY_SETTINGS_SCHEMA "org.gnome.desktop.privacy"
+#define WM_SETTINGS_SCHEMA "org.gnome.desktop.wm.preferences"
+#define A11Y_SCHEMA "org.gnome.desktop.a11y"
+#define CLASSIC_WM_SETTINGS_SCHEMA "org.gnome.shell.extensions.classic-overrides"
+
+#define XSETTINGS_PLUGIN_SCHEMA "org.gnome.settings-daemon.plugins.xsettings"
+#define XSETTINGS_OVERRIDE_KEY "overrides"
+
+#define GTK_MODULES_DISABLED_KEY "disabled-gtk-modules"
+#define GTK_MODULES_ENABLED_KEY "enabled-gtk-modules"
+
+#define TEXT_SCALING_FACTOR_KEY "text-scaling-factor"
+#define CURSOR_SIZE_KEY "cursor-size"
+#define CURSOR_THEME_KEY "cursor-theme"
+
+#define FONT_ANTIALIASING_KEY "antialiasing"
+#define FONT_HINTING_KEY "hinting"
+#define FONT_RGBA_ORDER_KEY "rgba-order"
+
+#define GTK_SETTINGS_DBUS_PATH "/org/gtk/Settings"
+#define GTK_SETTINGS_DBUS_NAME "org.gtk.Settings"
+
+static const gchar introspection_xml[] =
+"<node name='/org/gtk/Settings'>"
+" <interface name='org.gtk.Settings'>"
+" <property name='FontconfigTimestamp' type='x' access='read'/>"
+" <property name='Modules' type='s' access='read'/>"
+" <property name='EnableAnimations' type='b' access='read'/>"
+" </interface>"
+"</node>";
+
+/* As we cannot rely on the X server giving us good DPI information, and
+ * that we don't want multi-monitor screens to have different DPIs (thus
+ * different text sizes), we'll hard-code the value of the DPI
+ *
+ * See also:
+ * https://bugzilla.novell.com/show_bug.cgi?id=217790•
+ * https://bugzilla.gnome.org/show_bug.cgi?id=643704
+ *
+ * http://lists.fedoraproject.org/pipermail/devel/2011-October/157671.html
+ * Why EDID is not trustworthy for DPI
+ * Adam Jackson ajax at redhat.com
+ * Tue Oct 4 17:54:57 UTC 2011
+ *
+ * Previous message: GNOME 3 - font point sizes now scaled?
+ * Next message: Why EDID is not trustworthy for DPI
+ * Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
+ *
+ * On Tue, 2011-10-04 at 11:46 -0400, Kaleb S. KEITHLEY wrote:
+ *
+ * > Grovelling around in the F15 xorg-server sources and reviewing the Xorg
+ * > log file on my F15 box, I see, with _modern hardware_ at least, that we
+ * > do have the monitor geometry available from DDC or EDIC, and obviously
+ * > it is trivial to compute the actual, correct DPI for each screen.
+ *
+ * I am clearly going to have to explain this one more time, forever.
+ * Let's see if I can't write it authoritatively once and simply answer
+ * with a URL from here out. (As always, use of the second person "you"
+ * herein is plural, not singular.)
+ *
+ * EDID does not reliably give you the size of the display.
+ *
+ * Base EDID has at least two different places where you can give a
+ * physical size (before considering extensions that aren't widely deployed
+ * so whatever). The first is a global property, measured in centimeters,
+ * of the physical size of the glass. The second is attached to your (zero
+ * or more) detailed timing specifications, and reflects the size of the
+ * mode, in millimeters.
+ *
+ * So, how does this screw you?
+ *
+ * a) Glass size is too coarse. On a large display that cm roundoff isn't
+ * a big deal, but on subnotebooks it's a different game. The 11" MBA is
+ * 25.68x14.44 cm, so that gives you a range of 52.54-54.64 dpcm horizontal
+ * and 51.20-54.86 dpcm vertical (133.4-138.8 dpi h and 130.0-139.3 dpi v).
+ * Which is optimistic, because that's doing the math forward from knowing
+ * the actual size, and you as the EDID parser can't know which way the
+ * manufacturer rounded.
+ *
+ * b) Glass size need not be non-zero. This is in fact the usual case for
+ * projectors, which don't have a fixed display size since it's a function
+ * of how far away the wall is from the lens.
+ *
+ * c) Glass size could be partially non-zero. Yes, really. EDID 1.4
+ * defines a method of using these two bytes to encode aspect ratio, where
+ * if vertical size is 0 then the aspect ratio is computed as (horizontal
+ * value + 99) / 100 in portrait mode (and the obvious reverse thing if
+ * horizontal is zero). Admittedly, unlike every other item in this list,
+ * I've never seen this in the wild. But it's legal.
+ *
+ * d) Glass size could be a direct encoding of the aspect ratio. Base EDID
+ * doesn't condone this behaviour, but the CEA spec (to which all HDMI
+ * monitors must conform) does allow-but-not-require it, which means your
+ * 1920x1080 TV could claim to be 16 "cm" by 9 "cm". So of course that's
+ * what TV manufacturers do because that way they don't have to modify the
+ * EDID info when physical construction changes, and that's cheaper.
+ *
+ * e) You could use mode size to get size in millimeters, but you might not
+ * have any detailed timings.
+ *
+ * f) You could use mode size, but mode size is explicitly _not_ glass
+ * size. It's the size that the display chooses to present that mode.
+ * Sometimes those are the same, and sometimes they're not. You could be
+ * scaled or {letter,pillar}boxed, and that's not necessarily something you
+ * can control from the host side.
+ *
+ * g) You could use mode size, but it could be an encoded aspect ratio, as
+ * in case d above, because CEA says that's okay.
+ *
+ * h) You could use mode size, but it could be the aspect ratio from case d
+ * multiplied by 10 in each direction (because, of course, you gave size in
+ * centimeters and so your authoring tool just multiplied it up).
+ *
+ * i) Any or all of the above could be complete and utter garbage, because
+ * - and I really, really need you to understand this - there is no
+ * requirements program for any commercial OS or industry standard that
+ * requires honesty here, as far as I'm aware. There is every incentive
+ * for there to _never_ be one, because it would make the manufacturing
+ * process more expensive.
+ *
+ * So from this point the suggestion is usually "well come up with some
+ * heuristic to make a good guess assuming there's some correlation between
+ * the various numbers you're given". I have in fact written heuristics
+ * for this, and they're in your kernel and your X server, and they still
+ * encounter a huge number of cases where we simply _cannot_ know from EDID
+ * anything like a physical size, because - to pick only one example - the
+ * consumer electronics industry are cheap bastards, because you the
+ * consumer demanded that they be cheap.
+ *
+ * And then your only recourse is to an external database, and now you're
+ * up the creek again because the identifying information here is a
+ * vendor/model/serial tuple, and the vendor can and does change physical
+ * construction without changing model number. Now you get to play the
+ * guessing game of how big the serial number range is for each subvariant,
+ * assuming they bothered to encode a serial number - and they didn't. Or,
+ * if they bothered to encode week/year of manufacturer correctly - and
+ * they didn't - which weeks meant which models. And then you still have
+ * to go out and buy one of every TV at Fry's, and that covers you for one
+ * market, for three months.
+ *
+ * If someone wants to write something better, please, by all means. If
+ * it's kernel code, send it to dri-devel at lists.freedesktop.org and cc me
+ * and I will happily review it. Likewise xorg-devel@ for X server
+ * changes.
+ *
+ * I gently suggest that doing so is a waste of time.
+ *
+ * But if there's one thing free software has taught me, it's that you can
+ * not tell people something is a bad idea and have any expectation they
+ * will believe you.
+ *
+ * > Obviously in a multi-screen set-up using Xinerama this has the potential
+ * > to be a Hard Problem if the monitors differ greatly in their DPI.
+ * >
+ * > If the major resistance is over what to do with older hardware that
+ * > doesn't have this data available, then yes, punt; use a hard-coded
+ * > default. Likewise, if the two monitors really differ greatly, then punt.
+ *
+ * I'm going to limit myself to observing that "greatly" is a matter of
+ * opinion, and that in order to be really useful you'd need some way of
+ * communicating "I punted" to the desktop.
+ *
+ * Beyond that, sure, pick a heuristic, accept that it's going to be
+ * insufficient for someone, and then sit back and wait to get
+ * second-guessed on it over and over.
+ *
+ * > And it wouldn't be so hard to to add something like -dpi:0, -dpi:1,
+ * > -dpi:2 command line options to specify per-screen dpi. I kinda thought I
+ * > did that a long, long time ago, but maybe I only thought about doing it
+ * > and never actually got around to it.
+ *
+ * The RANDR extension as of version 1.2 does allow you to override
+ * physical size on a per-output basis at runtime. We even try pretty hard
+ * to set them as honestly as we can up front. The 96dpi thing people
+ * complain about is from the per-screen info, which is simply a default
+ * because of all the tl;dr above; because you have N outputs per screen
+ * which means a single number is in general useless; and because there is
+ * no way to refresh the per-screen info at runtime, as it's only ever sent
+ * in the initial connection handshake.
+ *
+ * - ajax
+ *
+ */
+#define DPI_FALLBACK 96
+
+typedef struct _TranslationEntry TranslationEntry;
+typedef void (* TranslationFunc) (GsdXSettingsManager *manager,
+ TranslationEntry *trans,
+ GVariant *value);
+
+struct _TranslationEntry {
+ const char *gsettings_schema;
+ const char *gsettings_key;
+ const char *xsetting_name;
+
+ TranslationFunc translate;
+};
+
+typedef struct _FixedEntry FixedEntry;
+typedef void (* FixedFunc) (GsdXSettingsManager *manager,
+ FixedEntry *fixed);
+typedef union {
+ const char *str;
+ int num;
+} FixedEntryValue;
+
+struct _FixedEntry {
+ const char *xsetting_name;
+ FixedFunc func;
+ FixedEntryValue val;
+};
+
+struct _GsdXSettingsManager
+{
+ GObject parent;
+
+ guint start_idle_id;
+ XSettingsManager *manager;
+ GHashTable *settings;
+
+ GSettings *plugin_settings;
+ FcMonitor *fontconfig_monitor;
+ gint64 fontconfig_timestamp;
+
+ GsdXSettingsGtk *gtk;
+
+ guint introspect_properties_changed_id;
+ guint shell_introspect_watch_id;
+ gboolean enable_animations;
+
+ guint display_config_watch_id;
+ guint monitors_changed_id;
+
+ guint shell_name_watch_id;
+ gboolean have_shell;
+
+ guint notify_idle_id;
+
+ GDBusNodeInfo *introspection_data;
+ GDBusConnection *dbus_connection;
+ guint gtk_settings_name_id;
+};
+
+#define GSD_XSETTINGS_ERROR gsd_xsettings_error_quark ()
+
+enum {
+ GSD_XSETTINGS_ERROR_INIT
+};
+
+static void gsd_xsettings_manager_class_init (GsdXSettingsManagerClass *klass);
+static void gsd_xsettings_manager_init (GsdXSettingsManager *xsettings_manager);
+static void gsd_xsettings_manager_finalize (GObject *object);
+
+static void register_manager_dbus (GsdXSettingsManager *manager);
+
+G_DEFINE_TYPE (GsdXSettingsManager, gsd_xsettings_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static GQuark
+gsd_xsettings_error_quark (void)
+{
+ return g_quark_from_static_string ("gsd-xsettings-error-quark");
+}
+
+static void
+translate_bool_int (GsdXSettingsManager *manager,
+ TranslationEntry *trans,
+ GVariant *value)
+{
+ xsettings_manager_set_int (manager->manager, trans->xsetting_name,
+ g_variant_get_boolean (value));
+}
+
+static void
+translate_int_int (GsdXSettingsManager *manager,
+ TranslationEntry *trans,
+ GVariant *value)
+{
+ xsettings_manager_set_int (manager->manager, trans->xsetting_name,
+ g_variant_get_int32 (value));
+}
+
+static void
+translate_string_string (GsdXSettingsManager *manager,
+ TranslationEntry *trans,
+ GVariant *value)
+{
+ xsettings_manager_set_string (manager->manager,
+ trans->xsetting_name,
+ g_variant_get_string (value, NULL));
+}
+
+static void
+translate_button_layout (GsdXSettingsManager *manager,
+ TranslationEntry *trans,
+ GVariant *value)
+{
+ GSettings *classic_settings;
+ GVariant *classic_value = NULL;
+ char *layout;
+
+ /* Hack: until we get session-dependent defaults in GSettings,
+ * swap out the usual schema for the "classic" one when
+ * running in classic mode
+ */
+ classic_settings = g_hash_table_lookup (manager->settings,
+ CLASSIC_WM_SETTINGS_SCHEMA);
+ if (classic_settings) {
+ classic_value = g_settings_get_value (classic_settings, "button-layout");
+ layout = g_variant_dup_string (classic_value, NULL);
+ } else {
+ layout = g_variant_dup_string (value, NULL);
+ }
+
+ translate_wm_button_layout_to_gtk (layout);
+
+ xsettings_manager_set_string (manager->manager,
+ trans->xsetting_name,
+ layout);
+
+ if (classic_value)
+ g_variant_unref (classic_value);
+ g_free (layout);
+}
+
+static void
+fixed_false_int (GsdXSettingsManager *manager,
+ FixedEntry *fixed)
+{
+ xsettings_manager_set_int (manager->manager, fixed->xsetting_name, FALSE);
+}
+
+static void
+fixed_true_int (GsdXSettingsManager *manager,
+ FixedEntry *fixed)
+{
+ xsettings_manager_set_int (manager->manager, fixed->xsetting_name, TRUE);
+}
+
+static void
+fixed_bus_id (GsdXSettingsManager *manager,
+ FixedEntry *fixed)
+{
+ const gchar *id;
+ GDBusConnection *bus;
+ GVariant *res;
+
+ bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+ res = g_dbus_connection_call_sync (bus,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetId",
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ NULL);
+
+ if (res) {
+ g_variant_get (res, "(&s)", &id);
+
+ xsettings_manager_set_string (manager->manager, fixed->xsetting_name, id);
+ g_variant_unref (res);
+ }
+
+ g_object_unref (bus);
+}
+
+static void
+fixed_string (GsdXSettingsManager *manager,
+ FixedEntry *fixed)
+{
+ xsettings_manager_set_string (manager->manager,
+ fixed->xsetting_name,
+ fixed->val.str);
+}
+
+static void
+fixed_int (GsdXSettingsManager *manager,
+ FixedEntry *fixed)
+{
+ xsettings_manager_set_int (manager->manager,
+ fixed->xsetting_name,
+ fixed->val.num);
+}
+
+#define DEFAULT_COLOR_PALETTE "black:white:gray50:red:purple:blue:light blue:green:yellow:orange:lavender:brown:goldenrod4:dodger blue:pink:light green:gray10:gray30:gray75:gray90"
+
+static FixedEntry fixed_entries [] = {
+ { "Gtk/MenuImages", fixed_false_int },
+ { "Gtk/ButtonImages", fixed_false_int },
+ { "Gtk/ShowInputMethodMenu", fixed_false_int },
+ { "Gtk/ShowUnicodeMenu", fixed_false_int },
+ { "Gtk/AutoMnemonics", fixed_true_int },
+ { "Gtk/DialogsUseHeader", fixed_true_int },
+ { "Gtk/SessionBusId", fixed_bus_id },
+ { "Gtk/ShellShowsAppMenu", fixed_false_int },
+ { "Gtk/ColorPalette", fixed_string, { .str = DEFAULT_COLOR_PALETTE } },
+ { "Net/FallbackIconTheme", fixed_string, { .str = "gnome" } },
+ { "Gtk/ToolbarStyle", fixed_string, { .str = "both-horiz" } },
+ { "Gtk/ToolbarIconSize", fixed_string, { .str = "large" } },
+ { "Gtk/CanChangeAccels", fixed_false_int },
+ { "Gtk/TimeoutInitial", fixed_int, { .num = 200 } },
+ { "Gtk/TimeoutRepeat", fixed_int, { .num = 20 } },
+ { "Gtk/ColorScheme", fixed_string, { .str = "" } },
+ { "Gtk/IMPreeditStyle", fixed_string, { .str = "callback" } },
+ { "Gtk/IMStatusStyle", fixed_string, { .str = "callback" } },
+ { "Gtk/MenuBarAccel", fixed_string, { .str = "F10" } }
+};
+
+static TranslationEntry translations [] = {
+ { "org.gnome.settings-daemon.peripherals.mouse", "double-click", "Net/DoubleClickTime", translate_int_int },
+ { "org.gnome.settings-daemon.peripherals.mouse", "drag-threshold", "Net/DndDragThreshold", translate_int_int },
+
+ { "org.gnome.desktop.background", "show-desktop-icons", "Gtk/ShellShowsDesktop", translate_bool_int },
+
+ { "org.gnome.desktop.interface", "font-name", "Gtk/FontName", translate_string_string },
+ { "org.gnome.desktop.interface", "gtk-key-theme", "Gtk/KeyThemeName", translate_string_string },
+ { "org.gnome.desktop.interface", "cursor-blink", "Net/CursorBlink", translate_bool_int },
+ { "org.gnome.desktop.interface", "cursor-blink-time", "Net/CursorBlinkTime", translate_int_int },
+ { "org.gnome.desktop.interface", "cursor-blink-timeout", "Gtk/CursorBlinkTimeout", translate_int_int },
+ { "org.gnome.desktop.interface", "gtk-theme", "Net/ThemeName", translate_string_string },
+ { "org.gnome.desktop.interface", "gtk-im-module", "Gtk/IMModule", translate_string_string },
+ { "org.gnome.desktop.interface", "icon-theme", "Net/IconThemeName", translate_string_string },
+ { "org.gnome.desktop.interface", "cursor-theme", "Gtk/CursorThemeName", translate_string_string },
+ { "org.gnome.desktop.interface", "gtk-enable-primary-paste", "Gtk/EnablePrimaryPaste", translate_bool_int },
+ { "org.gnome.desktop.interface", "overlay-scrolling", "Gtk/OverlayScrolling", translate_bool_int },
+ /* cursor-size is handled via the Xft side as it needs the scaling factor */
+
+ { "org.gnome.desktop.sound", "theme-name", "Net/SoundThemeName", translate_string_string },
+ { "org.gnome.desktop.sound", "event-sounds", "Net/EnableEventSounds" , translate_bool_int },
+ { "org.gnome.desktop.sound", "input-feedback-sounds", "Net/EnableInputFeedbackSounds", translate_bool_int },
+
+ { "org.gnome.desktop.privacy", "recent-files-max-age", "Gtk/RecentFilesMaxAge", translate_int_int },
+ { "org.gnome.desktop.privacy", "remember-recent-files", "Gtk/RecentFilesEnabled", translate_bool_int },
+ { "org.gnome.desktop.wm.preferences", "button-layout", "Gtk/DecorationLayout", translate_button_layout },
+ { "org.gnome.desktop.wm.preferences", "action-double-click-titlebar", "Gtk/TitlebarDoubleClick", translate_string_string },
+ { "org.gnome.desktop.wm.preferences", "action-middle-click-titlebar", "Gtk/TitlebarMiddleClick", translate_string_string },
+ { "org.gnome.desktop.wm.preferences", "action-right-click-titlebar", "Gtk/TitlebarRightClick", translate_string_string },
+ { "org.gnome.desktop.a11y", "always-show-text-caret", "Gtk/KeynavUseCaret", translate_bool_int }
+};
+
+static gboolean
+notify_idle (gpointer data)
+{
+ GsdXSettingsManager *manager = data;
+
+ xsettings_manager_notify (manager->manager);
+
+ manager->notify_idle_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+queue_notify (GsdXSettingsManager *manager)
+{
+ if (manager->notify_idle_id != 0)
+ return;
+
+ manager->notify_idle_id = g_idle_add (notify_idle, manager);
+ g_source_set_name_by_id (manager->notify_idle_id, "[gnome-settings-daemon] notify_idle");
+}
+
+typedef enum {
+ GTK_SETTINGS_FONTCONFIG_TIMESTAMP = 1 << 0,
+ GTK_SETTINGS_MODULES = 1 << 1,
+ GTK_SETTINGS_ENABLE_ANIMATIONS = 1 << 2
+} GtkSettingsMask;
+
+static void
+send_dbus_event (GsdXSettingsManager *manager,
+ GtkSettingsMask mask)
+{
+ GVariantBuilder props_builder;
+ GVariant *props_changed = NULL;
+
+ g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}"));
+
+ if (mask & GTK_SETTINGS_FONTCONFIG_TIMESTAMP) {
+ g_variant_builder_add (&props_builder, "{sv}", "FontconfigTimestamp",
+ g_variant_new_int64 (manager->fontconfig_timestamp));
+ }
+
+ if (mask & GTK_SETTINGS_MODULES) {
+ const char *modules = gsd_xsettings_gtk_get_modules (manager->gtk);
+ g_variant_builder_add (&props_builder, "{sv}", "Modules",
+ g_variant_new_string (modules ? modules : ""));
+ }
+
+ if (mask & GTK_SETTINGS_ENABLE_ANIMATIONS) {
+ g_variant_builder_add (&props_builder, "{sv}", "EnableAnimations",
+ g_variant_new_boolean (manager->enable_animations));
+ }
+
+ props_changed = g_variant_new ("(s@a{sv}@as)", GTK_SETTINGS_DBUS_NAME,
+ g_variant_builder_end (&props_builder),
+ g_variant_new_strv (NULL, 0));
+
+ g_dbus_connection_emit_signal (manager->dbus_connection,
+ NULL,
+ GTK_SETTINGS_DBUS_PATH,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ props_changed, NULL);
+}
+
+static double
+get_dpi_from_gsettings (GsdXSettingsManager *manager)
+{
+ GSettings *interface_settings;
+ double dpi;
+ double factor;
+
+ interface_settings = g_hash_table_lookup (manager->settings, INTERFACE_SETTINGS_SCHEMA);
+ factor = g_settings_get_double (interface_settings, TEXT_SCALING_FACTOR_KEY);
+
+ dpi = DPI_FALLBACK;
+
+ return dpi * factor;
+}
+
+static gboolean
+get_legacy_ui_scale (GVariantIter *properties,
+ int *scale)
+{
+ const char *key;
+ GVariant *value;
+
+ *scale = 0;
+
+ while (g_variant_iter_loop (properties, "{&sv}", &key, &value)) {
+ if (!g_str_equal (key, "legacy-ui-scaling-factor"))
+ continue;
+
+ *scale = g_variant_get_int32 (value);
+ break;
+ }
+
+ if (*scale < 1) {
+ g_warning ("Failed to get current UI legacy scaling factor");
+ *scale = 1;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#define MODE_FORMAT "(siiddada{sv})"
+#define MODES_FORMAT "a" MODE_FORMAT
+
+#define MONITOR_SPEC_FORMAT "(ssss)"
+#define MONITOR_FORMAT "(" MONITOR_SPEC_FORMAT MODES_FORMAT "a{sv})"
+#define MONITORS_FORMAT "a" MONITOR_FORMAT
+
+#define LOGICAL_MONITOR_FORMAT "(iiduba" MONITOR_SPEC_FORMAT "a{sv})"
+#define LOGICAL_MONITORS_FORMAT "a" LOGICAL_MONITOR_FORMAT
+
+#define CURRENT_STATE_FORMAT "(u" MONITORS_FORMAT LOGICAL_MONITORS_FORMAT "a{sv})"
+
+static int
+get_window_scale (GsdXSettingsManager *manager)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) current_state = NULL;
+ g_autoptr(GVariantIter) properties = NULL;
+ int scale = 1;
+
+ current_state =
+ g_dbus_connection_call_sync (manager->dbus_connection,
+ "org.gnome.Mutter.DisplayConfig",
+ "/org/gnome/Mutter/DisplayConfig",
+ "org.gnome.Mutter.DisplayConfig",
+ "GetCurrentState",
+ NULL,
+ NULL,
+ G_DBUS_CALL_FLAGS_NO_AUTO_START,
+ -1,
+ NULL,
+ &error);
+ if (!current_state) {
+ g_warning ("Failed to get current display configuration state: %s",
+ error->message);
+ return 1;
+ }
+
+ g_variant_get (current_state,
+ CURRENT_STATE_FORMAT,
+ NULL,
+ NULL,
+ NULL,
+ &properties);
+
+ if (!get_legacy_ui_scale (properties, &scale))
+ g_warning ("Failed to get current UI legacy scaling factor");
+
+ return scale;
+}
+
+typedef struct {
+ gboolean antialias;
+ gboolean hinting;
+ int scaled_dpi;
+ int dpi;
+ int window_scale;
+ int cursor_size;
+ char *cursor_theme;
+ const char *rgba;
+ const char *hintstyle;
+} GsdXftSettings;
+
+/* Read GSettings and determine the appropriate Xft settings based on them. */
+static void
+xft_settings_get (GsdXSettingsManager *manager,
+ GsdXftSettings *settings)
+{
+ GSettings *interface_settings;
+ GsdFontAntialiasingMode antialiasing;
+ GsdFontHinting hinting;
+ GsdFontRgbaOrder order;
+ gboolean use_rgba = FALSE;
+ double dpi;
+ int cursor_size;
+
+ interface_settings = g_hash_table_lookup (manager->settings, INTERFACE_SETTINGS_SCHEMA);
+
+ antialiasing = g_settings_get_enum (manager->plugin_settings, FONT_ANTIALIASING_KEY);
+ hinting = g_settings_get_enum (manager->plugin_settings, FONT_HINTING_KEY);
+ order = g_settings_get_enum (manager->plugin_settings, FONT_RGBA_ORDER_KEY);
+
+ settings->antialias = (antialiasing != GSD_FONT_ANTIALIASING_MODE_NONE);
+ settings->hinting = (hinting != GSD_FONT_HINTING_NONE);
+ settings->window_scale = get_window_scale (manager);
+ dpi = get_dpi_from_gsettings (manager);
+ settings->dpi = dpi * 1024; /* Xft wants 1/1024ths of an inch */
+ settings->scaled_dpi = dpi * settings->window_scale * 1024;
+ cursor_size = g_settings_get_int (interface_settings, CURSOR_SIZE_KEY);
+ settings->cursor_size = cursor_size * settings->window_scale;
+ settings->cursor_theme = g_settings_get_string (interface_settings, CURSOR_THEME_KEY);
+ settings->rgba = "rgb";
+ settings->hintstyle = "hintfull";
+
+ switch (hinting) {
+ case GSD_FONT_HINTING_NONE:
+ settings->hintstyle = "hintnone";
+ break;
+ case GSD_FONT_HINTING_SLIGHT:
+ settings->hintstyle = "hintslight";
+ break;
+ case GSD_FONT_HINTING_MEDIUM:
+ settings->hintstyle = "hintmedium";
+ break;
+ case GSD_FONT_HINTING_FULL:
+ settings->hintstyle = "hintfull";
+ break;
+ }
+
+ switch (order) {
+ case GSD_FONT_RGBA_ORDER_RGBA:
+ settings->rgba = "rgba";
+ break;
+ case GSD_FONT_RGBA_ORDER_RGB:
+ settings->rgba = "rgb";
+ break;
+ case GSD_FONT_RGBA_ORDER_BGR:
+ settings->rgba = "bgr";
+ break;
+ case GSD_FONT_RGBA_ORDER_VRGB:
+ settings->rgba = "vrgb";
+ break;
+ case GSD_FONT_RGBA_ORDER_VBGR:
+ settings->rgba = "vbgr";
+ break;
+ }
+
+ switch (antialiasing) {
+ case GSD_FONT_ANTIALIASING_MODE_NONE:
+ settings->antialias = 0;
+ break;
+ case GSD_FONT_ANTIALIASING_MODE_GRAYSCALE:
+ settings->antialias = 1;
+ break;
+ case GSD_FONT_ANTIALIASING_MODE_RGBA:
+ settings->antialias = 1;
+ use_rgba = TRUE;
+ }
+
+ if (!use_rgba) {
+ settings->rgba = "none";
+ }
+}
+
+static void
+xft_settings_clear (GsdXftSettings *settings)
+{
+ g_free (settings->cursor_theme);
+}
+
+static void
+xft_settings_set_xsettings (GsdXSettingsManager *manager,
+ GsdXftSettings *settings)
+{
+ gnome_settings_profile_start (NULL);
+
+ xsettings_manager_set_int (manager->manager, "Xft/Antialias", settings->antialias);
+ xsettings_manager_set_int (manager->manager, "Xft/Hinting", settings->hinting);
+ xsettings_manager_set_string (manager->manager, "Xft/HintStyle", settings->hintstyle);
+ xsettings_manager_set_int (manager->manager, "Gdk/WindowScalingFactor", settings->window_scale);
+ xsettings_manager_set_int (manager->manager, "Gdk/UnscaledDPI", settings->dpi);
+ xsettings_manager_set_int (manager->manager, "Xft/DPI", settings->scaled_dpi);
+ xsettings_manager_set_string (manager->manager, "Xft/RGBA", settings->rgba);
+ xsettings_manager_set_int (manager->manager, "Gtk/CursorThemeSize", settings->cursor_size);
+ xsettings_manager_set_string (manager->manager, "Gtk/CursorThemeName", settings->cursor_theme);
+
+ gnome_settings_profile_end (NULL);
+}
+
+static void
+update_property (GString *props, const gchar* key, const gchar* value)
+{
+ gchar* needle;
+ size_t needle_len;
+ gchar* found = NULL;
+
+ /* update an existing property */
+ needle = g_strconcat (key, ":", NULL);
+ needle_len = strlen (needle);
+ if (g_str_has_prefix (props->str, needle))
+ found = props->str;
+ else
+ found = strstr (props->str, needle);
+
+ if (found) {
+ size_t value_index;
+ gchar* end;
+
+ end = strchr (found, '\n');
+ value_index = (found - props->str) + needle_len + 1;
+ g_string_erase (props, value_index, end ? (end - found - needle_len) : -1);
+ g_string_insert (props, value_index, "\n");
+ g_string_insert (props, value_index, value);
+ } else {
+ g_string_append_printf (props, "%s:\t%s\n", key, value);
+ }
+
+ g_free (needle);
+}
+
+static void
+xft_settings_set_xresources (GsdXftSettings *settings)
+{
+ GString *add_string;
+ char dpibuf[G_ASCII_DTOSTR_BUF_SIZE];
+ Display *dpy;
+
+ gnome_settings_profile_start (NULL);
+
+ /* get existing properties */
+ dpy = XOpenDisplay (NULL);
+ g_return_if_fail (dpy != NULL);
+ add_string = g_string_new (XResourceManagerString (dpy));
+
+ g_debug("xft_settings_set_xresources: orig res '%s'", add_string->str);
+
+ g_snprintf (dpibuf, sizeof (dpibuf), "%d", (int) (settings->scaled_dpi / 1024.0 + 0.5));
+ update_property (add_string, "Xft.dpi", dpibuf);
+ update_property (add_string, "Xft.antialias",
+ settings->antialias ? "1" : "0");
+ update_property (add_string, "Xft.hinting",
+ settings->hinting ? "1" : "0");
+ update_property (add_string, "Xft.hintstyle",
+ settings->hintstyle);
+ update_property (add_string, "Xft.rgba",
+ settings->rgba);
+ update_property (add_string, "Xcursor.size",
+ g_ascii_dtostr (dpibuf, sizeof (dpibuf), (double) settings->cursor_size));
+ update_property (add_string, "Xcursor.theme",
+ settings->cursor_theme);
+
+ g_debug("xft_settings_set_xresources: new res '%s'", add_string->str);
+
+ /* Set the new X property */
+ XChangeProperty(dpy, RootWindow (dpy, 0),
+ XA_RESOURCE_MANAGER, XA_STRING, 8, PropModeReplace, (const unsigned char *) add_string->str, add_string->len);
+ XCloseDisplay (dpy);
+
+ g_string_free (add_string, TRUE);
+
+ gnome_settings_profile_end (NULL);
+}
+
+/* We mirror the Xft properties both through XSETTINGS and through
+ * X resources
+ */
+static void
+update_xft_settings (GsdXSettingsManager *manager)
+{
+ GsdXftSettings settings;
+
+ gnome_settings_profile_start (NULL);
+
+ xft_settings_get (manager, &settings);
+ xft_settings_set_xsettings (manager, &settings);
+ xft_settings_set_xresources (&settings);
+ xft_settings_clear (&settings);
+
+ gnome_settings_profile_end (NULL);
+}
+
+static void
+xft_callback (GSettings *settings,
+ const gchar *key,
+ GsdXSettingsManager *manager)
+{
+ update_xft_settings (manager);
+ queue_notify (manager);
+}
+
+static void
+override_callback (GSettings *settings,
+ const gchar *key,
+ GsdXSettingsManager *manager)
+{
+ GVariant *value;
+
+ value = g_settings_get_value (settings, XSETTINGS_OVERRIDE_KEY);
+
+ xsettings_manager_set_overrides (manager->manager, value);
+ queue_notify (manager);
+
+ g_variant_unref (value);
+}
+
+static void
+plugin_callback (GSettings *settings,
+ const char *key,
+ GsdXSettingsManager *manager)
+{
+ if (g_str_equal (key, GTK_MODULES_DISABLED_KEY) ||
+ g_str_equal (key, GTK_MODULES_ENABLED_KEY)) {
+ /* Do nothing, as GsdXsettingsGtk will handle it */
+ } else if (g_str_equal (key, XSETTINGS_OVERRIDE_KEY)) {
+ override_callback (settings, key, manager);
+ } else {
+ xft_callback (settings, key, manager);
+ }
+}
+
+static void
+gtk_modules_callback (GsdXSettingsGtk *gtk,
+ GParamSpec *spec,
+ GsdXSettingsManager *manager)
+{
+ const char *modules = gsd_xsettings_gtk_get_modules (manager->gtk);
+
+ if (modules == NULL) {
+ xsettings_manager_delete_setting (manager->manager, "Gtk/Modules");
+ } else {
+ g_debug ("Setting GTK modules '%s'", modules);
+ xsettings_manager_set_string (manager->manager,
+ "Gtk/Modules",
+ modules);
+ }
+
+ queue_notify (manager);
+ send_dbus_event (manager, GTK_SETTINGS_MODULES);
+}
+
+static void
+fontconfig_callback (FcMonitor *monitor,
+ GsdXSettingsManager *manager)
+{
+ gint64 timestamp = g_get_real_time ();
+ gint timestamp_sec = (int)(timestamp / G_TIME_SPAN_SECOND);
+
+ gnome_settings_profile_start (NULL);
+
+ xsettings_manager_set_int (manager->manager, "Fontconfig/Timestamp", timestamp_sec);
+
+ manager->fontconfig_timestamp = timestamp;
+
+ queue_notify (manager);
+ send_dbus_event (manager, GTK_SETTINGS_FONTCONFIG_TIMESTAMP);
+ gnome_settings_profile_end (NULL);
+}
+
+static gboolean
+start_fontconfig_monitor_idle_cb (GsdXSettingsManager *manager)
+{
+ gnome_settings_profile_start (NULL);
+
+ fc_monitor_start (manager->fontconfig_monitor);
+
+ gnome_settings_profile_end (NULL);
+
+ manager->start_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+start_fontconfig_monitor (GsdXSettingsManager *manager)
+{
+ gnome_settings_profile_start (NULL);
+
+ manager->fontconfig_monitor = fc_monitor_new ();
+ g_signal_connect (manager->fontconfig_monitor, "updated", G_CALLBACK (fontconfig_callback), manager);
+
+ manager->start_idle_id = g_idle_add ((GSourceFunc) start_fontconfig_monitor_idle_cb, manager);
+ g_source_set_name_by_id (manager->start_idle_id, "[gnome-settings-daemon] start_fontconfig_monitor_idle_cb");
+
+ gnome_settings_profile_end (NULL);
+}
+
+static void
+process_value (GsdXSettingsManager *manager,
+ TranslationEntry *trans,
+ GVariant *value)
+{
+ (* trans->translate) (manager, trans, value);
+}
+
+static TranslationEntry *
+find_translation_entry (GSettings *settings, const char *key)
+{
+ guint i;
+ char *schema;
+
+ g_object_get (settings, "schema-id", &schema, NULL);
+
+ if (g_str_equal (schema, CLASSIC_WM_SETTINGS_SCHEMA)) {
+ g_free (schema);
+ schema = g_strdup (WM_SETTINGS_SCHEMA);
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (translations); i++) {
+ if (g_str_equal (schema, translations[i].gsettings_schema) &&
+ g_str_equal (key, translations[i].gsettings_key)) {
+ g_free (schema);
+ return &translations[i];
+ }
+ }
+
+ g_free (schema);
+
+ return NULL;
+}
+
+static void
+xsettings_callback (GSettings *settings,
+ const char *key,
+ GsdXSettingsManager *manager)
+{
+ TranslationEntry *trans;
+ GVariant *value;
+
+ if (g_str_equal (key, TEXT_SCALING_FACTOR_KEY) ||
+ g_str_equal (key, CURSOR_SIZE_KEY) ||
+ g_str_equal (key, CURSOR_THEME_KEY)) {
+ xft_callback (NULL, key, manager);
+ return;
+ }
+
+ trans = find_translation_entry (settings, key);
+ if (trans == NULL) {
+ return;
+ }
+
+ value = g_settings_get_value (settings, key);
+
+ process_value (manager, trans, value);
+
+ g_variant_unref (value);
+
+ queue_notify (manager);
+}
+
+static void
+terminate_cb (void *data)
+{
+ gboolean *terminated = data;
+
+ if (*terminated) {
+ return;
+ }
+
+ *terminated = TRUE;
+ g_warning ("X Settings Manager is terminating");
+ gtk_main_quit ();
+}
+
+static gboolean
+setup_xsettings_managers (GsdXSettingsManager *manager)
+{
+ GdkDisplay *display;
+ gboolean res;
+ gboolean terminated;
+
+ display = gdk_display_get_default ();
+
+ res = xsettings_manager_check_running (gdk_x11_display_get_xdisplay (display),
+ gdk_x11_screen_get_screen_number (gdk_screen_get_default ()));
+
+ if (res) {
+ g_warning ("You can only run one xsettings manager at a time; exiting");
+ return FALSE;
+ }
+
+ terminated = FALSE;
+ manager->manager = xsettings_manager_new (gdk_x11_display_get_xdisplay (display),
+ gdk_x11_screen_get_screen_number (gdk_screen_get_default ()),
+ terminate_cb,
+ &terminated);
+ if (! manager->manager) {
+ g_warning ("Could not create xsettings manager!");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+monitors_changed (GsdXSettingsManager *manager)
+{
+ update_xft_settings (manager);
+ queue_notify (manager);
+}
+
+static void
+on_monitors_changed (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer data)
+{
+ GsdXSettingsManager *manager = data;
+ monitors_changed (manager);
+}
+
+static void
+on_display_config_name_appeared_handler (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer data)
+{
+ GsdXSettingsManager *manager = data;
+ monitors_changed (manager);
+}
+
+static void
+animations_enabled_changed (GsdXSettingsManager *manager)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) res = NULL;
+ g_autoptr(GVariant) animations_enabled_variant = NULL;
+ gboolean animations_enabled;
+
+ res = g_dbus_connection_call_sync (manager->dbus_connection,
+ "org.gnome.Shell.Introspect",
+ "/org/gnome/Shell/Introspect",
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)",
+ "org.gnome.Shell.Introspect",
+ "AnimationsEnabled"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (!res) {
+ g_warning ("Failed to get animations-enabled state: %s",
+ error->message);
+ return;
+ }
+
+ g_variant_get (res, "(v)", &animations_enabled_variant);
+ g_variant_get (animations_enabled_variant, "b", &animations_enabled);
+
+ if (manager->enable_animations == animations_enabled)
+ return;
+
+ manager->enable_animations = animations_enabled;
+ xsettings_manager_set_int (manager->manager, "Gtk/EnableAnimations",
+ animations_enabled);
+ queue_notify (manager);
+ send_dbus_event (manager, GTK_SETTINGS_ENABLE_ANIMATIONS);
+}
+
+static void
+on_introspect_properties_changed (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer data)
+{
+ GsdXSettingsManager *manager = data;
+ animations_enabled_changed (manager);
+}
+
+static void
+on_shell_introspect_name_appeared_handler (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer data)
+{
+ GsdXSettingsManager *manager = data;
+ animations_enabled_changed (manager);
+}
+
+static void
+launch_xwayland_services_on_dir (const gchar *path)
+{
+ GFileEnumerator *enumerator;
+ GError *error = NULL;
+ GList *l, *scripts = NULL;
+ GFile *dir;
+
+ g_debug ("launch_xwayland_services_on_dir: %s", path);
+
+ dir = g_file_new_for_path (path);
+ enumerator = g_file_enumerate_children (dir,
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, &error);
+ g_object_unref (dir);
+
+ if (!enumerator) {
+ if (!g_error_matches (error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND)) {
+ g_warning ("Error opening '%s': %s",
+ path, error->message);
+ }
+
+ g_error_free (error);
+ return;
+ }
+
+ while (TRUE) {
+ GFileInfo *info;
+ GFile *child;
+
+ if (!g_file_enumerator_iterate (enumerator,
+ &info, &child,
+ NULL, &error)) {
+ g_warning ("Error iterating on '%s': %s",
+ path, error->message);
+ g_error_free (error);
+ break;
+ }
+
+ if (!info)
+ break;
+
+ if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR ||
+ !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
+ continue;
+
+ scripts = g_list_prepend (scripts, g_file_get_path (child));
+ }
+
+ scripts = g_list_sort (scripts, (GCompareFunc) strcmp);
+
+ for (l = scripts; l; l = l->next) {
+ gchar *args[2] = { l->data, NULL };
+
+ g_debug ("launch_xwayland_services_on_dir: Spawning '%s'", args[0]);
+ if (!g_spawn_sync (NULL, args, NULL,
+ G_SPAWN_DEFAULT,
+ NULL, NULL,
+ NULL, NULL, NULL,
+ &error)) {
+ g_warning ("Error when spawning '%s': %s",
+ args[0], error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ g_object_unref (enumerator);
+ g_list_free_full (scripts, g_free);
+}
+
+static void
+launch_xwayland_services (void)
+{
+ const gchar * const * config_dirs;
+ gint i;
+
+ config_dirs = g_get_system_config_dirs ();
+
+ for (i = 0; config_dirs[i] != NULL; i++) {
+ gchar *config_dir;
+
+ config_dir = g_build_filename (config_dirs[i],
+ "Xwayland-session.d",
+ NULL);
+
+ launch_xwayland_services_on_dir (config_dir);
+ g_free (config_dir);
+ }
+}
+
+gboolean
+gsd_xsettings_manager_start (GsdXSettingsManager *manager,
+ GError **error)
+{
+ GVariant *overrides;
+ guint i;
+ GList *list, *l;
+ const char *session;
+
+ g_debug ("Starting xsettings manager");
+ gnome_settings_profile_start (NULL);
+
+ if (!setup_xsettings_managers (manager)) {
+ g_set_error (error, GSD_XSETTINGS_ERROR,
+ GSD_XSETTINGS_ERROR_INIT,
+ "Could not initialize xsettings manager.");
+ return FALSE;
+ }
+
+ manager->monitors_changed_id =
+ g_dbus_connection_signal_subscribe (manager->dbus_connection,
+ "org.gnome.Mutter.DisplayConfig",
+ "org.gnome.Mutter.DisplayConfig",
+ "MonitorsChanged",
+ "/org/gnome/Mutter/DisplayConfig",
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ on_monitors_changed,
+ manager,
+ NULL);
+ manager->display_config_watch_id =
+ g_bus_watch_name_on_connection (manager->dbus_connection,
+ "org.gnome.Mutter.DisplayConfig",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ on_display_config_name_appeared_handler,
+ NULL,
+ manager,
+ NULL);
+
+ manager->introspect_properties_changed_id =
+ g_dbus_connection_signal_subscribe (manager->dbus_connection,
+ "org.gnome.Shell.Introspect",
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ "/org/gnome/Shell/Introspect",
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ on_introspect_properties_changed,
+ manager,
+ NULL);
+ manager->shell_introspect_watch_id =
+ g_bus_watch_name_on_connection (manager->dbus_connection,
+ "org.gnome.Shell.Introspect",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ on_shell_introspect_name_appeared_handler,
+ NULL,
+ manager,
+ NULL);
+
+ manager->settings = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) g_object_unref);
+
+ g_hash_table_insert (manager->settings,
+ MOUSE_SETTINGS_SCHEMA, g_settings_new (MOUSE_SETTINGS_SCHEMA));
+ g_hash_table_insert (manager->settings,
+ BACKGROUND_SETTINGS_SCHEMA, g_settings_new (BACKGROUND_SETTINGS_SCHEMA));
+ g_hash_table_insert (manager->settings,
+ INTERFACE_SETTINGS_SCHEMA, g_settings_new (INTERFACE_SETTINGS_SCHEMA));
+ g_hash_table_insert (manager->settings,
+ SOUND_SETTINGS_SCHEMA, g_settings_new (SOUND_SETTINGS_SCHEMA));
+ g_hash_table_insert (manager->settings,
+ PRIVACY_SETTINGS_SCHEMA, g_settings_new (PRIVACY_SETTINGS_SCHEMA));
+ g_hash_table_insert (manager->settings,
+ WM_SETTINGS_SCHEMA, g_settings_new (WM_SETTINGS_SCHEMA));
+ g_hash_table_insert (manager->settings,
+ A11Y_SCHEMA, g_settings_new (A11Y_SCHEMA));
+
+ session = g_getenv ("XDG_CURRENT_DESKTOP");
+ if (session && strstr (session, "GNOME-Classic")) {
+ GSettingsSchema *schema;
+
+ schema = g_settings_schema_source_lookup (g_settings_schema_source_get_default (),
+ CLASSIC_WM_SETTINGS_SCHEMA, FALSE);
+ if (schema) {
+ g_hash_table_insert (manager->settings,
+ CLASSIC_WM_SETTINGS_SCHEMA,
+ g_settings_new_full (schema, NULL, NULL));
+ g_settings_schema_unref (schema);
+ }
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (fixed_entries); i++) {
+ FixedEntry *fixed = &fixed_entries[i];
+ (* fixed->func) (manager, fixed);
+ }
+
+ list = g_hash_table_get_values (manager->settings);
+ for (l = list; l != NULL; l = l->next) {
+ g_signal_connect_object (G_OBJECT (l->data), "changed", G_CALLBACK (xsettings_callback), manager, 0);
+ }
+ g_list_free (list);
+
+ for (i = 0; i < G_N_ELEMENTS (translations); i++) {
+ GVariant *val;
+ GSettings *settings;
+
+ settings = g_hash_table_lookup (manager->settings,
+ translations[i].gsettings_schema);
+ if (settings == NULL) {
+ g_warning ("Schemas '%s' has not been setup", translations[i].gsettings_schema);
+ continue;
+ }
+
+ val = g_settings_get_value (settings, translations[i].gsettings_key);
+
+ process_value (manager, &translations[i], val);
+ g_variant_unref (val);
+ }
+
+ /* Plugin settings (GTK modules and Xft) */
+ manager->plugin_settings = g_settings_new (XSETTINGS_PLUGIN_SCHEMA);
+ g_signal_connect_object (manager->plugin_settings, "changed", G_CALLBACK (plugin_callback), manager, 0);
+
+ manager->gtk = gsd_xsettings_gtk_new ();
+ g_signal_connect (G_OBJECT (manager->gtk), "notify::gtk-modules",
+ G_CALLBACK (gtk_modules_callback), manager);
+ gtk_modules_callback (manager->gtk, NULL, manager);
+
+ /* Xft settings */
+ update_xft_settings (manager);
+
+ /* Launch Xwayland services */
+ if (gnome_settings_is_wayland ())
+ launch_xwayland_services ();
+
+ register_manager_dbus (manager);
+
+ start_fontconfig_monitor (manager);
+
+ overrides = g_settings_get_value (manager->plugin_settings, XSETTINGS_OVERRIDE_KEY);
+ xsettings_manager_set_overrides (manager->manager, overrides);
+ queue_notify (manager);
+ g_variant_unref (overrides);
+
+
+ gnome_settings_profile_end (NULL);
+
+ return TRUE;
+}
+
+void
+gsd_xsettings_manager_stop (GsdXSettingsManager *manager)
+{
+ g_debug ("Stopping xsettings manager");
+
+ if (manager->introspect_properties_changed_id) {
+ g_dbus_connection_signal_unsubscribe (manager->dbus_connection,
+ manager->introspect_properties_changed_id);
+ manager->introspect_properties_changed_id = 0;
+ }
+
+ if (manager->shell_introspect_watch_id) {
+ g_bus_unwatch_name (manager->shell_introspect_watch_id);
+ manager->shell_introspect_watch_id = 0;
+ }
+
+ if (manager->monitors_changed_id) {
+ g_dbus_connection_signal_unsubscribe (manager->dbus_connection,
+ manager->monitors_changed_id);
+ manager->monitors_changed_id = 0;
+ }
+
+ if (manager->display_config_watch_id) {
+ g_bus_unwatch_name (manager->display_config_watch_id);
+ manager->display_config_watch_id = 0;
+ }
+
+ if (manager->shell_name_watch_id > 0) {
+ g_bus_unwatch_name (manager->shell_name_watch_id);
+ manager->shell_name_watch_id = 0;
+ }
+
+ if (manager->manager != NULL) {
+ xsettings_manager_destroy (manager->manager);
+ manager->manager = NULL;
+ }
+
+ if (manager->plugin_settings != NULL) {
+ g_signal_handlers_disconnect_by_data (manager->plugin_settings, manager);
+ g_object_unref (manager->plugin_settings);
+ manager->plugin_settings = NULL;
+ }
+
+ if (manager->gtk_settings_name_id > 0) {
+ g_bus_unown_name (manager->gtk_settings_name_id);
+ manager->gtk_settings_name_id = 0;
+ }
+
+ if (manager->fontconfig_monitor != NULL) {
+ g_signal_handlers_disconnect_by_data (manager->fontconfig_monitor, manager);
+ fc_monitor_stop (manager->fontconfig_monitor);
+ g_object_unref (manager->fontconfig_monitor);
+ manager->fontconfig_monitor = NULL;
+ }
+
+ if (manager->settings != NULL) {
+ g_hash_table_destroy (manager->settings);
+ manager->settings = NULL;
+ }
+
+ if (manager->gtk != NULL) {
+ g_object_unref (manager->gtk);
+ manager->gtk = NULL;
+ }
+}
+
+static void
+gsd_xsettings_manager_class_init (GsdXSettingsManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsd_xsettings_manager_finalize;
+}
+
+static void
+gsd_xsettings_manager_init (GsdXSettingsManager *manager)
+{
+ GError *error = NULL;
+
+ manager->dbus_connection = g_bus_get_sync (G_BUS_TYPE_SESSION,
+ NULL, &error);
+ if (!manager->dbus_connection)
+ g_error ("Failed to get session bus: %s", error->message);
+}
+
+static void
+gsd_xsettings_manager_finalize (GObject *object)
+{
+ GsdXSettingsManager *xsettings_manager;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GSD_IS_XSETTINGS_MANAGER (object));
+
+ xsettings_manager = GSD_XSETTINGS_MANAGER (object);
+
+ g_return_if_fail (xsettings_manager != NULL);
+
+ gsd_xsettings_manager_stop (xsettings_manager);
+
+ if (xsettings_manager->start_idle_id != 0)
+ g_source_remove (xsettings_manager->start_idle_id);
+
+ g_clear_object (&xsettings_manager->dbus_connection);
+
+ G_OBJECT_CLASS (gsd_xsettings_manager_parent_class)->finalize (object);
+}
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ GsdXSettingsManager *manager = user_data;
+
+ if (g_strcmp0 (property_name, "FontconfigTimestamp") == 0) {
+ return g_variant_new_int64 (manager->fontconfig_timestamp);
+ } else if (g_strcmp0 (property_name, "Modules") == 0) {
+ const char *modules = gsd_xsettings_gtk_get_modules (manager->gtk);
+ return g_variant_new_string (modules ? modules : "");
+ } else if (g_strcmp0 (property_name, "EnableAnimations") == 0) {
+ return g_variant_new_boolean (manager->enable_animations);
+ } else {
+ g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "No such interface: %s", interface_name);
+ return NULL;
+ }
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+ NULL,
+ handle_get_property,
+ NULL
+};
+
+static void
+register_manager_dbus (GsdXSettingsManager *manager)
+{
+ g_assert (manager->dbus_connection != NULL);
+
+ manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ g_assert (manager->introspection_data != NULL);
+
+ g_dbus_connection_register_object (manager->dbus_connection,
+ GTK_SETTINGS_DBUS_PATH,
+ manager->introspection_data->interfaces[0],
+ &interface_vtable,
+ manager,
+ NULL,
+ NULL);
+
+ manager->gtk_settings_name_id = g_bus_own_name_on_connection (manager->dbus_connection,
+ GTK_SETTINGS_DBUS_NAME,
+ G_BUS_NAME_OWNER_FLAGS_NONE,
+ NULL, NULL, NULL, NULL);
+}
+
+GsdXSettingsManager *
+gsd_xsettings_manager_new (void)
+{
+ if (manager_object != NULL) {
+ g_object_ref (manager_object);
+ } else {
+ manager_object = g_object_new (GSD_TYPE_XSETTINGS_MANAGER, NULL);
+ g_object_add_weak_pointer (manager_object,
+ (gpointer *) &manager_object);
+ }
+
+ return GSD_XSETTINGS_MANAGER (manager_object);
+}
diff --git a/plugins/xsettings/gsd-xsettings-manager.h b/plugins/xsettings/gsd-xsettings-manager.h
new file mode 100644
index 0000000..96fff13
--- /dev/null
+++ b/plugins/xsettings/gsd-xsettings-manager.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __GSD_XSETTINGS_MANAGER_H
+#define __GSD_XSETTINGS_MANAGER_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_XSETTINGS_MANAGER (gsd_xsettings_manager_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsdXSettingsManager, gsd_xsettings_manager, GSD, XSETTINGS_MANAGER, GObject)
+
+GsdXSettingsManager * gsd_xsettings_manager_new (void);
+gboolean gsd_xsettings_manager_start (GsdXSettingsManager *manager,
+ GError **error);
+void gsd_xsettings_manager_stop (GsdXSettingsManager *manager);
+
+G_END_DECLS
+
+#endif /* __GNOME_XSETTINGS_MANAGER_H */
diff --git a/plugins/xsettings/gtk-modules-test/canberra-gtk-module.desktop b/plugins/xsettings/gtk-modules-test/canberra-gtk-module.desktop
new file mode 100644
index 0000000..80a087b
--- /dev/null
+++ b/plugins/xsettings/gtk-modules-test/canberra-gtk-module.desktop
@@ -0,0 +1,6 @@
+[GTK Module]
+Name=canberra-gtk-module
+Description=Event Sound Module
+X-GTK-Module-Name=canberra-gtk-module
+X-GTK-Module-Enabled-Schema=org.gnome.desktop.sound
+X-GTK-Module-Enabled-Key=event-sounds
diff --git a/plugins/xsettings/gtk-modules-test/pk-gtk-module.desktop b/plugins/xsettings/gtk-modules-test/pk-gtk-module.desktop
new file mode 100644
index 0000000..f13cf0f
--- /dev/null
+++ b/plugins/xsettings/gtk-modules-test/pk-gtk-module.desktop
@@ -0,0 +1,4 @@
+[GTK Module]
+Name=PackageKit
+Description=PackageKit Font Installer
+X-GTK-Module-Name=pk-gtk-module
diff --git a/plugins/xsettings/main.c b/plugins/xsettings/main.c
new file mode 100644
index 0000000..65d9a20
--- /dev/null
+++ b/plugins/xsettings/main.c
@@ -0,0 +1,8 @@
+#define NEW gsd_xsettings_manager_new
+#define START gsd_xsettings_manager_start
+#define STOP gsd_xsettings_manager_stop
+#define MANAGER GsdXSettingsManager
+#define GDK_BACKEND "x11"
+#include "gsd-xsettings-manager.h"
+
+#include "daemon-skeleton-gtk.h"
diff --git a/plugins/xsettings/meson.build b/plugins/xsettings/meson.build
new file mode 100644
index 0000000..f91b963
--- /dev/null
+++ b/plugins/xsettings/meson.build
@@ -0,0 +1,66 @@
+gsd_xsettings_gtk = files('gsd-xsettings-gtk.c')
+
+fc_monitor = files('fc-monitor.c')
+
+wm_button_layout_translation = files('wm-button-layout-translation.c')
+
+sources = gsd_xsettings_gtk + fc_monitor + wm_button_layout_translation + files(
+ 'gsd-xsettings-manager.c',
+ 'xsettings-common.c',
+ 'xsettings-manager.c',
+ 'main.c'
+)
+
+deps = plugins_deps + [
+ gtk_dep,
+ x11_dep,
+ dependency('fontconfig')
+]
+
+cflags += ['-DGTK_MODULES_DIRECTORY="@0@"'.format(join_paths(gsd_pkglibdir, 'gtk-modules'))]
+
+executable(
+ 'gsd-' + plugin_name,
+ sources,
+ include_directories: [top_inc, common_inc, data_inc],
+ dependencies: deps,
+ c_args: cflags,
+ install: true,
+ install_rpath: gsd_pkglibdir,
+ install_dir: gsd_libexecdir
+)
+
+programs = [
+ ['test-gtk-modules', gsd_xsettings_gtk + ['test-gtk-modules.c'], cflags],
+ ['test-fontconfig-monitor', fc_monitor, cflags + ['-DFONTCONFIG_MONITOR_TEST']],
+ ['test-wm-button-layout-translations', wm_button_layout_translation + ['test-wm-button-layout-translations.c'], []]
+]
+
+foreach program: programs
+ executable(
+ program[0],
+ program[1],
+ include_directories: top_inc,
+ dependencies: deps,
+ c_args: program[2]
+ )
+endforeach
+
+install_data(
+ files('00-xrdb'),
+ install_dir: join_paths(gsd_sysconfdir, 'xdg/Xwayland-session.d')
+)
+
+test_py = find_program('test.py')
+
+envs = [
+ 'BUILDDIR=' + meson.current_build_dir(),
+ 'TOP_BUILDDIR=' + meson.build_root()
+]
+
+test(
+ 'test-xsettings',
+ test_py,
+ env: envs,
+ timeout: 300
+)
diff --git a/plugins/xsettings/test-gtk-modules.c b/plugins/xsettings/test-gtk-modules.c
new file mode 100644
index 0000000..ef83fc3
--- /dev/null
+++ b/plugins/xsettings/test-gtk-modules.c
@@ -0,0 +1,31 @@
+
+
+#include "gsd-xsettings-gtk.h"
+
+static void
+gtk_modules_callback (GsdXSettingsGtk *gtk,
+ GParamSpec *spec,
+ gpointer user_data)
+{
+ const char *modules;
+
+ modules = gsd_xsettings_gtk_get_modules (gtk);
+ g_message ("GTK+ modules list changed to: %s", modules ? modules : "(empty)");
+}
+
+int main (int argc, char **argv)
+{
+ GMainLoop *loop;
+ GsdXSettingsGtk *gtk;
+
+ gtk = gsd_xsettings_gtk_new ();
+ g_signal_connect (G_OBJECT (gtk), "notify::gtk-modules",
+ G_CALLBACK (gtk_modules_callback), NULL);
+
+ gtk_modules_callback (gtk, NULL, NULL);
+
+ loop = g_main_loop_new (NULL, TRUE);
+ g_main_loop_run (loop);
+
+ return 0;
+}
diff --git a/plugins/xsettings/test-wm-button-layout-translations.c b/plugins/xsettings/test-wm-button-layout-translations.c
new file mode 100644
index 0000000..5ab140a
--- /dev/null
+++ b/plugins/xsettings/test-wm-button-layout-translations.c
@@ -0,0 +1,54 @@
+#include <glib.h>
+
+#include "wm-button-layout-translation.h"
+
+static void
+test_button_layout_translations (void)
+{
+ static struct {
+ char *layout;
+ char *expected;
+ } tests[] = {
+ { "", "" },
+ { "invalid", "" },
+
+ { ":", ":" },
+ { ":invalid", ":" },
+ { "invalid:", ":" },
+ { "invalid:invalid", ":" },
+
+ { "appmenu", "menu" },
+ { "appmenu:", "menu:" },
+ { ":menu", ":icon" },
+ { "appmenu:close", "menu:close" },
+ { "appmenu:minimize,maximize,close", "menu:minimize,maximize,close" },
+ { "menu,appmenu:minimize,maximize,close", "icon,menu:minimize,maximize,close" },
+
+ { "close,close,close:close,close,close", "close,close,close:close,close,close" },
+
+ { "invalid,appmenu:invalid,minimize", "menu:minimize" },
+ { "appmenu,invalid:minimize,invalid", "menu:minimize" },
+ { "invalidmenu:invalidclose", ":" },
+ { "invalid,invalid,invalid:invalid,minimize,maximize,close", ":minimize,maximize,close" },
+ };
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (tests); i++)
+ {
+ char *layout = g_strdup (tests[i].layout);
+
+ translate_wm_button_layout_to_gtk (layout);
+ g_assert_cmpstr (layout, ==, tests[i].expected);
+ g_free (layout);
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/layout-translations", test_button_layout_translations);
+
+ return g_test_run ();
+}
diff --git a/plugins/xsettings/test.py b/plugins/xsettings/test.py
new file mode 100755
index 0000000..0b3a44d
--- /dev/null
+++ b/plugins/xsettings/test.py
@@ -0,0 +1,209 @@
+#!/usr/bin/python3
+'''GNOME settings daemon tests for xsettings plugin.'''
+
+__author__ = 'Bastien Nocera <hadess@hadess.net>'
+__copyright__ = '(C) 2018 Red Hat, Inc.'
+__license__ = 'GPL v2 or later'
+
+import unittest
+import subprocess
+import sys
+import time
+import os
+import os.path
+import signal
+import shutil
+
+project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+builddir = os.environ.get('BUILDDIR', os.path.dirname(__file__))
+
+sys.path.insert(0, os.path.join(project_root, 'tests'))
+sys.path.insert(0, builddir)
+import gsdtestcase
+import dbus
+import dbusmock
+
+from gi.repository import Gio
+from gi.repository import GLib
+
+class XsettingsPluginTest(gsdtestcase.GSDTestCase):
+ '''Test the xsettings plugin'''
+
+ def setUp(self):
+ self.start_logind()
+
+ self.daemon_death_expected = False
+ self.session_log_write = open(os.path.join(self.workdir, 'gnome-session.log'), 'wb')
+ self.session = subprocess.Popen(['gnome-session', '-f',
+ '-a', os.path.join(self.workdir, 'autostart'),
+ '--session=dummy', '--debug'],
+ stdout=self.session_log_write,
+ stderr=subprocess.STDOUT)
+
+ # wait until the daemon is on the bus
+ try:
+ self.wait_for_bus_object('org.gnome.SessionManager',
+ '/org/gnome/SessionManager')
+ except:
+ # on failure, print log
+ with open(self.session_log_write.name) as f:
+ print('----- session log -----\n%s\n------' % f.read())
+ raise
+
+ self.session_log = open(self.session_log_write.name)
+
+ self.obj_session_mgr = self.session_bus_con.get_object(
+ 'org.gnome.SessionManager', '/org/gnome/SessionManager')
+
+ self.start_mutter()
+
+ Gio.Settings.sync()
+ self.plugin_log_write = open(os.path.join(self.workdir, 'plugin_xsettings.log'), 'wb', buffering=0)
+ os.environ['GSD_ignore_llvmpipe'] = '1'
+
+ # Setup fontconfig config path before starting the daemon
+ self.fc_dir = os.path.join(self.workdir, 'fontconfig')
+ os.environ['FONTCONFIG_PATH'] = self.fc_dir
+ try:
+ os.makedirs(self.fc_dir)
+ except:
+ pass
+ shutil.copy(os.path.join(os.path.dirname(__file__), 'fontconfig-test/fonts.conf'),
+ os.path.join(self.fc_dir, 'fonts.conf'))
+
+ # Setup GTK+ modules before starting the daemon
+ modules_dir = os.path.join(self.workdir, 'gtk-modules')
+ os.environ['GSD_gtk_modules_dir'] = modules_dir
+ try:
+ os.makedirs(modules_dir)
+ except:
+ pass
+ shutil.copy(os.path.join(os.path.dirname(__file__), 'gtk-modules-test/canberra-gtk-module.desktop'),
+ os.path.join(modules_dir, 'canberra-gtk-module.desktop'))
+ shutil.copy(os.path.join(os.path.dirname(__file__), 'gtk-modules-test/pk-gtk-module.desktop'),
+ os.path.join(modules_dir, 'pk-gtk-module.desktop'))
+
+ self.settings_sound = Gio.Settings.new('org.gnome.desktop.sound')
+
+ env = os.environ.copy()
+ self.daemon = subprocess.Popen(
+ [os.path.join(builddir, 'gsd-xsettings'), '--verbose'],
+ # comment out this line if you want to see the logs in real time
+ stdout=self.plugin_log_write,
+ stderr=subprocess.STDOUT,
+ env=env)
+
+ # you can use this for reading the current daemon log in tests
+ self.plugin_log = open(self.plugin_log_write.name, 'rb', buffering=0)
+
+ # flush notification log
+ try:
+ self.p_notify.stdout.read()
+ except IOError:
+ pass
+
+ time.sleep(3)
+ obj_xsettings = self.session_bus_con.get_object(
+ 'org.gtk.Settings', '/org/gtk/Settings')
+ self.obj_xsettings_props = dbus.Interface(obj_xsettings, dbus.PROPERTIES_IFACE)
+
+ def tearDown(self):
+
+ daemon_running = self.daemon.poll() == None
+ if daemon_running:
+ self.daemon.terminate()
+ self.daemon.wait()
+ self.plugin_log.close()
+ self.plugin_log_write.flush()
+ self.plugin_log_write.close()
+
+ self.stop_session()
+ self.stop_mutter()
+ self.stop_logind()
+
+ # reset all changed gsettings, so that tests are independent from each
+ # other
+ for schema in [self.settings_sound]:
+ for k in schema.list_keys():
+ schema.reset(k)
+ Gio.Settings.sync()
+
+ # we check this at the end so that the other cleanup always happens
+ self.assertTrue(daemon_running or self.daemon_death_expected, 'daemon died during the test')
+
+ def stop_session(self):
+ '''Stop GNOME session'''
+
+ assert self.session
+ self.session.terminate()
+ self.session.wait()
+
+ self.session_log_write.flush()
+ self.session_log_write.close()
+ self.session_log.close()
+
+ def check_plugin_log(self, needle, timeout=0, failmsg=None):
+ '''Check that needle is found in the log within the given timeout.
+ Returns immediately when found.
+
+ Fail after the given timeout.
+ '''
+ if type(needle) == str:
+ needle = needle.encode('ascii')
+ # Fast path if the message was already logged
+ log = self.plugin_log.read()
+ if needle in log:
+ return
+
+ while timeout > 0:
+ time.sleep(0.5)
+ timeout -= 0.5
+
+ # read new data (lines) from the log
+ log = self.plugin_log.read()
+ if needle in log:
+ break
+ else:
+ if failmsg is not None:
+ self.fail(failmsg)
+ else:
+ self.fail('timed out waiting for needle "%s"' % needle)
+
+ def test_gtk_modules(self):
+ # Turn off event sounds
+ self.settings_sound['event-sounds'] = False
+ time.sleep(2)
+
+ # Verify that only the PackageKit plugin is enabled
+ self.assertEqual(self.obj_xsettings_props.Get('org.gtk.Settings', 'Modules'),
+ dbus.String('pk-gtk-module', variant_level=1))
+
+ # Turn on sounds
+ self.settings_sound['event-sounds'] = True
+ time.sleep(2)
+
+ # Check that both PK and canberra plugin are enabled
+ retval = self.obj_xsettings_props.Get('org.gtk.Settings', 'Modules')
+ values = sorted(str(retval).split(':'))
+ self.assertEqual(values, ['canberra-gtk-module', 'pk-gtk-module'])
+
+ def test_fontconfig_timestamp(self):
+ # Initially, the value is zero
+ before = self.obj_xsettings_props.Get('org.gtk.Settings', 'FontconfigTimestamp')
+ self.assertEqual(before, 0)
+
+ # Copy the fonts.conf again
+ shutil.copy(os.path.join(os.path.dirname(__file__), 'fontconfig-test/fonts.conf'),
+ os.path.join(self.fc_dir, 'fonts.conf'))
+
+ # Wait for gsd-xsettings to pick up the change (and process it)
+ self.check_plugin_log("Fontconfig update successful", timeout=5, failmsg="Fontconfig was not updated!")
+
+ # Sleep a bit to ensure that the setting is updated
+ time.sleep(1)
+
+ after = self.obj_xsettings_props.Get('org.gtk.Settings', 'FontconfigTimestamp')
+ self.assertTrue(after > before)
+
+# avoid writing to stderr
+unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))
diff --git a/plugins/xsettings/wm-button-layout-translation.c b/plugins/xsettings/wm-button-layout-translation.c
new file mode 100644
index 0000000..0fa4d2c
--- /dev/null
+++ b/plugins/xsettings/wm-button-layout-translation.c
@@ -0,0 +1,88 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2014 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Florian Müllner <fmuellner@gnome.org>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <glib.h>
+
+#include "wm-button-layout-translation.h"
+
+static void
+translate_buttons (char *layout, int *len_p)
+{
+ char *strp = layout, *button;
+ int len = 0;
+
+ if (!layout || !*layout)
+ goto out;
+
+ while ((button = strsep (&strp, ",")))
+ {
+ char *gtkbutton;
+
+ if (strcmp (button, "menu") == 0)
+ gtkbutton = "icon";
+ else if (strcmp (button, "appmenu") == 0)
+ gtkbutton = "menu";
+ else if (strcmp (button, "minimize") == 0)
+ gtkbutton = "minimize";
+ else if (strcmp (button, "maximize") == 0)
+ gtkbutton = "maximize";
+ else if (strcmp (button, "close") == 0)
+ gtkbutton = "close";
+ else
+ continue;
+
+ if (len)
+ layout[len++] = ',';
+
+ strcpy (layout + len, gtkbutton);
+ len += strlen (gtkbutton);
+ }
+ layout[len] = '\0';
+
+out:
+ if (len_p)
+ *len_p = len;
+}
+
+void
+translate_wm_button_layout_to_gtk (char *layout)
+{
+ char *strp = layout, *left_buttons, *right_buttons;
+ int left_len, right_len = 0;
+
+ left_buttons = strsep (&strp, ":");
+ right_buttons = strp;
+
+ translate_buttons (left_buttons, &left_len);
+ memmove (layout, left_buttons, left_len);
+
+ if (strp == NULL)
+ goto out; /* no ":" in layout */
+
+ layout[left_len++] = ':';
+
+ translate_buttons (right_buttons, &right_len);
+ memmove (layout + left_len, right_buttons, right_len);
+
+out:
+ layout[left_len + right_len] = '\0';
+}
diff --git a/plugins/xsettings/wm-button-layout-translation.h b/plugins/xsettings/wm-button-layout-translation.h
new file mode 100644
index 0000000..87210b6
--- /dev/null
+++ b/plugins/xsettings/wm-button-layout-translation.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2014 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Florian Müllner <fmuellner@gnome.org>
+ */
+
+#ifndef __WM_BUTTON_LAYOUT_TRANSLATION__
+#define __WM_BUTTON_LAYOUT_TRANSLATION__
+
+void translate_wm_button_layout_to_gtk (char *layout);
+
+#endif
diff --git a/plugins/xsettings/xsettings-common.c b/plugins/xsettings/xsettings-common.c
new file mode 100644
index 0000000..e5f9fcc
--- /dev/null
+++ b/plugins/xsettings/xsettings-common.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright © 2001 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Red Hat not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. Red Hat makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author: Owen Taylor, Red Hat, Inc.
+ */
+
+#include <glib.h>
+
+#include "string.h"
+#include "stdlib.h"
+
+#include <X11/Xlib.h>
+#include <X11/Xmd.h> /* For CARD32 */
+
+#include "xsettings-common.h"
+
+XSettingsSetting *
+xsettings_setting_new (const gchar *name)
+{
+ XSettingsSetting *result;
+
+ result = g_slice_new0 (XSettingsSetting);
+ result->name = g_strdup (name);
+
+ return result;
+}
+
+static gboolean
+xsettings_variant_equal0 (GVariant *a,
+ GVariant *b)
+{
+ if (a == b)
+ return TRUE;
+
+ if (!a || !b)
+ return FALSE;
+
+ return g_variant_equal (a, b);
+}
+
+GVariant *
+xsettings_setting_get (XSettingsSetting *setting)
+{
+ gint i;
+
+ for (i = G_N_ELEMENTS (setting->value) - 1; 0 <= i; i--)
+ if (setting->value[i])
+ return setting->value[i];
+
+ return NULL;
+}
+
+void
+xsettings_setting_set (XSettingsSetting *setting,
+ gint tier,
+ GVariant *value,
+ guint32 serial)
+{
+ GVariant *old_value;
+
+ old_value = xsettings_setting_get (setting);
+ if (old_value)
+ g_variant_ref (old_value);
+
+ if (setting->value[tier])
+ g_variant_unref (setting->value[tier]);
+ setting->value[tier] = value ? g_variant_ref_sink (value) : NULL;
+
+ if (!xsettings_variant_equal0 (old_value, xsettings_setting_get (setting)))
+ setting->last_change_serial = serial;
+
+ if (old_value)
+ g_variant_unref (old_value);
+}
+
+void
+xsettings_setting_free (XSettingsSetting *setting)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (setting->value); i++)
+ if (setting->value[i])
+ g_variant_unref (setting->value[i]);
+
+ g_free (setting->name);
+
+ g_slice_free (XSettingsSetting, setting);
+}
+
+char
+xsettings_byte_order (void)
+{
+ CARD32 myint = 0x01020304;
+ return (*(char *)&myint == 1) ? MSBFirst : LSBFirst;
+}
diff --git a/plugins/xsettings/xsettings-common.h b/plugins/xsettings/xsettings-common.h
new file mode 100644
index 0000000..2062f47
--- /dev/null
+++ b/plugins/xsettings/xsettings-common.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2001 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Red Hat not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. Red Hat makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author: Owen Taylor, Red Hat, Inc.
+ */
+#ifndef XSETTINGS_COMMON_H
+#define XSETTINGS_COMMON_H
+
+#include <glib.h>
+
+#define XSETTINGS_N_TIERS 2
+
+typedef struct _XSettingsColor XSettingsColor;
+typedef struct _XSettingsSetting XSettingsSetting;
+
+/* Types of settings possible. Enum values correspond to
+ * protocol values.
+ */
+typedef enum
+{
+ XSETTINGS_TYPE_INT = 0,
+ XSETTINGS_TYPE_STRING = 1,
+ XSETTINGS_TYPE_COLOR = 2
+} XSettingsType;
+
+struct _XSettingsColor
+{
+ unsigned short red, green, blue, alpha;
+};
+
+struct _XSettingsSetting
+{
+ char *name;
+ GVariant *value[XSETTINGS_N_TIERS];
+ unsigned long last_change_serial;
+};
+
+XSettingsSetting *xsettings_setting_new (const gchar *name);
+GVariant * xsettings_setting_get (XSettingsSetting *setting);
+void xsettings_setting_set (XSettingsSetting *setting,
+ gint tier,
+ GVariant *value,
+ guint32 serial);
+void xsettings_setting_free (XSettingsSetting *setting);
+
+char xsettings_byte_order (void);
+
+#endif /* XSETTINGS_COMMON_H */
diff --git a/plugins/xsettings/xsettings-manager.c b/plugins/xsettings/xsettings-manager.c
new file mode 100644
index 0000000..947cc9e
--- /dev/null
+++ b/plugins/xsettings/xsettings-manager.c
@@ -0,0 +1,393 @@
+/*
+ * Copyright © 2001 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Red Hat not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. Red Hat makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author: Owen Taylor, Red Hat, Inc.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+#include <X11/Xmd.h> /* For CARD16 */
+
+#include "xsettings-manager.h"
+
+#define XSETTINGS_VARIANT_TYPE_COLOR (G_VARIANT_TYPE ("(qqqq)"))
+
+struct _XSettingsManager
+{
+ Display *display;
+ int screen;
+
+ Window window;
+ Atom manager_atom;
+ Atom selection_atom;
+ Atom xsettings_atom;
+
+ XSettingsTerminateFunc terminate;
+ void *cb_data;
+
+ GHashTable *settings;
+ unsigned long serial;
+
+ GVariant *overrides;
+};
+
+typedef struct
+{
+ Window window;
+ Atom timestamp_prop_atom;
+} TimeStampInfo;
+
+static Bool
+timestamp_predicate (Display *display,
+ XEvent *xevent,
+ XPointer arg)
+{
+ TimeStampInfo *info = (TimeStampInfo *)arg;
+
+ if (xevent->type == PropertyNotify &&
+ xevent->xproperty.window == info->window &&
+ xevent->xproperty.atom == info->timestamp_prop_atom)
+ return True;
+
+ return False;
+}
+
+/**
+ * get_server_time:
+ * @display: display from which to get the time
+ * @window: a #Window, used for communication with the server.
+ * The window must have PropertyChangeMask in its
+ * events mask or a hang will result.
+ *
+ * Routine to get the current X server time stamp.
+ *
+ * Return value: the time stamp.
+ **/
+static Time
+get_server_time (Display *display,
+ Window window)
+{
+ unsigned char c = 'a';
+ XEvent xevent;
+ TimeStampInfo info;
+
+ info.timestamp_prop_atom = XInternAtom (display, "_TIMESTAMP_PROP", False);
+ info.window = window;
+
+ XChangeProperty (display, window,
+ info.timestamp_prop_atom, info.timestamp_prop_atom,
+ 8, PropModeReplace, &c, 1);
+
+ XIfEvent (display, &xevent,
+ timestamp_predicate, (XPointer)&info);
+
+ return xevent.xproperty.time;
+}
+
+Bool
+xsettings_manager_check_running (Display *display,
+ int screen)
+{
+ char buffer[256];
+ Atom selection_atom;
+
+ sprintf(buffer, "_XSETTINGS_S%d", screen);
+ selection_atom = XInternAtom (display, buffer, False);
+
+ if (XGetSelectionOwner (display, selection_atom))
+ return True;
+ else
+ return False;
+}
+
+XSettingsManager *
+xsettings_manager_new (Display *display,
+ int screen,
+ XSettingsTerminateFunc terminate,
+ void *cb_data)
+{
+ XSettingsManager *manager;
+ Time timestamp;
+ XClientMessageEvent xev;
+
+ char buffer[256];
+
+ manager = g_slice_new (XSettingsManager);
+
+ manager->display = display;
+ manager->screen = screen;
+
+ sprintf(buffer, "_XSETTINGS_S%d", screen);
+ manager->selection_atom = XInternAtom (display, buffer, False);
+ manager->xsettings_atom = XInternAtom (display, "_XSETTINGS_SETTINGS", False);
+ manager->manager_atom = XInternAtom (display, "MANAGER", False);
+
+ manager->terminate = terminate;
+ manager->cb_data = cb_data;
+
+ manager->settings = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) xsettings_setting_free);
+ manager->serial = 0;
+ manager->overrides = NULL;
+
+ manager->window = XCreateSimpleWindow (display,
+ RootWindow (display, screen),
+ 0, 0, 10, 10, 0,
+ WhitePixel (display, screen),
+ WhitePixel (display, screen));
+
+ XSelectInput (display, manager->window, PropertyChangeMask);
+ timestamp = get_server_time (display, manager->window);
+
+ XSetSelectionOwner (display, manager->selection_atom,
+ manager->window, timestamp);
+
+ /* Check to see if we managed to claim the selection. If not,
+ * we treat it as if we got it then immediately lost it
+ */
+
+ if (XGetSelectionOwner (display, manager->selection_atom) ==
+ manager->window)
+ {
+ xev.type = ClientMessage;
+ xev.window = RootWindow (display, screen);
+ xev.message_type = manager->manager_atom;
+ xev.format = 32;
+ xev.data.l[0] = timestamp;
+ xev.data.l[1] = manager->selection_atom;
+ xev.data.l[2] = manager->window;
+ xev.data.l[3] = 0; /* manager specific data */
+ xev.data.l[4] = 0; /* manager specific data */
+
+ XSendEvent (display, RootWindow (display, screen),
+ False, StructureNotifyMask, (XEvent *)&xev);
+ }
+ else
+ {
+ manager->terminate (manager->cb_data);
+ }
+
+ return manager;
+}
+
+void
+xsettings_manager_destroy (XSettingsManager *manager)
+{
+ XDestroyWindow (manager->display, manager->window);
+
+ g_hash_table_unref (manager->settings);
+
+ g_slice_free (XSettingsManager, manager);
+}
+
+static void
+xsettings_manager_set_setting (XSettingsManager *manager,
+ const gchar *name,
+ gint tier,
+ GVariant *value)
+{
+ XSettingsSetting *setting;
+
+ setting = g_hash_table_lookup (manager->settings, name);
+
+ if (setting == NULL)
+ {
+ setting = xsettings_setting_new (name);
+ setting->last_change_serial = manager->serial;
+ g_hash_table_insert (manager->settings, setting->name, setting);
+ }
+
+ xsettings_setting_set (setting, tier, value, manager->serial);
+
+ if (xsettings_setting_get (setting) == NULL)
+ g_hash_table_remove (manager->settings, name);
+}
+
+void
+xsettings_manager_set_int (XSettingsManager *manager,
+ const char *name,
+ int value)
+{
+ xsettings_manager_set_setting (manager, name, 0, g_variant_new_int32 (value));
+}
+
+void
+xsettings_manager_set_string (XSettingsManager *manager,
+ const char *name,
+ const char *value)
+{
+ xsettings_manager_set_setting (manager, name, 0, g_variant_new_string (value));
+}
+
+void
+xsettings_manager_set_color (XSettingsManager *manager,
+ const char *name,
+ XSettingsColor *value)
+{
+ GVariant *tmp;
+
+ tmp = g_variant_new ("(qqqq)", value->red, value->green, value->blue, value->alpha);
+ g_assert (g_variant_is_of_type (tmp, XSETTINGS_VARIANT_TYPE_COLOR)); /* paranoia... */
+ xsettings_manager_set_setting (manager, name, 0, tmp);
+}
+
+void
+xsettings_manager_delete_setting (XSettingsManager *manager,
+ const char *name)
+{
+ xsettings_manager_set_setting (manager, name, 0, NULL);
+}
+
+static gchar
+xsettings_get_typecode (GVariant *value)
+{
+ switch (g_variant_classify (value))
+ {
+ case G_VARIANT_CLASS_INT32:
+ return XSETTINGS_TYPE_INT;
+ case G_VARIANT_CLASS_STRING:
+ return XSETTINGS_TYPE_STRING;
+ case G_VARIANT_CLASS_TUPLE:
+ return XSETTINGS_TYPE_COLOR;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+align_string (GString *string,
+ gint alignment)
+{
+ /* Adds nul-bytes to the string until its length is an even multiple
+ * of the specified alignment requirement.
+ */
+ while ((string->len % alignment) != 0)
+ g_string_append_c (string, '\0');
+}
+
+static void
+setting_store (XSettingsSetting *setting,
+ GString *buffer)
+{
+ XSettingsType type;
+ GVariant *value;
+ guint16 len16;
+
+ value = xsettings_setting_get (setting);
+
+ type = xsettings_get_typecode (value);
+
+ g_string_append_c (buffer, type);
+ g_string_append_c (buffer, 0);
+
+ len16 = strlen (setting->name);
+ g_string_append_len (buffer, (gchar *) &len16, 2);
+ g_string_append (buffer, setting->name);
+ align_string (buffer, 4);
+
+ g_string_append_len (buffer, (gchar *) &setting->last_change_serial, 4);
+
+ if (type == XSETTINGS_TYPE_STRING)
+ {
+ const gchar *string;
+ gsize stringlen;
+ guint32 len32;
+
+ string = g_variant_get_string (value, &stringlen);
+ len32 = stringlen;
+ g_string_append_len (buffer, (gchar *) &len32, 4);
+ g_string_append (buffer, string);
+ align_string (buffer, 4);
+ }
+ else
+ /* GVariant format is the same as XSETTINGS format for the non-string types */
+ g_string_append_len (buffer, g_variant_get_data (value), g_variant_get_size (value));
+}
+
+void
+xsettings_manager_notify (XSettingsManager *manager)
+{
+ GString *buffer;
+ GHashTableIter iter;
+ int n_settings;
+ gpointer value;
+
+ n_settings = g_hash_table_size (manager->settings);
+
+ buffer = g_string_new (NULL);
+ g_string_append_c (buffer, xsettings_byte_order ());
+ g_string_append_c (buffer, '\0');
+ g_string_append_c (buffer, '\0');
+ g_string_append_c (buffer, '\0');
+
+ g_string_append_len (buffer, (gchar *) &manager->serial, 4);
+ g_string_append_len (buffer, (gchar *) &n_settings, 4);
+
+ g_hash_table_iter_init (&iter, manager->settings);
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ setting_store (value, buffer);
+
+ XChangeProperty (manager->display, manager->window,
+ manager->xsettings_atom, manager->xsettings_atom,
+ 8, PropModeReplace, (guchar *) buffer->str, buffer->len);
+
+ g_string_free (buffer, TRUE);
+ manager->serial++;
+}
+
+void
+xsettings_manager_set_overrides (XSettingsManager *manager,
+ GVariant *overrides)
+{
+ GVariantIter iter;
+ const gchar *key;
+ GVariant *value;
+
+ g_return_if_fail (overrides != NULL && g_variant_is_of_type (overrides, G_VARIANT_TYPE_VARDICT));
+
+ if (manager->overrides)
+ {
+ /* unset the existing overrides */
+
+ g_variant_iter_init (&iter, manager->overrides);
+ while (g_variant_iter_next (&iter, "{&sv}", &key, NULL))
+ /* only unset it at this point if it's not in the new list */
+ if (!g_variant_lookup (overrides, key, "*", NULL))
+ xsettings_manager_set_setting (manager, key, 1, NULL);
+ g_variant_unref (manager->overrides);
+ }
+
+ /* save this so we can do the unsets next time */
+ manager->overrides = g_variant_ref_sink (overrides);
+
+ /* set the new values */
+ g_variant_iter_init (&iter, overrides);
+ while (g_variant_iter_loop (&iter, "{&sv}", &key, &value))
+ {
+ /* only accept recognised types... */
+ if (!g_variant_is_of_type (value, G_VARIANT_TYPE_STRING) &&
+ !g_variant_is_of_type (value, G_VARIANT_TYPE_INT32) &&
+ !g_variant_is_of_type (value, XSETTINGS_VARIANT_TYPE_COLOR))
+ continue;
+
+ xsettings_manager_set_setting (manager, key, 1, value);
+ }
+}
diff --git a/plugins/xsettings/xsettings-manager.h b/plugins/xsettings/xsettings-manager.h
new file mode 100644
index 0000000..a8cf151
--- /dev/null
+++ b/plugins/xsettings/xsettings-manager.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright © 2001 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Red Hat not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission. Red Hat makes no representations about the
+ * suitability of this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ *
+ * RED HAT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL RED HAT
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author: Owen Taylor, Red Hat, Inc.
+ */
+#ifndef XSETTINGS_MANAGER_H
+#define XSETTINGS_MANAGER_H
+
+#include <X11/Xlib.h>
+#include "xsettings-common.h"
+
+typedef struct _XSettingsManager XSettingsManager;
+
+typedef void (*XSettingsTerminateFunc) (void *cb_data);
+
+Bool xsettings_manager_check_running (Display *display,
+ int screen);
+
+XSettingsManager *xsettings_manager_new (Display *display,
+ int screen,
+ XSettingsTerminateFunc terminate,
+ void *cb_data);
+
+void xsettings_manager_destroy (XSettingsManager *manager);
+
+void xsettings_manager_delete_setting (XSettingsManager *manager,
+ const char *name);
+void xsettings_manager_set_int (XSettingsManager *manager,
+ const char *name,
+ int value);
+void xsettings_manager_set_string (XSettingsManager *manager,
+ const char *name,
+ const char *value);
+void xsettings_manager_set_color (XSettingsManager *manager,
+ const char *name,
+ XSettingsColor *value);
+void xsettings_manager_notify (XSettingsManager *manager);
+void xsettings_manager_set_overrides (XSettingsManager *manager,
+ GVariant *overrides);
+
+#endif /* XSETTINGS_MANAGER_H */