/* ZoiteChat * Copyright (C) 1998-2010 Peter Zelezny. * Copyright (C) 2009-2013 Berke Viktor. * Copyright (C) 2026 deepend-tildeclub. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "theme-gtk3.h" #include "../../common/zoitechat.h" #include "../../common/zoitechatc.h" #include #include #include #include #include "theme-policy.h" #include "../../common/gtk3-theme-service.h" static GPtrArray *theme_gtk3_providers_base; static GPtrArray *theme_gtk3_providers_variant; static GHashTable *theme_gtk3_provider_cache; static gboolean theme_gtk3_active; static char *theme_gtk3_current_id; static ThemeGtk3Variant theme_gtk3_current_variant; typedef struct { GHashTable *defaults; char **icon_search_path; gint icon_search_path_count; gboolean icon_search_path_captured; } ThemeGtk3SettingsState; static ThemeGtk3SettingsState theme_gtk3_settings_state; static gboolean settings_apply_property (GtkSettings *settings, const char *property_name, const char *raw_value); static gboolean theme_gtk3_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; g_free (lower); return dark; } static ThemeGtk3Variant theme_gtk3_infer_variant (const ZoitechatGtk3Theme *theme) { char *css_dir; char *light_css; gboolean has_light_css; ThemeGtk3Variant variant; if (!theme) return THEME_GTK3_VARIANT_PREFER_LIGHT; css_dir = zoitechat_gtk3_theme_pick_css_dir (theme->path); light_css = css_dir ? g_build_filename (theme->path, css_dir, "gtk.css", NULL) : NULL; has_light_css = light_css && g_file_test (light_css, G_FILE_TEST_IS_REGULAR); g_free (light_css); g_free (css_dir); variant = THEME_GTK3_VARIANT_PREFER_LIGHT; if ((theme->has_dark_variant && !has_light_css) || theme_gtk3_theme_name_is_dark (theme->id) || theme_gtk3_theme_name_is_dark (theme->display_name)) variant = THEME_GTK3_VARIANT_PREFER_DARK; return variant; } static void settings_value_free (gpointer data) { GValue *value = data; if (!value) return; g_value_unset (value); g_free (value); } static GValue * settings_value_dup (const GValue *source) { GValue *copy; copy = g_new0 (GValue, 1); g_value_init (copy, G_VALUE_TYPE (source)); g_value_copy (source, copy); return copy; } static GHashTable * settings_defaults_table (void) { if (!theme_gtk3_settings_state.defaults) { theme_gtk3_settings_state.defaults = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, settings_value_free); } return theme_gtk3_settings_state.defaults; } static void settings_rescan_icon_theme (void) { GtkIconTheme *icon_theme; icon_theme = gtk_icon_theme_get_default (); if (!icon_theme) return; gtk_icon_theme_rescan_if_needed (icon_theme); } static void theme_gtk3_reset_widgets (void) { GdkScreen *screen = gdk_screen_get_default (); if (screen) gtk_style_context_reset_widgets (screen); } static void settings_capture_icon_search_path (void) { GtkIconTheme *icon_theme = gtk_icon_theme_get_default (); if (!icon_theme || theme_gtk3_settings_state.icon_search_path_captured) return; gtk_icon_theme_get_search_path (icon_theme, &theme_gtk3_settings_state.icon_search_path, &theme_gtk3_settings_state.icon_search_path_count); theme_gtk3_settings_state.icon_search_path_captured = TRUE; } static void settings_append_icon_search_path (const char *path) { GtkIconTheme *icon_theme = gtk_icon_theme_get_default (); if (!icon_theme || !path || !g_file_test (path, G_FILE_TEST_IS_DIR)) return; settings_capture_icon_search_path (); gtk_icon_theme_append_search_path (icon_theme, path); gtk_icon_theme_rescan_if_needed (icon_theme); } static void settings_apply_icon_paths (const char *theme_root) { char *icons_dir; char *theme_parent; if (!theme_root) return; icons_dir = g_build_filename (theme_root, "icons", NULL); theme_parent = g_path_get_dirname (theme_root); settings_append_icon_search_path (icons_dir); settings_append_icon_search_path (theme_root); settings_append_icon_search_path (theme_parent); g_free (theme_parent); g_free (icons_dir); } static void settings_restore_icon_search_path (void) { GtkIconTheme *icon_theme = gtk_icon_theme_get_default (); if (!icon_theme || !theme_gtk3_settings_state.icon_search_path_captured) return; gtk_icon_theme_set_search_path (icon_theme, (const char **) theme_gtk3_settings_state.icon_search_path, theme_gtk3_settings_state.icon_search_path_count); gtk_icon_theme_rescan_if_needed (icon_theme); g_strfreev (theme_gtk3_settings_state.icon_search_path); theme_gtk3_settings_state.icon_search_path = NULL; theme_gtk3_settings_state.icon_search_path_count = 0; theme_gtk3_settings_state.icon_search_path_captured = FALSE; } static void theme_gtk3_parsing_error_cb (GtkCssProvider *provider, GtkCssSection *section, const GError *error, gpointer user_data) { (void) provider; (void) section; (void) error; (void) user_data; g_signal_stop_emission_by_name (provider, "parsing-error"); } static GHashTable * theme_gtk3_provider_cache_table (void) { if (!theme_gtk3_provider_cache) { theme_gtk3_provider_cache = g_hash_table_new_full ( g_str_hash, g_str_equal, g_free, g_object_unref); } return theme_gtk3_provider_cache; } static char * theme_gtk3_provider_cache_key (const char *theme_root, const char *css_dir, gboolean prefer_dark) { return g_strdup_printf ("%s\n%s\n%d", theme_root, css_dir, prefer_dark ? 1 : 0); } static GtkCssProvider * theme_gtk3_provider_cache_load (const char *path, GError **error) { GtkCssProvider *provider; provider = gtk_css_provider_new (); g_signal_connect (provider, "parsing-error", G_CALLBACK (theme_gtk3_parsing_error_cb), NULL); if (!gtk_css_provider_load_from_path (provider, path, error)) { g_object_unref (provider); return NULL; } return provider; } static GtkCssProvider * theme_gtk3_provider_cache_get_or_load (const char *theme_root, const char *css_dir, gboolean prefer_dark, GError **error) { GHashTable *cache; char *key; char *css_path; GtkCssProvider *provider; cache = theme_gtk3_provider_cache_table (); key = theme_gtk3_provider_cache_key (theme_root, css_dir, prefer_dark); provider = g_hash_table_lookup (cache, key); if (provider) { g_object_ref (provider); g_free (key); return provider; } css_path = g_build_filename (theme_root, css_dir, prefer_dark ? "gtk-dark.css" : "gtk.css", NULL); provider = theme_gtk3_provider_cache_load (css_path, error); g_free (css_path); if (!provider) { g_free (key); return NULL; } g_hash_table_insert (cache, key, g_object_ref (provider)); return provider; } void theme_gtk3_invalidate_provider_cache (void) { if (theme_gtk3_provider_cache) g_hash_table_remove_all (theme_gtk3_provider_cache); } static void settings_apply_for_variant (ThemeGtk3Variant variant) { GtkSettings *settings = gtk_settings_get_default (); gboolean dark = FALSE; GValue current = G_VALUE_INIT; GParamSpec *property; if (!settings) return; if (variant == THEME_GTK3_VARIANT_PREFER_DARK) dark = TRUE; else if (variant == THEME_GTK3_VARIANT_FOLLOW_SYSTEM) dark = theme_policy_system_prefers_dark (); property = g_object_class_find_property (G_OBJECT_GET_CLASS (settings), "gtk-application-prefer-dark-theme"); if (!property) return; g_value_init (¤t, G_PARAM_SPEC_VALUE_TYPE (property)); g_object_get_property (G_OBJECT (settings), "gtk-application-prefer-dark-theme", ¤t); if (g_value_get_boolean (¤t) != dark) g_object_set (settings, "gtk-application-prefer-dark-theme", dark, NULL); g_value_unset (¤t); } static gboolean settings_theme_root_is_searchable (const char *theme_root) { char *parent; char *user_data_themes; char *home_themes; const gchar *const *system_data_dirs; guint i; gboolean searchable = FALSE; if (!theme_root || !theme_root[0]) return FALSE; parent = g_path_get_dirname (theme_root); user_data_themes = g_build_filename (g_get_user_data_dir (), "themes", NULL); home_themes = g_build_filename (g_get_home_dir (), ".themes", NULL); if (g_strcmp0 (parent, user_data_themes) == 0 || g_strcmp0 (parent, home_themes) == 0 || g_strcmp0 (parent, "/usr/share/themes") == 0) searchable = TRUE; system_data_dirs = g_get_system_data_dirs (); for (i = 0; !searchable && system_data_dirs && system_data_dirs[i]; i++) { char *system_themes = g_build_filename (system_data_dirs[i], "themes", NULL); if (g_strcmp0 (parent, system_themes) == 0) searchable = TRUE; g_free (system_themes); } g_free (home_themes); g_free (user_data_themes); g_free (parent); return searchable; } static gboolean settings_theme_link_search_path (const char *theme_root, const char *theme_name) { char *themes_root; char *link_path; gboolean ok = TRUE; if (!theme_root || !theme_name || !theme_name[0]) return FALSE; themes_root = g_build_filename (g_get_user_data_dir (), "themes", NULL); if (g_mkdir_with_parents (themes_root, 0700) != 0) { g_free (themes_root); return FALSE; } link_path = g_build_filename (themes_root, theme_name, NULL); if (!g_file_test (link_path, G_FILE_TEST_EXISTS)) { GFile *link_file = g_file_new_for_path (link_path); GError *link_error = NULL; ok = g_file_make_symbolic_link (link_file, theme_root, NULL, &link_error); g_clear_error (&link_error); g_object_unref (link_file); } g_free (link_path); g_free (themes_root); return ok; } static void settings_apply_theme_name (const char *theme_root) { GtkSettings *settings; char *theme_name; if (!theme_root) return; settings = gtk_settings_get_default (); if (!settings) return; theme_name = g_path_get_basename (theme_root); if (theme_name && theme_name[0]) { gboolean searchable = settings_theme_root_is_searchable (theme_root); if (!searchable) searchable = settings_theme_link_search_path (theme_root, theme_name); if (searchable) settings_apply_property (settings, "gtk-theme-name", theme_name); } g_free (theme_name); } static gboolean settings_value_equal_typed (const GValue *a, const GValue *b, GType property_type) { if (property_type == G_TYPE_BOOLEAN) return g_value_get_boolean (a) == g_value_get_boolean (b); if (property_type == G_TYPE_STRING) return g_strcmp0 (g_value_get_string (a), g_value_get_string (b)) == 0; if (property_type == G_TYPE_INT) return g_value_get_int (a) == g_value_get_int (b); if (property_type == G_TYPE_UINT) return g_value_get_uint (a) == g_value_get_uint (b); if (property_type == G_TYPE_FLOAT) return g_value_get_float (a) == g_value_get_float (b); if (property_type == G_TYPE_DOUBLE) return g_value_get_double (a) == g_value_get_double (b); if (G_TYPE_IS_ENUM (property_type)) return g_value_get_enum (a) == g_value_get_enum (b); if (G_TYPE_IS_FLAGS (property_type)) return g_value_get_flags (a) == g_value_get_flags (b); return FALSE; } static gboolean settings_parse_long (const char *text, glong min_value, glong max_value, glong *value) { char *end = NULL; gint64 parsed; if (!text) return FALSE; parsed = g_ascii_strtoll (text, &end, 10); if (end == text || *end != '\0') return FALSE; if (parsed < min_value || parsed > max_value) return FALSE; *value = (glong) parsed; return TRUE; } static void settings_remember_default (GtkSettings *settings, const char *property_name, GParamSpec *property) { GHashTable *defaults; GValue current = G_VALUE_INIT; if (!settings || !property_name || !property) return; defaults = settings_defaults_table (); if (g_hash_table_contains (defaults, property_name)) return; g_value_init (¤t, G_PARAM_SPEC_VALUE_TYPE (property)); g_object_get_property (G_OBJECT (settings), property_name, ¤t); g_hash_table_insert (defaults, g_strdup (property_name), settings_value_dup (¤t)); g_value_unset (¤t); } static gboolean settings_apply_property (GtkSettings *settings, const char *property_name, const char *raw_value) { GParamSpec *property; GValue value = G_VALUE_INIT; GValue current = G_VALUE_INIT; GType property_type; gboolean ok = FALSE; gboolean changed = TRUE; property = g_object_class_find_property (G_OBJECT_GET_CLASS (settings), property_name); if (!property) return FALSE; settings_remember_default (settings, property_name, property); property_type = G_PARAM_SPEC_VALUE_TYPE (property); g_value_init (&value, property_type); if (property_type == G_TYPE_BOOLEAN) { if (g_ascii_strcasecmp (raw_value, "true") == 0 || g_ascii_strcasecmp (raw_value, "yes") == 0 || g_strcmp0 (raw_value, "1") == 0) { g_value_set_boolean (&value, TRUE); ok = TRUE; } else if (g_ascii_strcasecmp (raw_value, "false") == 0 || g_ascii_strcasecmp (raw_value, "no") == 0 || g_strcmp0 (raw_value, "0") == 0) { g_value_set_boolean (&value, FALSE); ok = TRUE; } } else if (property_type == G_TYPE_STRING) { g_value_set_string (&value, raw_value); ok = TRUE; } else if (property_type == G_TYPE_INT) { glong parsed; if (settings_parse_long (raw_value, G_MININT, G_MAXINT, &parsed)) { g_value_set_int (&value, (gint) parsed); ok = TRUE; } } else if (property_type == G_TYPE_UINT) { glong parsed; if (settings_parse_long (raw_value, 0, G_MAXUINT, &parsed)) { g_value_set_uint (&value, (guint) parsed); ok = TRUE; } } else if (property_type == G_TYPE_DOUBLE) { char *end = NULL; double parsed = g_ascii_strtod (raw_value, &end); if (end != raw_value && *end == '\0') { g_value_set_double (&value, parsed); ok = TRUE; } } else if (property_type == G_TYPE_FLOAT) { char *end = NULL; double parsed = g_ascii_strtod (raw_value, &end); if (end != raw_value && *end == '\0') { g_value_set_float (&value, (gfloat) parsed); ok = TRUE; } } else if (G_TYPE_IS_ENUM (property_type)) { GEnumClass *enum_class = g_type_class_ref (property_type); GEnumValue *enum_value = g_enum_get_value_by_nick (enum_class, raw_value); if (!enum_value) enum_value = g_enum_get_value_by_name (enum_class, raw_value); if (!enum_value) { glong parsed; if (settings_parse_long (raw_value, G_MININT, G_MAXINT, &parsed)) enum_value = g_enum_get_value (enum_class, (gint) parsed); } if (enum_value) { g_value_set_enum (&value, enum_value->value); ok = TRUE; } g_type_class_unref (enum_class); } else if (G_TYPE_IS_FLAGS (property_type)) { GFlagsClass *flags_class = g_type_class_ref (property_type); char **tokens = g_strsplit_set (raw_value, ",|", -1); guint flags_value = 0; guint i = 0; for (; tokens && tokens[i]; i++) { char *token = g_strstrip (tokens[i]); GFlagsValue *flag_value; if (!token[0]) continue; flag_value = g_flags_get_value_by_nick (flags_class, token); if (!flag_value) flag_value = g_flags_get_value_by_name (flags_class, token); if (!flag_value) { glong parsed; if (!settings_parse_long (token, 0, G_MAXUINT, &parsed)) { ok = FALSE; break; } flags_value |= (guint) parsed; ok = TRUE; continue; } flags_value |= flag_value->value; ok = TRUE; } if (ok) g_value_set_flags (&value, flags_value); g_strfreev (tokens); g_type_class_unref (flags_class); } if (ok) { g_value_init (¤t, property_type); g_object_get_property (G_OBJECT (settings), property_name, ¤t); changed = !settings_value_equal_typed (¤t, &value, property_type); g_value_unset (¤t); } if (ok && changed) g_object_set_property (G_OBJECT (settings), property_name, &value); g_value_unset (&value); return ok; } static void settings_restore_defaults (void) { GtkSettings *settings = gtk_settings_get_default (); GHashTableIter iter; gpointer key; gpointer value; if (settings && theme_gtk3_settings_state.defaults) { g_hash_table_iter_init (&iter, theme_gtk3_settings_state.defaults); while (g_hash_table_iter_next (&iter, &key, &value)) g_object_set_property (G_OBJECT (settings), (const char *) key, (const GValue *) value); g_hash_table_remove_all (theme_gtk3_settings_state.defaults); } settings_rescan_icon_theme (); settings_restore_icon_search_path (); } static void settings_cleanup (void) { if (theme_gtk3_settings_state.defaults) { g_hash_table_destroy (theme_gtk3_settings_state.defaults); theme_gtk3_settings_state.defaults = NULL; } if (theme_gtk3_settings_state.icon_search_path_captured) settings_restore_icon_search_path (); } static void settings_apply_from_file (const char *theme_root, const char *css_dir) { GtkSettings *settings; GPtrArray *settings_paths; char *selected_path; char *fallback_path; guint layer; settings = gtk_settings_get_default (); if (!settings) return; if (!css_dir) return; settings_apply_icon_paths (theme_root); settings_paths = g_ptr_array_new_with_free_func (g_free); selected_path = g_build_filename (theme_root, css_dir, "settings.ini", NULL); fallback_path = g_build_filename (theme_root, "gtk-3.0", "settings.ini", NULL); if (g_strcmp0 (css_dir, "gtk-3.0") != 0) g_ptr_array_add (settings_paths, fallback_path); else g_free (fallback_path); g_ptr_array_add (settings_paths, selected_path); for (layer = 0; layer < settings_paths->len; layer++) { GKeyFile *keyfile; char **keys; gsize n_keys = 0; gsize i; const char *settings_path = g_ptr_array_index (settings_paths, layer); if (!g_file_test (settings_path, G_FILE_TEST_IS_REGULAR)) continue; keyfile = g_key_file_new (); if (!g_key_file_load_from_file (keyfile, settings_path, G_KEY_FILE_NONE, NULL)) { g_key_file_unref (keyfile); continue; } keys = g_key_file_get_keys (keyfile, "Settings", &n_keys, NULL); for (i = 0; keys && i < n_keys; i++) { char *raw_value; char *value; raw_value = g_key_file_get_value (keyfile, "Settings", keys[i], NULL); if (!raw_value) continue; value = g_strstrip (raw_value); if (value[0] != '\0') settings_apply_property (settings, keys[i], value); g_free (raw_value); } g_strfreev (keys); g_key_file_unref (keyfile); } settings_rescan_icon_theme (); g_ptr_array_unref (settings_paths); } static void theme_gtk3_remove_provider (void) { GdkScreen *screen = gdk_screen_get_default (); guint i; if (screen && theme_gtk3_providers_variant) { for (i = 0; i < theme_gtk3_providers_variant->len; i++) { GtkCssProvider *provider = g_ptr_array_index (theme_gtk3_providers_variant, i); gtk_style_context_remove_provider_for_screen (screen, GTK_STYLE_PROVIDER (provider)); } } if (screen && theme_gtk3_providers_base) { for (i = 0; i < theme_gtk3_providers_base->len; i++) { GtkCssProvider *provider = g_ptr_array_index (theme_gtk3_providers_base, i); gtk_style_context_remove_provider_for_screen (screen, GTK_STYLE_PROVIDER (provider)); } } if (theme_gtk3_providers_variant) g_ptr_array_unref (theme_gtk3_providers_variant); if (theme_gtk3_providers_base) g_ptr_array_unref (theme_gtk3_providers_base); theme_gtk3_providers_variant = NULL; theme_gtk3_providers_base = NULL; settings_restore_defaults (); theme_gtk3_reset_widgets (); theme_gtk3_active = FALSE; } static gboolean load_css_with_variant (ZoitechatGtk3Theme *theme, ThemeGtk3Variant variant, GError **error) { gboolean prefer_dark = FALSE; GdkScreen *screen; GPtrArray *chain; guint i; if (variant == THEME_GTK3_VARIANT_PREFER_DARK) prefer_dark = TRUE; else if (variant == THEME_GTK3_VARIANT_FOLLOW_SYSTEM) prefer_dark = theme_policy_system_prefers_dark (); settings_apply_theme_name (theme->path); chain = zoitechat_gtk3_theme_build_inheritance_chain (theme->path); if (!chain || chain->len == 0) { if (chain) g_ptr_array_unref (chain); return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "GTK3 theme CSS not found."), FALSE; } theme_gtk3_providers_base = g_ptr_array_new_with_free_func (g_object_unref); theme_gtk3_providers_variant = g_ptr_array_new_with_free_func (g_object_unref); screen = gdk_screen_get_default (); for (i = 0; i < chain->len; i++) { const char *theme_root = g_ptr_array_index (chain, i); char *css_dir = zoitechat_gtk3_theme_pick_css_dir_for_minor (theme_root, gtk_get_minor_version ()); char *variant_css; GtkCssProvider *provider; GtkCssProvider *variant_provider; if (!css_dir) continue; provider = theme_gtk3_provider_cache_get_or_load (theme_root, css_dir, FALSE, error); if (!provider) { g_free (css_dir); g_ptr_array_unref (chain); return FALSE; } if (screen) gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER + (gint) (i * 2)); g_ptr_array_add (theme_gtk3_providers_base, provider); variant_css = g_build_filename (theme_root, css_dir, "gtk-dark.css", NULL); if (prefer_dark && g_file_test (variant_css, G_FILE_TEST_IS_REGULAR)) { variant_provider = theme_gtk3_provider_cache_get_or_load (theme_root, css_dir, TRUE, error); if (!variant_provider) { g_free (variant_css); g_free (css_dir); g_ptr_array_unref (chain); return FALSE; } if (screen) gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (variant_provider), GTK_STYLE_PROVIDER_PRIORITY_USER + (gint) (i * 2) + 1); g_ptr_array_add (theme_gtk3_providers_variant, variant_provider); } g_free (variant_css); settings_apply_from_file (theme_root, css_dir); g_free (css_dir); } g_ptr_array_unref (chain); settings_apply_for_variant (variant); theme_gtk3_reset_widgets (); theme_gtk3_active = TRUE; return TRUE; } static gboolean theme_gtk3_apply_internal (const char *theme_id, ThemeGtk3Variant variant, gboolean force_reload, GError **error) { ZoitechatGtk3Theme *theme; char *previous_id = g_strdup (theme_gtk3_current_id); ThemeGtk3Variant previous_variant = theme_gtk3_current_variant; gboolean had_previous = theme_gtk3_active && previous_id && previous_id[0]; gboolean ok; if (!force_reload && theme_gtk3_active && g_strcmp0 (theme_gtk3_current_id, theme_id) == 0 && theme_gtk3_current_variant == variant) return TRUE; theme = zoitechat_gtk3_theme_find_by_id (theme_id); if (!theme) { g_free (previous_id); return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "GTK3 theme not found."), FALSE; } theme_gtk3_remove_provider (); if (force_reload) theme_gtk3_invalidate_provider_cache (); ok = load_css_with_variant (theme, variant, error); zoitechat_gtk3_theme_free (theme); if (ok) { g_free (theme_gtk3_current_id); theme_gtk3_current_id = g_strdup (theme_id); theme_gtk3_current_variant = variant; g_free (previous_id); return TRUE; } if (had_previous) { GError *restore_error = NULL; theme = zoitechat_gtk3_theme_find_by_id (previous_id); if (theme) { if (load_css_with_variant (theme, previous_variant, &restore_error)) { g_free (theme_gtk3_current_id); theme_gtk3_current_id = g_strdup (previous_id); theme_gtk3_current_variant = previous_variant; } zoitechat_gtk3_theme_free (theme); } g_clear_error (&restore_error); } g_free (previous_id); return ok; } gboolean theme_gtk3_apply (const char *theme_id, ThemeGtk3Variant variant, GError **error) { return theme_gtk3_apply_internal (theme_id, variant, FALSE, error); } gboolean theme_gtk3_refresh (const char *theme_id, ThemeGtk3Variant variant, GError **error) { return theme_gtk3_apply_internal (theme_id, variant, TRUE, error); } ThemeGtk3Variant theme_gtk3_variant_for_theme (const char *theme_id) { ZoitechatGtk3Theme *theme; ThemeGtk3Variant variant; theme = zoitechat_gtk3_theme_find_by_id (theme_id); if (!theme) return THEME_GTK3_VARIANT_PREFER_LIGHT; variant = theme_gtk3_infer_variant (theme); zoitechat_gtk3_theme_free (theme); return variant; } void theme_gtk3_disable (void) { theme_gtk3_remove_provider (); g_clear_pointer (&theme_gtk3_current_id, g_free); theme_gtk3_invalidate_provider_cache (); g_clear_pointer (&theme_gtk3_provider_cache, g_hash_table_destroy); settings_cleanup (); } void theme_gtk3_init (void) { theme_gtk3_apply_current (NULL); } gboolean theme_gtk3_apply_current (GError **error) { if (!prefs.hex_gui_gtk3_theme[0]) { theme_gtk3_disable (); return TRUE; } return theme_gtk3_apply (prefs.hex_gui_gtk3_theme, (ThemeGtk3Variant) prefs.hex_gui_gtk3_variant, error); } gboolean theme_gtk3_is_active (void) { return theme_gtk3_active; }