diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c index 4dc10533..45a30383 100644 --- a/src/common/cfgfiles.c +++ b/src/common/cfgfiles.c @@ -419,6 +419,7 @@ const struct prefs vars[] = {"gui_input_nick", P_OFFINT (hex_gui_input_nick), TYPE_BOOL}, {"gui_input_spell", P_OFFINT (hex_gui_input_spell), TYPE_BOOL}, {"gui_input_style", P_OFFINT (hex_gui_input_style), TYPE_BOOL}, + {"gui_gtk3_theme_name", P_OFFSET (hex_gui_gtk3_theme_name), TYPE_STR}, {"gui_join_dialog", P_OFFINT (hex_gui_join_dialog), TYPE_BOOL}, {"gui_lagometer", P_OFFINT (hex_gui_lagometer), TYPE_INT}, {"gui_lang", P_OFFINT (hex_gui_lang), TYPE_INT}, diff --git a/src/common/zoitechat.h b/src/common/zoitechat.h index 9d7908b2..3e62b3a0 100644 --- a/src/common/zoitechat.h +++ b/src/common/zoitechat.h @@ -297,6 +297,7 @@ struct zoitechatprefs char hex_dcc_completed_dir[PATHLEN + 1]; char hex_dcc_dir[PATHLEN + 1]; char hex_dcc_ip[DOMAINLEN + 1]; + char hex_gui_gtk3_theme_name[128]; char hex_gui_ulist_doubleclick[256]; char hex_input_command_char[4]; char hex_irc_extra_hilight[300]; diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c index b8e380b4..2d4ecca1 100644 --- a/src/fe-gtk/fe-gtk.c +++ b/src/fe-gtk/fe-gtk.c @@ -590,6 +590,109 @@ fe_system_prefers_dark (void) static gboolean auto_dark_mode_enabled = FALSE; static gboolean dark_mode_state_initialized = FALSE; + +static GtkCssProvider *gtk3_theme_provider = NULL; +static char *gtk3_theme_provider_name = NULL; +static gboolean gtk3_theme_provider_dark = FALSE; + +gboolean +fe_apply_gtk3_theme (const char *theme_name, GError **error) +{ + GdkScreen *screen = gdk_screen_get_default (); + char *theme_dir = NULL; + char *gtk3_dir = NULL; + char *gtk_css = NULL; + char *gtk_dark_css = NULL; + const char *selected_css = NULL; + gboolean dark = fe_dark_mode_is_enabled (); + GList *toplevels, *node; + + if (!theme_name || !*theme_name) + { + if (gtk3_theme_provider && screen) + { + gtk_style_context_remove_provider_for_screen ( + screen, + GTK_STYLE_PROVIDER (gtk3_theme_provider)); + } + g_clear_object (>k3_theme_provider); + g_clear_pointer (>k3_theme_provider_name, g_free); + gtk3_theme_provider_dark = FALSE; + + toplevels = gtk_window_list_toplevels (); + for (node = toplevels; node; node = node->next) + fe_apply_theme_to_toplevel (GTK_WIDGET (node->data)); + g_list_free (toplevels); + + return TRUE; + } + + if (gtk3_theme_provider_name + && g_strcmp0 (gtk3_theme_provider_name, theme_name) == 0 + && gtk3_theme_provider_dark == dark) + { + return TRUE; + } + + theme_dir = g_build_filename (get_xdir (), "gtk3-themes", theme_name, NULL); + gtk3_dir = g_build_filename (theme_dir, "gtk-3.0", NULL); + gtk_css = g_build_filename (gtk3_dir, "gtk.css", NULL); + gtk_dark_css = g_build_filename (gtk3_dir, "gtk-dark.css", NULL); + + if (!g_file_test (gtk_css, G_FILE_TEST_IS_REGULAR)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, + _("GTK3 theme '%s' is missing gtk-3.0/gtk.css."), theme_name); + g_free (gtk_dark_css); + g_free (gtk_css); + g_free (gtk3_dir); + g_free (theme_dir); + return FALSE; + } + + if (dark && g_file_test (gtk_dark_css, G_FILE_TEST_IS_REGULAR)) + selected_css = gtk_dark_css; + else + selected_css = gtk_css; + + if (!gtk3_theme_provider) + gtk3_theme_provider = gtk_css_provider_new (); + + if (!gtk_css_provider_load_from_path (gtk3_theme_provider, selected_css, error)) + { + g_free (gtk_dark_css); + g_free (gtk_css); + g_free (gtk3_dir); + g_free (theme_dir); + return FALSE; + } + + if (screen) + { + gtk_style_context_add_provider_for_screen ( + screen, + GTK_STYLE_PROVIDER (gtk3_theme_provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + gtk_style_context_reset_widgets (screen); + } + + g_free (gtk3_theme_provider_name); + gtk3_theme_provider_name = g_strdup (theme_name); + gtk3_theme_provider_dark = dark; + + toplevels = gtk_window_list_toplevels (); + for (node = toplevels; node; node = node->next) + fe_apply_theme_to_toplevel (GTK_WIDGET (node->data)); + g_list_free (toplevels); + + g_free (gtk_dark_css); + g_free (gtk_css); + g_free (gtk3_dir); + g_free (theme_dir); + return TRUE; +} + + static void fe_set_gtk_prefer_dark_theme (gboolean dark) { @@ -722,6 +825,12 @@ fe_apply_theme_for_mode (unsigned int mode, gboolean *palette_changed) if (input_style) create_input_style (input_style); + if (!fe_apply_gtk3_theme (prefs.hex_gui_gtk3_theme_name, NULL) + && prefs.hex_gui_gtk3_theme_name[0] != '\0') + { + fe_message (_("Failed to apply configured GTK3 theme from gtk3-themes."), FE_MSG_ERROR); + } + /* Existing toplevel windows also need the class refreshed for selectors like * .zoitechat-dark / .zoitechat-light to update immediately. */ toplevels = gtk_window_list_toplevels (); diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h index 83b5be0e..c597c80d 100644 --- a/src/fe-gtk/fe-gtk.h +++ b/src/fe-gtk/fe-gtk.h @@ -193,6 +193,7 @@ gboolean fe_dark_mode_state_is_initialized (void); void fe_set_auto_dark_mode_state (gboolean enabled); void fe_refresh_auto_dark_mode (void); gboolean fe_apply_theme_for_mode (unsigned int mode, gboolean *palette_changed); +gboolean fe_apply_gtk3_theme (const char *theme_name, GError **error); void fe_apply_theme_to_toplevel (GtkWidget *window); #define SPELL_ENTRY_GET_TEXT(e) ((char *)(gtk_entry_get_text (GTK_ENTRY(e)))) diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 2925b758..81c0a1c5 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -72,6 +72,7 @@ typedef struct GtkWidget *gtk3_combo; GtkWidget *gtk3_import_button; GtkWidget *gtk3_apply_button; + GtkWidget *gtk3_use_system_button; GtkWidget *gtk3_status_label; } setup_theme_ui; @@ -2068,12 +2069,14 @@ setup_theme_populate_gtk3 (setup_theme_ui *ui) gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (ui->gtk3_combo), g_ptr_array_index (names, i)); settings = gtk_settings_get_default (); - if (settings) + if (prefs.hex_gui_gtk3_theme_name[0] != '\0') + current_theme = g_strdup (prefs.hex_gui_gtk3_theme_name); + else if (settings) g_object_get (settings, "gtk-theme-name", ¤t_theme, NULL); if (names->len > 0) { - gtk_combo_box_set_active (GTK_COMBO_BOX (ui->gtk3_combo), 0); + gtk_combo_box_set_active (GTK_COMBO_BOX (ui->gtk3_combo), -1); if (current_theme) { for (i = 0; i < names->len; i++) @@ -2087,9 +2090,10 @@ setup_theme_populate_gtk3 (setup_theme_ui *ui) } } - gtk_widget_set_sensitive (ui->gtk3_apply_button, names->len > 0); + gtk_widget_set_sensitive (ui->gtk3_apply_button, + gtk_combo_box_get_active (GTK_COMBO_BOX (ui->gtk3_combo)) >= 0); gtk_label_set_text (GTK_LABEL (ui->gtk3_status_label), - names->len > 0 ? _("Select a GTK3 theme to activate for the interface.") : _("No GTK3 themes found.")); + names->len > 0 ? _("Select a GTK3 theme to activate from the dropdown, or use the system GTK theme.") : _("No GTK3 themes found.")); g_ptr_array_free (names, TRUE); g_hash_table_destroy (seen); @@ -2169,28 +2173,42 @@ static void setup_theme_gtk3_apply_cb (GtkWidget *button, gpointer user_data) { setup_theme_ui *ui = user_data; - GtkSettings *settings; char *theme; + GError *error = NULL; theme = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (ui->gtk3_combo)); if (!theme) return; - settings = gtk_settings_get_default (); - if (!settings) + if (!fe_apply_gtk3_theme (theme, &error)) { - setup_theme_show_message (GTK_MESSAGE_ERROR, _("GTK settings are unavailable, cannot activate GTK3 theme.")); + setup_theme_show_message (GTK_MESSAGE_ERROR, + error ? error->message : _("Failed to apply GTK3 theme.")); + g_clear_error (&error); g_free (theme); return; } - g_object_set (settings, "gtk-theme-name", theme, NULL); - gtk_label_set_text (GTK_LABEL (ui->gtk3_status_label), _("GTK3 theme activated for this session.")); - setup_theme_show_message (GTK_MESSAGE_INFO, _("GTK3 theme activated. Application palette settings were not changed.")); + safe_strcpy (prefs.hex_gui_gtk3_theme_name, theme, sizeof (prefs.hex_gui_gtk3_theme_name)); + save_config (); + gtk_label_set_text (GTK_LABEL (ui->gtk3_status_label), _("GTK3 theme activated from ZoiteChat's local theme store.")); + setup_theme_show_message (GTK_MESSAGE_INFO, _("GTK3 theme activated and saved.")); g_free (theme); } +static void +setup_theme_gtk3_use_system_cb (GtkWidget *button, gpointer user_data) +{ + setup_theme_ui *ui = user_data; + + fe_apply_gtk3_theme (NULL, NULL); + prefs.hex_gui_gtk3_theme_name[0] = '\0'; + save_config (); + gtk_label_set_text (GTK_LABEL (ui->gtk3_status_label), _("Using system GTK theme.")); + setup_theme_show_message (GTK_MESSAGE_INFO, _("Using system GTK theme.")); +} + static GtkWidget * setup_create_theme_page (void) { @@ -2262,7 +2280,7 @@ setup_create_theme_page (void) gtk_container_set_border_width (GTK_CONTAINER (hbox), 6); gtk_container_add (GTK_CONTAINER (frame), hbox); - label = gtk_label_new (_("Import a GTK3 theme archive or select an installed GTK3 theme.")); + label = gtk_label_new (_("Import a GTK3 theme archive or select a GTK3 theme installed in ZoiteChat's local theme store.")); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); @@ -2285,6 +2303,11 @@ setup_create_theme_page (void) g_signal_connect (G_OBJECT (ui->gtk3_apply_button), "clicked", G_CALLBACK (setup_theme_gtk3_apply_cb), ui); + ui->gtk3_use_system_button = gtk_button_new_with_mnemonic (_("Use _System GTK Theme")); + gtk_box_pack_start (GTK_BOX (button_box), ui->gtk3_use_system_button, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (ui->gtk3_use_system_button), "clicked", + G_CALLBACK (setup_theme_gtk3_use_system_cb), ui); + ui->gtk3_status_label = gtk_label_new (NULL); gtk_widget_set_halign (ui->gtk3_status_label, GTK_ALIGN_START); gtk_widget_set_valign (ui->gtk3_status_label, GTK_ALIGN_CENTER);