[evolution] I#1898 - Better handle Dark Style GNOME 42 setting



commit 4c33f886e083784d707be9fa8ec50f56977f5150
Author: Milan Crha <mcrha redhat com>
Date:   Thu May 12 17:44:14 2022 +0200

    I#1898 - Better handle Dark Style GNOME 42 setting
    
    Closes https://gitlab.gnome.org/GNOME/evolution/-/issues/1898

 src/e-util/CMakeLists.txt           |   2 +
 src/e-util/e-color-scheme-watcher.c | 490 ++++++++++++++++++++++++++++++++++++
 src/e-util/e-color-scheme-watcher.h |  27 ++
 src/e-util/e-util.h                 |   1 +
 src/shell/e-shell.c                 |   3 +
 5 files changed, 523 insertions(+)
---
diff --git a/src/e-util/CMakeLists.txt b/src/e-util/CMakeLists.txt
index 0c940f326f..46588be512 100644
--- a/src/e-util/CMakeLists.txt
+++ b/src/e-util/CMakeLists.txt
@@ -100,6 +100,7 @@ set(SOURCES
        e-collection-account-wizard.c
        e-color-chooser-widget.c
        e-color-combo.c
+       e-color-scheme-watcher.c
        e-config.c
        e-config-lookup.c
        e-config-lookup-result.c
@@ -381,6 +382,7 @@ set(HEADERS
        e-collection-account-wizard.h
        e-color-chooser-widget.h
        e-color-combo.h
+       e-color-scheme-watcher.h
        e-config.h
        e-config-lookup.h
        e-config-lookup-result.h
diff --git a/src/e-util/e-color-scheme-watcher.c b/src/e-util/e-color-scheme-watcher.c
new file mode 100644
index 0000000000..b5b5f54651
--- /dev/null
+++ b/src/e-util/e-color-scheme-watcher.c
@@ -0,0 +1,490 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-config.h"
+
+/* This is partly based on:
+ * https://gitlab.gnome.org/GNOME/Initiatives/-/wikis/Dark-Style-Preference
+ * and partly on the libhandy code.
+ */
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "e-color-scheme-watcher.h"
+
+typedef enum {
+       E_COLOR_SCHEME_UNKNOWN = -1,
+       E_COLOR_SCHEME_DEFAULT = 0,
+       E_COLOR_SCHEME_PREFER_DARK,
+       E_COLOR_SCHEME_PREFER_LIGHT
+} EColorScheme;
+
+struct _EColorSchemeWatcher
+{
+       GObject parent_instance;
+
+       GCancellable *cancellable;
+       GDBusProxy *settings_portal;
+       gchar *last_theme_name;
+       EColorScheme color_scheme;
+       gboolean use_fdo_setting;
+};
+
+G_DEFINE_TYPE (EColorSchemeWatcher, e_color_scheme_watcher, G_TYPE_OBJECT);
+
+#if (GTK_MINOR_VERSION % 2)
+#define MINOR (GTK_MINOR_VERSION + 1)
+#else
+#define MINOR GTK_MINOR_VERSION
+#endif
+
+static gboolean
+e_color_scheme_watcher_theme_dir_exists (const gchar *dir,
+                                        const gchar *subdir,
+                                        const gchar *name,
+                                        const gchar *variant)
+{
+       gchar *file;
+       gchar *base;
+       gboolean exists = FALSE;
+       gint ii;
+
+       if (variant)
+               file = g_strconcat ("gtk-", variant, ".css", NULL);
+       else
+               file = g_strdup ("gtk.css");
+
+       if (subdir)
+               base = g_build_filename (dir, subdir, name, NULL);
+       else
+               base = g_build_filename (dir, name, NULL);
+
+       for (ii = MINOR; ii >= 0 && !exists; ii = ii - 2) {
+               gchar *subsubdir = NULL;
+               gchar *path = NULL;
+
+               if (ii < 14)
+                       ii = 0;
+
+               subsubdir = g_strdup_printf ("gtk-3.%d", ii);
+               path = g_build_filename (base, subsubdir, file, NULL);
+
+               exists = g_file_test (path, G_FILE_TEST_EXISTS);
+
+               g_clear_pointer (&path, g_free);
+               g_clear_pointer (&subsubdir, g_free);
+       }
+
+       g_clear_pointer (&file, g_free);
+       g_clear_pointer (&base, g_free);
+
+       return exists;
+}
+
+#undef MINOR
+
+static gboolean
+e_color_scheme_watcher_theme_exists (const gchar *name,
+                                    const gchar *variant)
+{
+       gchar *dir = NULL;
+       const gchar *const *dirs;
+       const gchar *var;
+       gint ii;
+
+       /* First look in the user's data directory */
+       if (e_color_scheme_watcher_theme_dir_exists (g_get_user_data_dir (), "themes", name, variant))
+               return TRUE;
+
+       /* Next look in the user's home directory */
+       if (e_color_scheme_watcher_theme_dir_exists (g_get_home_dir (), ".themes", name, variant))
+               return TRUE;
+
+       /* Look in system data directories */
+       dirs = g_get_system_data_dirs ();
+       for (ii = 0; dirs[ii]; ii++) {
+               if (e_color_scheme_watcher_theme_dir_exists (dirs[ii], "themes", name, variant))
+                       return TRUE;
+       }
+
+       /* Finally, try in the default theme directory */
+
+       var = g_getenv ("GTK_DATA_PREFIX");
+       if (var)
+               dir = g_build_filename (var, "share", "themes", NULL);
+       else
+               dir = g_build_filename (EVOLUTION_DATADIR, "themes", NULL);
+
+       if (e_color_scheme_watcher_theme_dir_exists (dir, NULL, name, variant)) {
+               g_free (dir);
+               return TRUE;
+       }
+
+       g_free (dir);
+
+       return FALSE;
+}
+
+static gboolean
+e_color_scheme_watcher_check_theme_exists (const gchar *name,
+                                          const gchar *variant)
+{
+       gchar *resource_path = NULL;
+
+       /* try loading the resource for the theme. This is mostly meant for built-in themes. */
+       if (variant)
+               resource_path = g_strdup_printf ("/org/gtk/libgtk/theme/%s/gtk-%s.css", name, variant);
+       else
+               resource_path = g_strdup_printf ("/org/gtk/libgtk/theme/%s/gtk.css", name);
+
+       if (g_resources_get_info (resource_path, 0, NULL, NULL, NULL)) {
+               g_free (resource_path);
+               return TRUE;
+       }
+
+       g_free (resource_path);
+
+       return e_color_scheme_watcher_theme_exists (name, variant);
+}
+
+static void
+e_color_scheme_watcher_sync_theme (EColorSchemeWatcher *self)
+{
+       GtkSettings *gtk_settings = gtk_settings_get_default ();
+       gchar *theme_name = NULL;
+
+       g_object_get (gtk_settings,
+               "gtk-theme-name", &theme_name,
+               NULL);
+
+       if (theme_name) {
+               gboolean update_theme = FALSE;
+
+               if (self->color_scheme == E_COLOR_SCHEME_PREFER_DARK) {
+                       g_object_set (gtk_settings,
+                               "gtk-application-prefer-dark-theme", TRUE,
+                               NULL);
+
+                       if (g_strcmp0 (theme_name, "HighContrast") == 0) {
+                               g_object_set (gtk_settings,
+                                       "gtk-theme-name", "HighContrastInverse",
+                                       NULL);
+                       } else if (g_strcmp0 (theme_name, "Breeze") == 0) {
+                               g_object_set (gtk_settings,
+                                       "gtk-theme-name", "Breeze-Dark",
+                                       NULL);
+                       } else {
+                               update_theme = g_strcmp0 (theme_name, "HighContrastInverse") != 0 &&
+                                              g_strcmp0 (theme_name, "Breeze-Dark") != 0;
+                       }
+               } else if (self->color_scheme == E_COLOR_SCHEME_PREFER_LIGHT) {
+                       g_object_set (gtk_settings,
+                               "gtk-application-prefer-dark-theme", FALSE,
+                               NULL);
+
+                       if (g_strcmp0 (theme_name, "HighContrastInverse") == 0) {
+                               g_object_set (gtk_settings,
+                                       "gtk-theme-name", "HighContrast",
+                                       NULL);
+                       } else if (g_strcmp0 (theme_name, "Breeze-Dark") == 0) {
+                               g_object_set (gtk_settings,
+                                       "gtk-theme-name", "Breeze",
+                                       NULL);
+                       } else {
+                               update_theme = g_strcmp0 (theme_name, "HighContrast") != 0 &&
+                                              g_strcmp0 (theme_name, "Breeze") != 0;
+                       }
+               } else {
+                       gtk_settings_reset_property (gtk_settings, "gtk-theme-name");
+                       gtk_settings_reset_property (gtk_settings, "gtk-application-prefer-dark-theme");
+               }
+
+               if (update_theme) {
+                       gchar *new_theme_name = NULL;
+                       gboolean suffix_cut = FALSE;
+
+                       if (g_str_has_suffix (theme_name, "-dark")) {
+                               theme_name[strlen (theme_name) - 5] = '\0';
+                               suffix_cut = TRUE;
+                       }
+
+                       if (self->color_scheme == E_COLOR_SCHEME_PREFER_DARK &&
+                           e_color_scheme_watcher_check_theme_exists (theme_name, "dark")) {
+                               new_theme_name = g_strconcat (theme_name, "-dark", NULL);
+                       } else if (suffix_cut && e_color_scheme_watcher_check_theme_exists (theme_name, 
NULL)) {
+                               new_theme_name = theme_name;
+                               theme_name = NULL;
+                       }
+
+                       if (new_theme_name) {
+                               g_object_set (gtk_settings,
+                                       "gtk-theme-name", new_theme_name,
+                                       NULL);
+                       }
+
+                       g_free (new_theme_name);
+               }
+
+               g_free (theme_name);
+       }
+}
+
+static void
+e_color_scheme_watcher_set_color_scheme (EColorSchemeWatcher *self,
+                                        EColorScheme color_scheme)
+{
+       if (color_scheme > E_COLOR_SCHEME_PREFER_LIGHT || color_scheme == E_COLOR_SCHEME_UNKNOWN)
+               color_scheme = E_COLOR_SCHEME_DEFAULT;
+
+       if (color_scheme != self->color_scheme) {
+               self->color_scheme = color_scheme;
+
+               e_color_scheme_watcher_sync_theme (self);
+       }
+}
+
+static EColorScheme
+e_color_scheme_watcher_read_dgo (GVariant *value)
+{
+       const gchar *str;
+
+       if (!value || !g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+               return E_COLOR_SCHEME_UNKNOWN;
+
+       str = g_variant_get_string (value, NULL);
+
+       if (!g_strcmp0 (str, "default"))
+               return E_COLOR_SCHEME_DEFAULT;
+
+       if (!g_strcmp0 (str, "prefer-dark"))
+               return E_COLOR_SCHEME_PREFER_DARK;
+
+       if (!g_strcmp0 (str, "prefer-light"))
+               return E_COLOR_SCHEME_PREFER_LIGHT;
+
+       g_debug ("Invalid/unknown GNOME color scheme: '%s'", str);
+
+       return E_COLOR_SCHEME_UNKNOWN;
+}
+
+static void
+e_color_scheme_watcher_settings_portal_changed_cb (GDBusProxy *proxy,
+                                                  const gchar *sender_name,
+                                                  const gchar *signal_name,
+                                                  GVariant *parameters,
+                                                  EColorSchemeWatcher *self)
+{
+       const gchar *namespc;
+       const gchar *name;
+       GVariant *value = NULL;
+
+       if (g_strcmp0 (signal_name, "SettingChanged"))
+               return;
+
+       g_variant_get (parameters, "(&s&sv)", &namespc, &name, &value);
+
+       if (self->use_fdo_setting &&
+           g_strcmp0 (namespc, "org.freedesktop.appearance") == 0 &&
+           g_strcmp0 (name, "color-scheme") == 0) {
+               e_color_scheme_watcher_set_color_scheme (self, g_variant_get_uint32 (value));
+       } else if (g_strcmp0 (namespc, "org.gnome.desktop.interface") == 0) {
+               if (g_strcmp0 (name, "gtk-theme") == 0) {
+                       const gchar *theme_name = g_variant_get_string (value, NULL);
+                       if (g_strcmp0 (theme_name, self->last_theme_name) != 0) {
+                               GtkSettings *gtk_settings = gtk_settings_get_default ();
+
+                               g_clear_pointer (&self->last_theme_name, g_free);
+                               self->last_theme_name = g_strdup (theme_name);
+
+                               gtk_settings_reset_property (gtk_settings, "gtk-theme-name");
+                               gtk_settings_reset_property (gtk_settings, 
"gtk-application-prefer-dark-theme");
+                               e_color_scheme_watcher_sync_theme (self);
+                       }
+               } else if (!self->use_fdo_setting && g_strcmp0 (name, "color-scheme") == 0) {
+                       EColorScheme color_scheme = e_color_scheme_watcher_read_dgo (value);
+
+                       if (color_scheme != E_COLOR_SCHEME_UNKNOWN)
+                               e_color_scheme_watcher_set_color_scheme (self, color_scheme);
+               }
+       }
+
+       g_clear_pointer (&value, g_variant_unref);
+}
+
+static void
+e_color_scheme_watcher_read_dgo_cb (GObject *source_object,
+                                   GAsyncResult *async_result,
+                                   gpointer user_data)
+{
+       EColorSchemeWatcher *self = user_data;
+       GVariant *result;
+       GVariant *child = NULL;
+       GVariant *value = NULL;
+       GError *error = NULL;
+
+       result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), async_result, &error);
+
+       if (result) {
+               g_variant_get (result, "(v)", &child);
+
+               if (child) {
+                       g_variant_get (child, "v", &value);
+
+                       if (value) {
+                               EColorScheme color_scheme = e_color_scheme_watcher_read_dgo (value);
+
+                               if (color_scheme != E_COLOR_SCHEME_UNKNOWN) {
+                                       e_color_scheme_watcher_set_color_scheme (self, color_scheme);
+
+                                       g_signal_connect_object (self->settings_portal, "g-signal",
+                                               G_CALLBACK 
(e_color_scheme_watcher_settings_portal_changed_cb), self, 0);
+                               }
+                       }
+               }
+       } else {
+               g_debug ("Failed to read color scheme from GNOME: %s", error ? error->message : "Unknown 
error");
+               g_clear_error (&error);
+       }
+
+       g_clear_pointer (&result, g_variant_unref);
+       g_clear_pointer (&child, g_variant_unref);
+       g_clear_pointer (&value, g_variant_unref);
+}
+
+static void
+e_color_scheme_watcher_read_fdo_cb (GObject *source_object,
+                                   GAsyncResult *async_result,
+                                   gpointer user_data)
+{
+       EColorSchemeWatcher *self = user_data;
+       GVariant *result;
+       GVariant *child = NULL;
+       GVariant *value = NULL;
+       GError *error = NULL;
+
+       result = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), async_result, &error);
+
+       if (result) {
+               g_variant_get (result, "(v)", &child);
+
+               if (child) {
+                       g_variant_get (child, "v", &value);
+
+                       if (value) {
+                               EColorScheme color_scheme = g_variant_get_uint32 (value);
+
+                               self->use_fdo_setting = TRUE;
+
+                               e_color_scheme_watcher_set_color_scheme (self, color_scheme);
+
+                               g_signal_connect_object (self->settings_portal, "g-signal",
+                                       G_CALLBACK (e_color_scheme_watcher_settings_portal_changed_cb), self, 
0);
+                       }
+               }
+       } else {
+               g_debug ("Failed to read color scheme from freedesktop.org: %s", error ? error->message : 
"Unknown error");
+               g_clear_error (&error);
+       }
+
+       g_clear_pointer (&result, g_variant_unref);
+       g_clear_pointer (&child, g_variant_unref);
+       g_clear_pointer (&value, g_variant_unref);
+
+       if (!self->use_fdo_setting) {
+               g_dbus_proxy_call (self->settings_portal,
+                                  "Read",
+                                  g_variant_new ("(ss)",
+                                                 "org.gnome.desktop.interface",
+                                                 "color-scheme"),
+                                  G_DBUS_CALL_FLAGS_NONE,
+                                  5000,
+                                  self->cancellable,
+                                  e_color_scheme_watcher_read_dgo_cb,
+                                  self);
+       }
+}
+
+static void
+e_color_scheme_watcher_got_proxy_cb (GObject *source_object,
+                                    GAsyncResult *result,
+                                    gpointer user_data)
+{
+       EColorSchemeWatcher *self = user_data;
+       GDBusProxy *proxy;
+       GError *error = NULL;
+
+       proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+
+       if (!proxy) {
+               g_debug ("Failed to get color scheme proxy: %s\n", error ? error->message : "Unknown error");
+               g_clear_error (&error);
+               return;
+       }
+
+       g_return_if_fail (E_IS_COLOR_SCHEME_WATCHER (self));
+
+       self->settings_portal = proxy;
+
+       g_dbus_proxy_call (self->settings_portal,
+                          "Read",
+                          g_variant_new ("(ss)",
+                                         "org.freedesktop.appearance",
+                                         "color-scheme"),
+                          G_DBUS_CALL_FLAGS_NONE,
+                          5000,
+                          self->cancellable,
+                          e_color_scheme_watcher_read_fdo_cb,
+                          self);
+}
+
+static void
+e_color_scheme_watcher_dispose (GObject *object)
+{
+       EColorSchemeWatcher *self = E_COLOR_SCHEME_WATCHER (object);
+
+       if (self->cancellable) {
+               g_cancellable_cancel (self->cancellable);
+               g_clear_object (&self->cancellable);
+       }
+
+       g_clear_object (&self->settings_portal);
+       g_clear_pointer (&self->last_theme_name, g_free);
+
+       G_OBJECT_CLASS (e_color_scheme_watcher_parent_class)->dispose (object);
+}
+
+static void
+e_color_scheme_watcher_class_init (EColorSchemeWatcherClass *klass)
+{
+       GObjectClass *object_class;
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->dispose = e_color_scheme_watcher_dispose;
+}
+
+static void
+e_color_scheme_watcher_init (EColorSchemeWatcher *self)
+{
+       self->color_scheme = E_COLOR_SCHEME_UNKNOWN;
+       self->cancellable = g_cancellable_new ();
+
+       g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                                 G_DBUS_PROXY_FLAGS_NONE,
+                                 NULL,
+                                 "org.freedesktop.portal.Desktop",
+                                 "/org/freedesktop/portal/desktop",
+                                 "org.freedesktop.portal.Settings",
+                                 self->cancellable,
+                                 e_color_scheme_watcher_got_proxy_cb,
+                                 self);
+}
+
+EColorSchemeWatcher *
+e_color_scheme_watcher_new (void)
+{
+       return g_object_new (E_TYPE_COLOR_SCHEME_WATCHER, NULL);
+}
diff --git a/src/e-util/e-color-scheme-watcher.h b/src/e-util/e-color-scheme-watcher.h
new file mode 100644
index 0000000000..a8b3464a21
--- /dev/null
+++ b/src/e-util/e-color-scheme-watcher.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_COLOR_SCHEME_WATCHER_H
+#define E_COLOR_SCHEME_WATCHER_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_COLOR_SCHEME_WATCHER e_color_scheme_watcher_get_type ()
+
+G_DECLARE_FINAL_TYPE (EColorSchemeWatcher, e_color_scheme_watcher, E, COLOR_SCHEME_WATCHER, GObject)
+
+EColorSchemeWatcher *
+               e_color_scheme_watcher_new      (void);
+
+G_END_DECLS
+
+#endif /* E_COLOR_SCHEME_WATCHER_H */
diff --git a/src/e-util/e-util.h b/src/e-util/e-util.h
index c27dfa3110..d580d14382 100644
--- a/src/e-util/e-util.h
+++ b/src/e-util/e-util.h
@@ -83,6 +83,7 @@
 #include <e-util/e-collection-account-wizard.h>
 #include <e-util/e-color-chooser-widget.h>
 #include <e-util/e-color-combo.h>
