feat: GTK3-only theming overhaul—new theme service (discover/import/inherits), layered CSS+settings.ini apply w/ safe rollback + caching; widget/xtext palette mapping + all-colors editor; lots of win32/CI libarchive plumbing + installer assets;

This commit is contained in:
2026-03-04 23:28:01 -07:00
parent 43374f4fae
commit 50346683a1
56 changed files with 5642 additions and 754 deletions

View File

@@ -13,11 +13,16 @@ struct session *lastact_sess;
struct zoitechatprefs prefs;
static gboolean stub_dark_active;
static gboolean stub_gtk3_active;
static ThemeSemanticToken stub_last_color_token;
static int stub_runtime_get_color_calls;
static int stub_runtime_widget_calls;
static int stub_runtime_xtext_calls;
static int stub_runtime_xtext_mapped_calls;
static size_t stub_runtime_xtext_last_len;
static ThemeGtkPaletteMap stub_last_gtk_map;
static gboolean stub_last_gtk_map_valid;
static gboolean gtk_available;
static GdkRGBA stub_light_colors[THEME_TOKEN_COUNT];
static GdkRGBA stub_dark_colors[THEME_TOKEN_COUNT];
@@ -70,6 +75,13 @@ theme_runtime_dark_set_color (ThemeSemanticToken token, const GdkRGBA *col)
(void) col;
}
gboolean
theme_runtime_mode_has_user_colors (gboolean dark_mode)
{
(void) dark_mode;
return FALSE;
}
gboolean
theme_runtime_get_color (ThemeSemanticToken token, GdkRGBA *out_rgba)
{
@@ -109,6 +121,36 @@ theme_runtime_is_dark_active (void)
return stub_dark_active;
}
void
theme_runtime_get_widget_style_values_mapped (const ThemeGtkPaletteMap *gtk_map, ThemeWidgetStyleValues *out_values)
{
(void) gtk_map;
theme_runtime_get_widget_style_values (out_values);
}
void
theme_runtime_get_xtext_colors_mapped (const ThemeGtkPaletteMap *gtk_map, XTextColor *palette, size_t palette_len)
{
size_t i;
stub_runtime_xtext_mapped_calls++;
stub_last_gtk_map = *gtk_map;
stub_last_gtk_map_valid = TRUE;
stub_runtime_xtext_last_len = palette_len;
for (i = 0; i < palette_len; i++)
{
palette[i].red = (unsigned short) (100 + i);
palette[i].green = (unsigned short) (200 + i);
palette[i].blue = (unsigned short) (300 + i);
}
}
gboolean
theme_gtk3_is_active (void)
{
return stub_gtk3_active;
}
static gboolean
rgba_equal (const GdkRGBA *a, const GdkRGBA *b)
{
@@ -127,7 +169,10 @@ reset_stubs (void)
stub_runtime_get_color_calls = 0;
stub_runtime_widget_calls = 0;
stub_runtime_xtext_calls = 0;
stub_runtime_xtext_mapped_calls = 0;
stub_runtime_xtext_last_len = 0;
stub_last_gtk_map_valid = FALSE;
stub_gtk3_active = FALSE;
for (i = 0; i < THEME_TOKEN_COUNT; i++)
{
g_snprintf (light, sizeof (light), "#%02x%02x%02x", (unsigned int) (i + 1), 0x11, 0x22);
@@ -137,6 +182,15 @@ reset_stubs (void)
}
}
static gboolean
rgba_close (const GdkRGBA *a, const GdkRGBA *b)
{
return fabs (a->red - b->red) < 0.0001 &&
fabs (a->green - b->green) < 0.0001 &&
fabs (a->blue - b->blue) < 0.0001 &&
fabs (a->alpha - b->alpha) < 0.0001;
}
static void
test_access_semantic_token_routes_directly (void)
{
@@ -204,6 +258,62 @@ test_access_widget_style_forwarding (void)
g_assert_true (fabs (values.foreground.green - (0xfc / 255.0)) < 0.0001);
}
static void
test_access_xtext_palette_widget_mapping_when_gtk3_active (void)
{
GtkWidget *window;
GtkWidget *label;
GtkStyleContext *context;
GtkCssProvider *provider;
XTextColor palette[2] = { 0 };
GdkRGBA expected;
if (!gtk_available)
{
g_test_skip ("GTK display not available");
return;
}
reset_stubs ();
stub_gtk3_active = TRUE;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
label = gtk_label_new ("mapped");
gtk_container_add (GTK_CONTAINER (window), label);
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider,
"label { color: #112233; background-color: #445566; }"
"label:selected { color: #778899; background-color: #aabbcc; }"
"label:link { color: #123456; }",
-1,
NULL);
context = gtk_widget_get_style_context (label);
gtk_style_context_add_provider (context,
GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_USER);
gtk_widget_realize (window);
theme_get_xtext_colors_for_widget (label, palette, G_N_ELEMENTS (palette));
g_assert_cmpint (stub_runtime_xtext_mapped_calls, ==, 1);
g_assert_cmpint (stub_runtime_xtext_calls, ==, 0);
g_assert_true (stub_last_gtk_map_valid);
g_assert_true (gdk_rgba_parse (&expected, "#112233"));
g_assert_true (rgba_close (&stub_last_gtk_map.text_foreground, &expected));
g_assert_true (gdk_rgba_parse (&expected, "#445566"));
g_assert_true (rgba_close (&stub_last_gtk_map.text_background, &expected));
g_assert_true (gdk_rgba_parse (&expected, "#778899"));
g_assert_true (rgba_close (&stub_last_gtk_map.selection_foreground, &expected));
g_assert_true (gdk_rgba_parse (&expected, "#aabbcc"));
g_assert_true (rgba_close (&stub_last_gtk_map.selection_background, &expected));
g_assert_true (gdk_rgba_parse (&expected, "#123456"));
g_assert_true (rgba_close (&stub_last_gtk_map.accent, &expected));
g_assert_cmpuint (palette[0].red, ==, 100);
g_assert_cmpuint (palette[1].green, ==, 201);
gtk_widget_destroy (window);
g_object_unref (provider);
}
static void
test_access_dark_light_switch_affects_token_consumers (void)
{
@@ -229,8 +339,11 @@ main (int argc, char **argv)
g_test_add_func ("/theme/access/semantic_token_routes_directly", test_access_semantic_token_routes_directly);
g_test_add_func ("/theme/access/token_routes_without_legacy_accessor", test_access_token_routes_without_legacy_accessor);
g_test_add_func ("/theme/access/xtext_palette_forwarding", test_access_xtext_palette_forwarding);
g_test_add_func ("/theme/access/xtext_palette_widget_mapping_when_gtk3_active",
test_access_xtext_palette_widget_mapping_when_gtk3_active);
g_test_add_func ("/theme/access/widget_style_forwarding", test_access_widget_style_forwarding);
g_test_add_func ("/theme/access/dark_light_switch_affects_token_consumers",
test_access_dark_light_switch_affects_token_consumers);
gtk_available = gtk_init_check (&argc, &argv);
return g_test_run ();
}

