diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:51:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:51:55 +0000 |
commit | 86b7f1a83d7db9c912f32b29c32e1124c0a6454d (patch) | |
tree | 42a7ff7c6885e99e0669d07b104df11b2bf387b6 /plugins/xsettings | |
parent | Initial commit. (diff) | |
download | gnome-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')
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 */ |