+#include <e-util/e-color-scheme-watcher.h>
 #include <e-util/e-config.h>
 #include <e-util/e-config-lookup.h>
 #include <e-util/e-config-lookup-result.h>
diff --git a/src/shell/e-shell.c b/src/shell/e-shell.c
index 40a56f3c01..0c095e5ef6 100644
--- a/src/shell/e-shell.c
+++ b/src/shell/e-shell.c
@@ -54,6 +54,7 @@ struct _EShellPrivate {
        EClientCache *client_cache;
        GtkWidget *preferences_window;
        GCancellable *cancellable;
+       EColorSchemeWatcher *color_scheme_watcher;
 
        /* Shell Backends */
        GList *loaded_backends;              /* not referenced */
@@ -1655,6 +1656,7 @@ shell_dispose (GObject *object)
        g_clear_object (&priv->registry);
        g_clear_object (&priv->credentials_prompter);
        g_clear_object (&priv->client_cache);
+       g_clear_object (&priv->color_scheme_watcher);
 
        g_clear_pointer (&priv->preferences_window, gtk_widget_destroy);
 
@@ -2205,6 +2207,7 @@ e_shell_init (EShell *shell)
        shell->priv->auth_prompt_parents = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
        shell->priv->safe_mode = e_file_lock_exists ();
        shell->priv->requires_shutdown = FALSE;
+       shell->priv->color_scheme_watcher = e_color_scheme_watcher_new ();
 
        /* Add our icon directory to the theme's search path
         * here instead of in main() so Anjal picks it up. */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]