View File

@@ -0,0 +1,325 @@
#include <glib.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include "../theme-gtk3.h"
#include "../../../common/gtk3-theme-service.h"
#include "../../../common/zoitechat.h"
#include "../../../common/zoitechatc.h"
struct session *current_sess;
struct session *current_tab;
struct session *lastact_sess;
struct zoitechatprefs prefs;
static gboolean gtk_available;
static char *temp_root;
static char *theme_parent_root;
static char *theme_child_root;
static char *theme_switch_root;
gboolean
theme_policy_system_prefers_dark (void)
{
return FALSE;
}
static void
remove_tree (const char *path)
{
GDir *dir;
const char *name;
if (!path || !g_file_test (path, G_FILE_TEST_EXISTS))
return;
if (!g_file_test (path, G_FILE_TEST_IS_DIR))
{
g_remove (path);
return;
}
dir = g_dir_open (path, 0, NULL);
if (dir)
{
while ((name = g_dir_read_name (dir)) != NULL)
{
char *child = g_build_filename (path, name, NULL);
remove_tree (child);
g_free (child);
}
g_dir_close (dir);
}
g_rmdir (path);
}
static void
write_file (const char *path, const char *contents)
{
gboolean ok = g_file_set_contents (path, contents, -1, NULL);
g_assert_true (ok);
}
static void
ensure_css_dir (const char *theme_root, const char *css_dir)
{
char *dir = g_build_filename (theme_root, css_dir, NULL);
char *css = g_build_filename (dir, "gtk.css", NULL);
int rc = g_mkdir_with_parents (dir, 0700);
g_assert_cmpint (rc, ==, 0);
write_file (css, "* { }\n");
g_free (css);
g_free (dir);
}
static void
write_settings (const char *theme_root, const char *css_dir, const char *settings)
{
char *path = g_build_filename (theme_root, css_dir, "settings.ini", NULL);
write_file (path, settings);
g_free (path);
}
static ZoitechatGtk3Theme *
make_theme (const char *id, const char *path)
{
ZoitechatGtk3Theme *theme = g_new0 (ZoitechatGtk3Theme, 1);
theme->id = g_strdup (id);
theme->display_name = g_strdup (id);
theme->path = g_strdup (path);
theme->source = ZOITECHAT_GTK3_THEME_SOURCE_USER;
return theme;
}
void
zoitechat_gtk3_theme_free (ZoitechatGtk3Theme *theme)
{
if (!theme)
return;
g_free (theme->id);
g_free (theme->display_name);
g_free (theme->path);
g_free (theme->thumbnail_path);
g_free (theme);
}
ZoitechatGtk3Theme *
zoitechat_gtk3_theme_find_by_id (const char *theme_id)
{
if (g_strcmp0 (theme_id, "layered") == 0)
return make_theme (theme_id, theme_child_root);
if (g_strcmp0 (theme_id, "switch") == 0)
return make_theme (theme_id, theme_switch_root);
return NULL;
}
char *
zoitechat_gtk3_theme_pick_css_dir_for_minor (const char *theme_root, int preferred_minor)
{
char *path;
(void) preferred_minor;
path = g_build_filename (theme_root, "gtk-3.24", "gtk.css", NULL);
if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
{
g_free (path);
return g_strdup ("gtk-3.24");
}
g_free (path);
path = g_build_filename (theme_root, "gtk-3.0", "gtk.css", NULL);
if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
{
g_free (path);
return g_strdup ("gtk-3.0");
}
g_free (path);
return NULL;
}
char *
zoitechat_gtk3_theme_pick_css_dir (const char *theme_root)
{
return zoitechat_gtk3_theme_pick_css_dir_for_minor (theme_root, -1);
}
GPtrArray *
zoitechat_gtk3_theme_build_inheritance_chain (const char *theme_root)
{
GPtrArray *chain = g_ptr_array_new_with_free_func (g_free);
if (g_strcmp0 (theme_root, theme_child_root) == 0)
{
g_ptr_array_add (chain, g_strdup (theme_parent_root));
g_ptr_array_add (chain, g_strdup (theme_child_root));
return chain;
}
if (g_strcmp0 (theme_root, theme_switch_root) == 0)
{
g_ptr_array_add (chain, g_strdup (theme_switch_root));
return chain;
}
g_ptr_array_unref (chain);
return NULL;
}
static gboolean
get_bool_setting (const char *name)
{
GtkSettings *settings = gtk_settings_get_default ();
gboolean value = FALSE;
g_object_get (settings, name, &value, NULL);
return value;
}
static gint
get_int_setting (const char *name)
{
GtkSettings *settings = gtk_settings_get_default ();
gint value = 0;
g_object_get (settings, name, &value, NULL);
return value;
}
static void
setup_themes (void)
{
char *path;
temp_root = g_dir_make_tmp ("zoitechat-theme-gtk3-settings-XXXXXX", NULL);
g_assert_nonnull (temp_root);
theme_parent_root = g_build_filename (temp_root, "parent", NULL);
theme_child_root = g_build_filename (temp_root, "child", NULL);
theme_switch_root = g_build_filename (temp_root, "switch", NULL);
g_assert_cmpint (g_mkdir_with_parents (theme_parent_root, 0700), ==, 0);
g_assert_cmpint (g_mkdir_with_parents (theme_child_root, 0700), ==, 0);
g_assert_cmpint (g_mkdir_with_parents (theme_switch_root, 0700), ==, 0);
ensure_css_dir (theme_parent_root, "gtk-3.24");
write_settings (theme_parent_root, "gtk-3.24",
"[Settings]\n"
"gtk-enable-animations=true\n"
"gtk-cursor-blink-time=111\n");
ensure_css_dir (theme_child_root, "gtk-3.0");
ensure_css_dir (theme_child_root, "gtk-3.24");
write_settings (theme_child_root, "gtk-3.0",
"[Settings]\n"
"gtk-enable-animations=false\n"
"gtk-cursor-blink-time=222\n");
write_settings (theme_child_root, "gtk-3.24",
"[Settings]\n"
"gtk-cursor-blink-time=333\n");
ensure_css_dir (theme_switch_root, "gtk-3.24");
write_settings (theme_switch_root, "gtk-3.24",
"[Settings]\n"
"gtk-enable-animations=false\n"
"gtk-cursor-blink-time=444\n");
path = g_build_filename (theme_parent_root, "index.theme", NULL);
write_file (path, "[Desktop Entry]\nName=parent\n");
g_free (path);
path = g_build_filename (theme_child_root, "index.theme", NULL);
write_file (path, "[Desktop Entry]\nName=child\nInherits=parent\n");
g_free (path);
path = g_build_filename (theme_switch_root, "index.theme", NULL);
write_file (path, "[Desktop Entry]\nName=switch\n");
g_free (path);
}
static void
teardown_themes (void)
{
g_assert_nonnull (temp_root);
remove_tree (temp_root);
g_free (theme_parent_root);
g_free (theme_child_root);
g_free (theme_switch_root);
g_free (temp_root);
theme_parent_root = NULL;
theme_child_root = NULL;
theme_switch_root = NULL;
temp_root = NULL;
}
static void
test_settings_layer_precedence (void)
{
GError *error = NULL;
if (!gtk_available)
{
g_test_skip ("GTK display not available");
return;
}
g_assert_true (theme_gtk3_apply ("layered", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
g_assert_no_error (error);
g_assert_false (get_bool_setting ("gtk-enable-animations"));
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, 333);
g_assert_true (theme_gtk3_is_active ());
theme_gtk3_disable ();
}
static void
test_settings_restored_on_disable_and_switch (void)
{
GError *error = NULL;
gboolean default_animations;
gint default_blink;
char *default_theme_name = NULL;
char *active_theme_name = NULL;
if (!gtk_available)
{
g_test_skip ("GTK display not available");
return;
}
default_animations = get_bool_setting ("gtk-enable-animations");
default_blink = get_int_setting ("gtk-cursor-blink-time");
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &default_theme_name, NULL);
g_assert_true (theme_gtk3_apply ("layered", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
g_assert_no_error (error);
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, 333);
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
g_assert_cmpstr (active_theme_name, ==, "child");
g_free (active_theme_name);
active_theme_name = NULL;
g_assert_true (theme_gtk3_apply ("switch", THEME_GTK3_VARIANT_PREFER_LIGHT, &error));
g_assert_no_error (error);
g_assert_false (get_bool_setting ("gtk-enable-animations"));
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, 444);
theme_gtk3_disable ();
g_assert_cmpint (get_int_setting ("gtk-cursor-blink-time"), ==, default_blink);
g_assert_cmpint (get_bool_setting ("gtk-enable-animations"), ==, default_animations);
g_object_get (gtk_settings_get_default (), "gtk-theme-name", &active_theme_name, NULL);
g_assert_cmpstr (active_theme_name, ==, default_theme_name);
g_free (active_theme_name);
g_free (default_theme_name);
g_assert_false (theme_gtk3_is_active ());
}
int
main (int argc, char **argv)
{
int rc;
g_test_init (&argc, &argv, NULL);
gtk_available = gtk_init_check (&argc, &argv);
setup_themes ();
g_test_add_func ("/theme/gtk3/settings_layer_precedence", test_settings_layer_precedence);
g_test_add_func ("/theme/gtk3/settings_restored_on_disable_and_switch", test_settings_restored_on_disable_and_switch);
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_LIGHT;
if (!gtk_available)
g_test_message ("Skipping GTK3 settings tests because GTK initialization failed");
rc = g_test_run ();
theme_gtk3_disable ();
teardown_themes ();
return rc;
}

