mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-07-09 21:39:25 +00:00
theming: respect system theme by default
This commit is contained in:
@@ -20,14 +20,379 @@
|
||||
|
||||
#include "theme-policy.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "theme-gtk3.h"
|
||||
#include "../fe-gtk.h"
|
||||
#include "../../common/zoitechat.h"
|
||||
#include "../../common/zoitechatc.h"
|
||||
|
||||
/* org.freedesktop.appearance color-scheme values (XDG desktop portal) */
|
||||
enum
|
||||
{
|
||||
THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE = 0,
|
||||
THEME_POLICY_COLOR_SCHEME_PREFER_DARK = 1,
|
||||
THEME_POLICY_COLOR_SCHEME_PREFER_LIGHT = 2
|
||||
};
|
||||
|
||||
static ThemePolicyAppearanceChangedFunc policy_appearance_changed_cb;
|
||||
|
||||
/* GtkSettings state captured before ZoiteChat writes to it, so that values
|
||||
* coming from the user's own GTK configuration are not mistaken for values
|
||||
* this application set. */
|
||||
static gboolean policy_initial_gtk_state_captured;
|
||||
static gboolean policy_initial_prefer_dark;
|
||||
static char *policy_initial_theme_name;
|
||||
|
||||
#ifndef G_OS_WIN32
|
||||
static gboolean policy_portal_watch_started;
|
||||
static gboolean policy_portal_value_valid;
|
||||
static guint32 policy_portal_color_scheme;
|
||||
|
||||
static gboolean policy_gsettings_checked;
|
||||
static GSettings *policy_gsettings_interface;
|
||||
#endif
|
||||
|
||||
static void
|
||||
policy_capture_initial_gtk_state (void)
|
||||
{
|
||||
GtkSettings *settings;
|
||||
|
||||
if (policy_initial_gtk_state_captured)
|
||||
return;
|
||||
|
||||
settings = gtk_settings_get_default ();
|
||||
if (!settings)
|
||||
return;
|
||||
|
||||
g_object_get (settings,
|
||||
"gtk-application-prefer-dark-theme", &policy_initial_prefer_dark,
|
||||
"gtk-theme-name", &policy_initial_theme_name,
|
||||
NULL);
|
||||
policy_initial_gtk_state_captured = TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
policy_theme_name_is_dark (const char *name)
|
||||
{
|
||||
char *lower;
|
||||
gboolean dark;
|
||||
|
||||
if (!name || !name[0])
|
||||
return FALSE;
|
||||
|
||||
lower = g_ascii_strdown (name, -1);
|
||||
dark = strstr (lower, "dark") != NULL || strstr (lower, "noir") != NULL ||
|
||||
strstr (lower, "black") != NULL;
|
||||
g_free (lower);
|
||||
return dark;
|
||||
}
|
||||
|
||||
#ifndef G_OS_WIN32
|
||||
|
||||
static void
|
||||
policy_notify_appearance_changed (void)
|
||||
{
|
||||
if (policy_appearance_changed_cb)
|
||||
policy_appearance_changed_cb ();
|
||||
}
|
||||
|
||||
static gboolean
|
||||
policy_color_scheme_from_variant (GVariant *value, guint32 *color_scheme)
|
||||
{
|
||||
GVariant *inner;
|
||||
gboolean ok = FALSE;
|
||||
|
||||
if (!value || !color_scheme)
|
||||
return FALSE;
|
||||
|
||||
inner = g_variant_ref (value);
|
||||
while (g_variant_is_of_type (inner, G_VARIANT_TYPE_VARIANT))
|
||||
{
|
||||
GVariant *unwrapped = g_variant_get_variant (inner);
|
||||
|
||||
g_variant_unref (inner);
|
||||
inner = unwrapped;
|
||||
}
|
||||
|
||||
if (g_variant_is_of_type (inner, G_VARIANT_TYPE_UINT32))
|
||||
{
|
||||
*color_scheme = g_variant_get_uint32 (inner);
|
||||
ok = TRUE;
|
||||
}
|
||||
|
||||
g_variant_unref (inner);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void
|
||||
policy_portal_store_color_scheme (guint32 color_scheme)
|
||||
{
|
||||
gboolean changed;
|
||||
|
||||
changed = !policy_portal_value_valid || policy_portal_color_scheme != color_scheme;
|
||||
policy_portal_value_valid = TRUE;
|
||||
policy_portal_color_scheme = color_scheme;
|
||||
|
||||
if (changed)
|
||||
policy_notify_appearance_changed ();
|
||||
}
|
||||
|
||||
static void
|
||||
policy_portal_setting_changed_cb (GDBusConnection *connection, const char *sender_name,
|
||||
const char *object_path, const char *interface_name,
|
||||
const char *signal_name, GVariant *parameters,
|
||||
gpointer user_data)
|
||||
{
|
||||
const char *setting_namespace = NULL;
|
||||
const char *setting_key = NULL;
|
||||
GVariant *value = NULL;
|
||||
guint32 color_scheme = THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE;
|
||||
|
||||
(void) connection;
|
||||
(void) sender_name;
|
||||
(void) object_path;
|
||||
(void) interface_name;
|
||||
(void) signal_name;
|
||||
(void) user_data;
|
||||
|
||||
if (!parameters || !g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(ssv)")))
|
||||
return;
|
||||
|
||||
g_variant_get (parameters, "(&s&sv)", &setting_namespace, &setting_key, &value);
|
||||
if (g_strcmp0 (setting_namespace, "org.freedesktop.appearance") == 0 &&
|
||||
g_strcmp0 (setting_key, "color-scheme") == 0 &&
|
||||
policy_color_scheme_from_variant (value, &color_scheme))
|
||||
policy_portal_store_color_scheme (color_scheme);
|
||||
|
||||
if (value)
|
||||
g_variant_unref (value);
|
||||
}
|
||||
|
||||
static void
|
||||
policy_portal_read_ready_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||||
{
|
||||
GVariant *reply;
|
||||
GVariant *value = NULL;
|
||||
guint32 color_scheme = THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE;
|
||||
|
||||
(void) user_data;
|
||||
|
||||
reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, NULL);
|
||||
if (!reply)
|
||||
return;
|
||||
|
||||
if (g_variant_is_of_type (reply, G_VARIANT_TYPE ("(v)")))
|
||||
{
|
||||
g_variant_get (reply, "(v)", &value);
|
||||
if (policy_color_scheme_from_variant (value, &color_scheme))
|
||||
policy_portal_store_color_scheme (color_scheme);
|
||||
if (value)
|
||||
g_variant_unref (value);
|
||||
}
|
||||
|
||||
g_variant_unref (reply);
|
||||
}
|
||||
|
||||
static void
|
||||
policy_portal_bus_ready_cb (GObject *source, GAsyncResult *result, gpointer user_data)
|
||||
{
|
||||
GDBusConnection *connection;
|
||||
|
||||
(void) source;
|
||||
(void) user_data;
|
||||
|
||||
connection = g_bus_get_finish (result, NULL);
|
||||
if (!connection)
|
||||
return;
|
||||
|
||||
g_dbus_connection_signal_subscribe (connection,
|
||||
"org.freedesktop.portal.Desktop",
|
||||
"org.freedesktop.portal.Settings",
|
||||
"SettingChanged",
|
||||
"/org/freedesktop/portal/desktop",
|
||||
NULL,
|
||||
G_DBUS_SIGNAL_FLAGS_NONE,
|
||||
policy_portal_setting_changed_cb,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
g_dbus_connection_call (connection,
|
||||
"org.freedesktop.portal.Desktop",
|
||||
"/org/freedesktop/portal/desktop",
|
||||
"org.freedesktop.portal.Settings",
|
||||
"Read",
|
||||
g_variant_new ("(ss)", "org.freedesktop.appearance", "color-scheme"),
|
||||
G_VARIANT_TYPE ("(v)"),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
3000,
|
||||
NULL,
|
||||
policy_portal_read_ready_cb,
|
||||
NULL);
|
||||
g_object_unref (connection);
|
||||
}
|
||||
|
||||
static void
|
||||
policy_portal_watch_start (void)
|
||||
{
|
||||
if (policy_portal_watch_started)
|
||||
return;
|
||||
|
||||
policy_portal_watch_started = TRUE;
|
||||
g_bus_get (G_BUS_TYPE_SESSION, NULL, policy_portal_bus_ready_cb, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
policy_gsettings_changed_cb (GSettings *settings, const char *key, gpointer user_data)
|
||||
{
|
||||
(void) settings;
|
||||
(void) key;
|
||||
(void) user_data;
|
||||
|
||||
policy_notify_appearance_changed ();
|
||||
}
|
||||
|
||||
static GSettings *
|
||||
policy_get_interface_gsettings (void)
|
||||
{
|
||||
GSettingsSchemaSource *source;
|
||||
GSettingsSchema *schema;
|
||||
gboolean has_key;
|
||||
|
||||
if (policy_gsettings_checked)
|
||||
return policy_gsettings_interface;
|
||||
|
||||
policy_gsettings_checked = TRUE;
|
||||
|
||||
source = g_settings_schema_source_get_default ();
|
||||
if (!source)
|
||||
return NULL;
|
||||
|
||||
schema = g_settings_schema_source_lookup (source, "org.gnome.desktop.interface", TRUE);
|
||||
if (!schema)
|
||||
return NULL;
|
||||
|
||||
/* Requires GLib 2.40, always satisfied by the GTK 3.22 dependency. */
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||
has_key = g_settings_schema_has_key (schema, "color-scheme");
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
g_settings_schema_unref (schema);
|
||||
if (!has_key)
|
||||
return NULL;
|
||||
|
||||
policy_gsettings_interface = g_settings_new ("org.gnome.desktop.interface");
|
||||
g_signal_connect (policy_gsettings_interface, "changed::color-scheme",
|
||||
G_CALLBACK (policy_gsettings_changed_cb), NULL);
|
||||
return policy_gsettings_interface;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
policy_gsettings_color_scheme (gboolean *prefers_dark)
|
||||
{
|
||||
GSettings *settings;
|
||||
char *scheme;
|
||||
gboolean explicit_preference = FALSE;
|
||||
|
||||
settings = policy_get_interface_gsettings ();
|
||||
if (!settings)
|
||||
return FALSE;
|
||||
|
||||
scheme = g_settings_get_string (settings, "color-scheme");
|
||||
if (g_strcmp0 (scheme, "prefer-dark") == 0)
|
||||
{
|
||||
*prefers_dark = TRUE;
|
||||
explicit_preference = TRUE;
|
||||
}
|
||||
else if (g_strcmp0 (scheme, "prefer-light") == 0)
|
||||
{
|
||||
*prefers_dark = FALSE;
|
||||
explicit_preference = TRUE;
|
||||
}
|
||||
g_free (scheme);
|
||||
return explicit_preference;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
policy_gtk_heuristic_prefers_dark (void)
|
||||
{
|
||||
GtkSettings *settings;
|
||||
char *theme_name = NULL;
|
||||
gboolean dark;
|
||||
|
||||
policy_capture_initial_gtk_state ();
|
||||
|
||||
/* gtk-application-prefer-dark-theme is overwritten by ZoiteChat at
|
||||
* runtime, so only its captured startup value (settings.ini, XSETTINGS)
|
||||
* reflects the user's own configuration. */
|
||||
if (policy_initial_gtk_state_captured && policy_initial_prefer_dark)
|
||||
return TRUE;
|
||||
|
||||
if (theme_gtk3_is_active ())
|
||||
{
|
||||
/* gtk-theme-name currently holds the in-app theme override;
|
||||
* fall back to the captured system theme name. */
|
||||
theme_name = g_strdup (policy_initial_theme_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
settings = gtk_settings_get_default ();
|
||||
if (settings)
|
||||
g_object_get (settings, "gtk-theme-name", &theme_name, NULL);
|
||||
}
|
||||
|
||||
dark = policy_theme_name_is_dark (theme_name);
|
||||
g_free (theme_name);
|
||||
return dark;
|
||||
}
|
||||
|
||||
#endif /* !G_OS_WIN32 */
|
||||
|
||||
void
|
||||
theme_policy_init (void)
|
||||
{
|
||||
policy_capture_initial_gtk_state ();
|
||||
#ifndef G_OS_WIN32
|
||||
policy_portal_watch_start ();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
theme_policy_set_appearance_changed_callback (ThemePolicyAppearanceChangedFunc callback)
|
||||
{
|
||||
policy_appearance_changed_cb = callback;
|
||||
}
|
||||
|
||||
gboolean
|
||||
theme_policy_system_prefers_dark (void)
|
||||
{
|
||||
return FALSE;
|
||||
#ifdef G_OS_WIN32
|
||||
gboolean prefer_dark = FALSE;
|
||||
|
||||
policy_capture_initial_gtk_state ();
|
||||
|
||||
if (fe_win32_high_contrast_is_enabled ())
|
||||
return FALSE;
|
||||
if (fe_win32_try_get_system_dark (&prefer_dark))
|
||||
return prefer_dark;
|
||||
return policy_theme_name_is_dark (policy_initial_theme_name);
|
||||
#else
|
||||
gboolean prefers_dark = FALSE;
|
||||
|
||||
policy_capture_initial_gtk_state ();
|
||||
policy_portal_watch_start ();
|
||||
|
||||
if (policy_portal_value_valid &&
|
||||
policy_portal_color_scheme != THEME_POLICY_COLOR_SCHEME_NO_PREFERENCE)
|
||||
return policy_portal_color_scheme == THEME_POLICY_COLOR_SCHEME_PREFER_DARK;
|
||||
|
||||
if (policy_gsettings_color_scheme (&prefers_dark))
|
||||
return prefers_dark;
|
||||
|
||||
return policy_gtk_heuristic_prefers_dark ();
|
||||
#endif
|
||||
}
|
||||
|
||||
gboolean
|
||||
|
||||
Reference in New Issue
Block a user