View File

@@ -0,0 +1,71 @@
#include <glib.h>
#include "../theme-gtk3.h"
static int apply_current_calls;
void
test_theme_gtk3_stub_reset (void)
{
apply_current_calls = 0;
}
int
test_theme_gtk3_stub_apply_current_calls (void)
{
return apply_current_calls;
}
void
theme_gtk3_init (void)
{
}
gboolean
theme_gtk3_apply_current (GError **error)
{
(void) error;
apply_current_calls++;
return TRUE;
}
gboolean
theme_gtk3_apply (const char *theme_id, ThemeGtk3Variant variant, GError **error)
{
(void) theme_id;
(void) variant;
(void) error;
return TRUE;
}
gboolean
theme_gtk3_refresh (const char *theme_id, ThemeGtk3Variant variant, GError **error)
{
(void) theme_id;
(void) variant;
(void) error;
return TRUE;
}
ThemeGtk3Variant
theme_gtk3_variant_for_theme (const char *theme_id)
{
(void) theme_id;
return THEME_GTK3_VARIANT_PREFER_LIGHT;
}
void
theme_gtk3_invalidate_provider_cache (void)
{
}
void
theme_gtk3_disable (void)
{
}
gboolean
theme_gtk3_is_active (void)
{
return FALSE;
}

View File

@@ -1,6 +1,7 @@
#include <gtk/gtk.h>
#include "../theme-manager.h"
#include "../theme-gtk3.h"
#include "../../fe-gtk.h"
#include "../../../common/zoitechat.h"
#include "../../../common/zoitechatc.h"
@@ -19,6 +20,9 @@ static ThemeChangedEvent last_event;
static int idle_add_calls;
static guint next_idle_source_id = 33;
void test_theme_gtk3_stub_reset (void);
int test_theme_gtk3_stub_apply_current_calls (void);
void setup_apply_real (const ThemeChangedEvent *event)
{
(void) event;
@@ -85,6 +89,11 @@ void theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *colo
(void) color;
}
void theme_runtime_reset_mode_colors (gboolean dark_mode)
{
(void) dark_mode;
}
gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *dark_active)
{
(void) mode;
@@ -135,6 +144,12 @@ void theme_get_widget_style_values (ThemeWidgetStyleValues *out_values)
gdk_rgba_parse (&out_values->foreground, "#f0f0f0");
}
void theme_get_widget_style_values_for_widget (GtkWidget *widget, ThemeWidgetStyleValues *out_values)
{
(void) widget;
theme_get_widget_style_values (out_values);
}
void fe_win32_apply_native_titlebar (GtkWidget *window, gboolean dark)
{
(void) window;
@@ -167,6 +182,8 @@ reset_state (void)
listener_calls = 0;
idle_add_calls = 0;
next_idle_source_id = 33;
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
test_theme_gtk3_stub_reset ();
}
static void
@@ -187,6 +204,7 @@ test_auto_refresh_dispatches_mode_palette_and_style_reasons (void)
g_assert_cmpint (auto_state_calls, ==, 2);
g_assert_true (last_auto_state);
g_assert_cmpint (listener_calls, ==, 1);
g_assert_cmpint (test_theme_gtk3_stub_apply_current_calls (), ==, 1);
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_PALETTE));
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_WIDGET_STYLE));
g_assert_true (theme_changed_event_has_reason (&last_event, THEME_CHANGED_REASON_USERLIST));
@@ -212,6 +230,29 @@ test_auto_refresh_ignores_non_auto_mode (void)
g_assert_cmpint (idle_add_calls, ==, 1);
g_assert_cmpint (auto_state_calls, ==, 0);
g_assert_cmpint (listener_calls, ==, 0);
g_assert_cmpint (test_theme_gtk3_stub_apply_current_calls (), ==, 0);
theme_manager_set_idle_add_func (NULL);
theme_listener_unregister (listener_id);
}
static void
test_auto_refresh_reapplies_gtk3_for_follow_system_variant (void)
{
guint listener_id;
reset_state ();
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_FOLLOW_SYSTEM;
listener_id = theme_listener_register ("auto.gtk3", auto_listener, NULL);
theme_manager_set_idle_add_func (immediate_idle_add);
theme_manager_refresh_auto_mode ();
g_assert_cmpint (idle_add_calls, ==, 1);
g_assert_cmpint (auto_state_calls, ==, 0);
g_assert_cmpint (listener_calls, ==, 0);
g_assert_cmpint (test_theme_gtk3_stub_apply_current_calls (), ==, 1);
theme_manager_set_idle_add_func (NULL);
theme_listener_unregister (listener_id);
@@ -225,5 +266,7 @@ main (int argc, char **argv)
test_auto_refresh_dispatches_mode_palette_and_style_reasons);
g_test_add_func ("/theme/manager/auto_refresh_ignores_non_auto_mode",
test_auto_refresh_ignores_non_auto_mode);
g_test_add_func ("/theme/manager/auto_refresh_reapplies_gtk3_for_follow_system_variant",
test_auto_refresh_reapplies_gtk3_for_follow_system_variant);
return g_test_run ();
}

View File

@@ -81,6 +81,11 @@ void theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *colo
(void) color;
}
void theme_runtime_reset_mode_colors (gboolean dark_mode)
{
(void) dark_mode;
}
gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *dark_active)
{
(void) mode;
@@ -131,6 +136,12 @@ void theme_get_widget_style_values (ThemeWidgetStyleValues *out_values)
gdk_rgba_parse (&out_values->foreground, "#f0f0f0");
}
void theme_get_widget_style_values_for_widget (GtkWidget *widget, ThemeWidgetStyleValues *out_values)
{
(void) widget;
theme_get_widget_style_values (out_values);
}
void fe_win32_apply_native_titlebar (GtkWidget *window, gboolean dark)
{
(void) window;

View File

@@ -85,6 +85,11 @@ void theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *colo
stub_last_user_token = token;
}
void theme_runtime_reset_mode_colors (gboolean dark_mode)
{
(void) dark_mode;
}
gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *dark_active)
{
(void) mode;
@@ -147,6 +152,12 @@ void theme_get_widget_style_values (ThemeWidgetStyleValues *out_values)
gdk_rgba_parse (&out_values->foreground, "#f0f0f0");
}
void theme_get_widget_style_values_for_widget (GtkWidget *widget, ThemeWidgetStyleValues *out_values)
{
(void) widget;
theme_get_widget_style_values (out_values);
}
void fe_win32_apply_native_titlebar (GtkWidget *window, gboolean dark)
{
(void) window;

View File

@@ -0,0 +1,280 @@
#include <gtk/gtk.h>
#include "../../../common/zoitechat.h"
#include "../../../common/zoitechatc.h"
#include "../../../common/gtk3-theme-service.h"
#include "../../fe-gtk.h"
#include "../theme-gtk3.h"
#include "../theme-manager.h"
struct session *current_sess;
struct session *current_tab;
struct zoitechatprefs prefs;
InputStyle *input_style;
static gboolean gtk_available;
static int apply_current_calls;
static char applied_theme_id[256];
static ThemeGtk3Variant applied_variant;
static gboolean removed_selected;
GtkWidget *
gtkutil_box_new (GtkOrientation orientation, gboolean homogeneous, gint spacing)
{
(void)homogeneous;
return gtk_box_new (orientation, spacing);
}
void
gtkutil_apply_palette (GtkWidget *wid, const GdkRGBA *fg, const GdkRGBA *bg, const PangoFontDescription *font)
{
(void)wid;
(void)fg;
(void)bg;
(void)font;
}
void
fe_open_url (const char *url)
{
(void)url;
}
gboolean
theme_get_color (ThemeSemanticToken token, GdkRGBA *color)
{
(void)token;
if (color)
gdk_rgba_parse (color, "#000000");
return TRUE;
}
void
theme_manager_set_token_color (unsigned int dark_mode, ThemeSemanticToken token, const GdkRGBA *color, gboolean *changed)
{
(void)dark_mode;
(void)token;
(void)color;
if (changed)
*changed = FALSE;
}
void
theme_manager_reset_mode_colors (unsigned int mode, gboolean *palette_changed)
{
(void)mode;
if (palette_changed)
*palette_changed = FALSE;
}
void
theme_manager_save_preferences (void)
{
}
ThemePaletteBehavior
theme_manager_get_userlist_palette_behavior (const PangoFontDescription *font_desc)
{
ThemePaletteBehavior behavior;
behavior.font_desc = font_desc;
behavior.apply_background = FALSE;
behavior.apply_foreground = FALSE;
return behavior;
}
void
theme_manager_apply_userlist_style (GtkWidget *widget, ThemePaletteBehavior behavior)
{
(void)widget;
(void)behavior;
}
void
theme_manager_attach_window (GtkWidget *window)
{
(void)window;
}
char *
zoitechat_gtk3_theme_service_get_user_themes_dir (void)
{
return g_strdup ("/tmp");
}
static ZoitechatGtk3Theme *
new_theme (const char *id, const char *name, ZoitechatGtk3ThemeSource source)
{
ZoitechatGtk3Theme *theme = g_new0 (ZoitechatGtk3Theme, 1);
theme->id = g_strdup (id);
theme->display_name = g_strdup (name);
theme->source = source;
return theme;
}
void
zoitechat_gtk3_theme_free (ZoitechatGtk3Theme *theme)
{
if (!theme)
return;
g_free (theme->id);
g_free (theme->display_name);
g_free (theme->path);
g_free (theme->thumbnail_path);
g_free (theme);
}
GPtrArray *
zoitechat_gtk3_theme_service_discover (void)
{
GPtrArray *themes = g_ptr_array_new_with_free_func ((GDestroyNotify)zoitechat_gtk3_theme_free);
if (!removed_selected)
g_ptr_array_add (themes, new_theme ("removed-theme", "Removed Theme", ZOITECHAT_GTK3_THEME_SOURCE_USER));
g_ptr_array_add (themes, new_theme ("fallback-theme", "Fallback Theme", ZOITECHAT_GTK3_THEME_SOURCE_SYSTEM));
return themes;
}
ZoitechatGtk3Theme *
zoitechat_gtk3_theme_find_by_id (const char *theme_id)
{
(void)theme_id;
return NULL;
}
gboolean
zoitechat_gtk3_theme_service_import (const char *source_path, char **imported_id, GError **error)
{
(void)source_path;
(void)imported_id;
(void)error;
return FALSE;
}
gboolean
zoitechat_gtk3_theme_service_remove_user_theme (const char *theme_id, GError **error)
{
(void)error;
if (g_strcmp0 (theme_id, "removed-theme") == 0)
{
removed_selected = TRUE;
return TRUE;
}
return FALSE;
}
char *
zoitechat_gtk3_theme_pick_css_dir_for_minor (const char *theme_root, int preferred_minor)
{
(void)theme_root;
(void)preferred_minor;
return NULL;
}
char *
zoitechat_gtk3_theme_pick_css_dir (const char *theme_root)
{
(void)theme_root;
return NULL;
}
GPtrArray *
zoitechat_gtk3_theme_build_inheritance_chain (const char *theme_root)
{
(void)theme_root;
return NULL;
}
gboolean
theme_gtk3_apply_current (GError **error)
{
(void)error;
apply_current_calls++;
g_strlcpy (applied_theme_id, prefs.hex_gui_gtk3_theme, sizeof (applied_theme_id));
applied_variant = (ThemeGtk3Variant)prefs.hex_gui_gtk3_variant;
return TRUE;
}
void
theme_gtk3_init (void)
{
}
gboolean
theme_gtk3_apply (const char *theme_id, ThemeGtk3Variant variant, GError **error)
{
(void)theme_id;
(void)variant;
(void)error;
return TRUE;
}
ThemeGtk3Variant
theme_gtk3_variant_for_theme (const char *theme_id)
{
if (g_str_has_suffix (theme_id, "dark"))
return THEME_GTK3_VARIANT_PREFER_DARK;
return THEME_GTK3_VARIANT_PREFER_LIGHT;
}
void
theme_gtk3_disable (void)
{
}
gboolean
theme_gtk3_is_active (void)
{
return FALSE;
}
#include "../theme-preferences.c"
static void
test_removed_selected_theme_commits_fallback_and_applies (void)
{
GtkWidget *page;
theme_preferences_ui *ui;
struct zoitechatprefs setup_prefs;
if (!gtk_available)
{
g_test_skip ("GTK display not available");
return;
}
memset (&setup_prefs, 0, sizeof (setup_prefs));
memset (&prefs, 0, sizeof (prefs));
g_strlcpy (prefs.hex_gui_gtk3_theme, "removed-theme", sizeof (prefs.hex_gui_gtk3_theme));
prefs.hex_gui_gtk3_variant = THEME_GTK3_VARIANT_PREFER_DARK;
removed_selected = FALSE;
apply_current_calls = 0;
applied_theme_id[0] = '\0';
page = theme_preferences_create_page (NULL, &setup_prefs, NULL);
ui = g_object_get_data (G_OBJECT (page), "theme-preferences-ui");
g_assert_nonnull (ui);
g_assert_nonnull (ui->gtk3_remove);
gtk_button_clicked (GTK_BUTTON (ui->gtk3_remove));
g_assert_cmpstr (prefs.hex_gui_gtk3_theme, ==, "fallback-theme");
g_assert_cmpstr (setup_prefs.hex_gui_gtk3_theme, ==, "fallback-theme");
g_assert_cmpint (prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT);
g_assert_cmpint (setup_prefs.hex_gui_gtk3_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT);
g_assert_cmpint (apply_current_calls, ==, 1);
g_assert_cmpstr (applied_theme_id, ==, "fallback-theme");
g_assert_cmpint (applied_variant, ==, THEME_GTK3_VARIANT_PREFER_LIGHT);
gtk_widget_destroy (page);
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
gtk_available = gtk_init_check (&argc, &argv);
g_test_add_func ("/theme/preferences/gtk3_removed_selection_applies_fallback",
test_removed_selected_theme_commits_fallback_and_applies);
return g_test_run ();
}

View File

@@ -1,4 +1,5 @@
#include <errno.h>
#include <math.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
@@ -267,6 +268,91 @@ test_ui_edits_persist_without_legacy_array_mutation (void)
g_assert_true (colors_equal (&dark_loaded, &dark_expected));
}
static void
test_gtk_map_colors_blend_with_palette_without_transparency (void)
{
ThemeGtkPaletteMap map = { 0 };
ThemeWidgetStyleValues base_values;
ThemeWidgetStyleValues values;
GdkRGBA mapped_bg;
double alpha;
double expected_red;
double expected_green;
double expected_blue;
setup_temp_home ();
theme_runtime_load ();
theme_runtime_get_widget_style_values (&base_values);
map.enabled = TRUE;
g_assert_true (gdk_rgba_parse (&map.text_foreground, "rgba(10, 20, 30, 0.25)"));
g_assert_true (gdk_rgba_parse (&map.text_background, "rgba(40, 50, 60, 0.30)"));
g_assert_true (gdk_rgba_parse (&map.selection_foreground, "rgba(70, 80, 90, 0.35)"));
g_assert_true (gdk_rgba_parse (&map.selection_background, "rgba(100, 110, 120, 0.40)"));
g_assert_true (gdk_rgba_parse (&map.accent, "rgba(130, 140, 150, 0.45)"));
theme_runtime_get_widget_style_values_mapped (&map, &values);
g_assert_cmpfloat (values.foreground.alpha, ==, 1.0);
g_assert_cmpfloat (values.background.alpha, ==, 1.0);
g_assert_cmpfloat (values.selection_foreground.alpha, ==, 1.0);
g_assert_cmpfloat (values.selection_background.alpha, ==, 1.0);
mapped_bg = map.text_background;
alpha = mapped_bg.alpha;
expected_red = (mapped_bg.red * alpha) + (base_values.background.red * (1.0 - alpha));
expected_green = (mapped_bg.green * alpha) + (base_values.background.green * (1.0 - alpha));
expected_blue = (mapped_bg.blue * alpha) + (base_values.background.blue * (1.0 - alpha));
g_assert_true (fabs (values.background.red - expected_red) < 0.0001);
g_assert_true (fabs (values.background.green - expected_green) < 0.0001);
g_assert_true (fabs (values.background.blue - expected_blue) < 0.0001);
}
static void
test_gtk_map_uses_theme_defaults_until_custom_token_is_set (void)
{
ThemeGtkPaletteMap map = { 0 };
ThemeWidgetStyleValues values;
GdkRGBA custom;
setup_temp_home ();
theme_runtime_load ();
map.enabled = TRUE;
g_assert_true (gdk_rgba_parse (&map.text_foreground, "#010203"));
g_assert_true (gdk_rgba_parse (&map.text_background, "#111213"));
g_assert_true (gdk_rgba_parse (&map.selection_foreground, "#212223"));
g_assert_true (gdk_rgba_parse (&map.selection_background, "#313233"));
g_assert_true (gdk_rgba_parse (&map.accent, "#414243"));
theme_runtime_get_widget_style_values_mapped (&map, &values);
g_assert_true (colors_equal (&values.foreground, &map.text_foreground));
g_assert_true (gdk_rgba_parse (&custom, "#a1b2c3"));
theme_runtime_user_set_color (THEME_TOKEN_TEXT_FOREGROUND, &custom);
theme_runtime_apply_mode (ZOITECHAT_DARK_MODE_LIGHT, NULL);
theme_runtime_get_widget_style_values_mapped (&map, &values);
g_assert_true (colors_equal (&values.foreground, &custom));
}
static void
test_save_writes_only_custom_token_keys (void)
{
GdkRGBA custom;
char *cfg;
setup_temp_home ();
theme_runtime_load ();
g_assert_true (gdk_rgba_parse (&custom, "#445566"));
theme_runtime_user_set_color (THEME_TOKEN_TEXT_FOREGROUND, &custom);
theme_runtime_save ();
cfg = read_colors_conf ();
g_assert_nonnull (g_strstr_len (cfg, -1, "theme.mode.light.token.text_foreground"));
g_assert_null (g_strstr_len (cfg, -1, "theme.mode.light.token.text_background"));
g_free (cfg);
}
int
main (int argc, char **argv)
{
@@ -277,5 +363,11 @@ main (int argc, char **argv)
test_loads_legacy_color_keys_via_migration_loader);
g_test_add_func ("/theme/runtime/ui_edits_persist_without_legacy_array_mutation",
test_ui_edits_persist_without_legacy_array_mutation);
g_test_add_func ("/theme/runtime/gtk_map_colors_blend_with_palette_without_transparency",
test_gtk_map_colors_blend_with_palette_without_transparency);
g_test_add_func ("/theme/runtime/gtk_map_uses_theme_defaults_until_custom_token_is_set",
test_gtk_map_uses_theme_defaults_until_custom_token_is_set);
g_test_add_func ("/theme/runtime/save_writes_only_custom_token_keys",
test_save_writes_only_custom_token_keys);
return g_test_run ();
}