mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-03-10 16:00:18 +00:00
Compare commits
46 Commits
theme-feat
...
gtk3-themi
| Author | SHA1 | Date | |
|---|---|---|---|
| 69985913b8 | |||
| df45cc996d | |||
| 5763653672 | |||
| 2ece544792 | |||
| 8e0958fd19 | |||
| 324aeab8c9 | |||
| e8dbe06f01 | |||
| f3213c56eb | |||
| 9ef607f44a | |||
| a93bed2c41 | |||
| d10c9654fa | |||
| a823047104 | |||
| bf349a27b1 | |||
| 90ada474a0 | |||
| 4a996c9135 | |||
| c9682d98f3 | |||
| 8a4ecf8649 | |||
| 252f4a3c07 | |||
| d21a5c1b60 | |||
| a1ba30865a | |||
| 607faa80ca | |||
| 1c1110847c | |||
| a796f78884 | |||
| 4354aaa57a | |||
| 4aeb5b5697 | |||
| 30609ba6db | |||
| 6b8e41b4c6 | |||
| 0edab77fac | |||
| c8ae4f3b18 | |||
| 578a417804 | |||
| 440e9ecf5a | |||
| ac2ab1443c | |||
| ce5128e4fb | |||
| c37faa1492 | |||
| 685989fa25 | |||
| cbc474477b | |||
| 51f8795d1a | |||
| faacd95dfc | |||
| ed02b21228 | |||
| d321717da8 | |||
| 6310ab245c | |||
| 5952006662 | |||
| e4cb453915 | |||
| 361e35de7f | |||
| bbde2e5578 | |||
| 97c6f36b20 |
@@ -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},
|
||||
|
||||
@@ -3774,37 +3774,29 @@ cmd_url (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
||||
if (zoitechat_theme_path_from_arg (word[2], &theme_path))
|
||||
{
|
||||
GError *error = NULL;
|
||||
char *basename = g_path_get_basename (theme_path);
|
||||
char *dot = strrchr (basename, '.');
|
||||
char *message;
|
||||
char *theme_name = NULL;
|
||||
|
||||
if (dot)
|
||||
*dot = '\0';
|
||||
|
||||
if (zoitechat_import_theme (theme_path, &error))
|
||||
if (zoitechat_import_gtk3_theme_archive (theme_path, &theme_name, &error))
|
||||
{
|
||||
if (zoitechat_apply_theme (basename, &error))
|
||||
if (theme_name)
|
||||
{
|
||||
message = g_strdup_printf (_("Theme \"%s\" imported and applied."), basename);
|
||||
char *message = g_strdup_printf (_("GTK3 theme \"%s\" imported. Use Theme settings to apply it."), theme_name);
|
||||
fe_message (message, FE_MSG_INFO);
|
||||
handle_command (sess, "gui apply", FALSE);
|
||||
g_free (message);
|
||||
}
|
||||
else
|
||||
{
|
||||
fe_message (error ? error->message : _("Theme imported, but failed to apply."),
|
||||
FE_MSG_ERROR);
|
||||
g_clear_error (&error);
|
||||
fe_message (_("GTK3 theme imported. Use Theme settings to apply it."), FE_MSG_INFO);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fe_message (error ? error->message : _("Failed to import theme."),
|
||||
fe_message (error ? error->message : _("Failed to import GTK3 theme archive."),
|
||||
FE_MSG_ERROR);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
g_free (basename);
|
||||
g_free (theme_name);
|
||||
g_free (theme_path);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ gboolean
|
||||
zoitechat_theme_path_from_arg (const char *arg, char **path_out)
|
||||
{
|
||||
char *path = NULL;
|
||||
const char *ext;
|
||||
gboolean valid_ext = FALSE;
|
||||
|
||||
if (!arg)
|
||||
return FALSE;
|
||||
@@ -126,11 +126,21 @@ zoitechat_theme_path_from_arg (const char *arg, char **path_out)
|
||||
if (!path)
|
||||
return FALSE;
|
||||
|
||||
ext = strrchr (path, '.');
|
||||
if (!g_file_test (path, G_FILE_TEST_IS_REGULAR) ||
|
||||
!ext ||
|
||||
(g_ascii_strcasecmp (ext, ".zct") != 0 &&
|
||||
g_ascii_strcasecmp (ext, ".hct") != 0))
|
||||
if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
char *path_lower = g_ascii_strdown (path, -1);
|
||||
|
||||
valid_ext = g_str_has_suffix (path_lower, ".zip")
|
||||
|| g_str_has_suffix (path_lower, ".tar")
|
||||
|| g_str_has_suffix (path_lower, ".tar.gz")
|
||||
|| g_str_has_suffix (path_lower, ".tgz")
|
||||
|| g_str_has_suffix (path_lower, ".tar.xz")
|
||||
|| g_str_has_suffix (path_lower, ".txz");
|
||||
|
||||
g_free (path_lower);
|
||||
}
|
||||
|
||||
if (!valid_ext)
|
||||
{
|
||||
g_free (path);
|
||||
return FALSE;
|
||||
@@ -253,149 +263,444 @@ zoitechat_remote_win32 (void)
|
||||
|
||||
|
||||
static gboolean
|
||||
zoitechat_copy_theme_file (const char *src, const char *dest, GError **error)
|
||||
zoitechat_is_safe_archive_entry (const char *entry)
|
||||
{
|
||||
char *data = NULL;
|
||||
gsize len = 0;
|
||||
char **parts;
|
||||
gboolean safe = TRUE;
|
||||
guint i;
|
||||
|
||||
if (!g_file_get_contents (src, &data, &len, error))
|
||||
if (!entry || !*entry)
|
||||
return FALSE;
|
||||
|
||||
if (!g_file_set_contents (dest, data, len, error))
|
||||
{
|
||||
g_free (data);
|
||||
if (g_path_is_absolute (entry) || entry[0] == '/' || entry[0] == '\\')
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_free (data);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
zoitechat_apply_theme (const char *theme_name, GError **error)
|
||||
{
|
||||
char *theme_dir;
|
||||
char *colors_src;
|
||||
char *colors_dest;
|
||||
char *events_src;
|
||||
char *events_dest;
|
||||
gboolean ok = FALSE;
|
||||
|
||||
if (!theme_name || !*theme_name)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||
_("No theme name specified."));
|
||||
if (g_ascii_isalpha (entry[0]) && entry[1] == ':')
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
theme_dir = g_build_filename (get_xdir (), "themes", theme_name, NULL);
|
||||
colors_src = g_build_filename (theme_dir, "colors.conf", NULL);
|
||||
colors_dest = g_build_filename (get_xdir (), "colors.conf", NULL);
|
||||
events_src = g_build_filename (theme_dir, "pevents.conf", NULL);
|
||||
events_dest = g_build_filename (get_xdir (), "pevents.conf", NULL);
|
||||
|
||||
if (!g_file_test (colors_src, G_FILE_TEST_IS_REGULAR))
|
||||
parts = g_strsplit_set (entry, "/\\", -1);
|
||||
for (i = 0; parts[i] != NULL; i++)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||||
_("This theme is missing a colors.conf file."));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!zoitechat_copy_theme_file (colors_src, colors_dest, error))
|
||||
goto cleanup;
|
||||
|
||||
if (g_file_test (events_src, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
if (!zoitechat_copy_theme_file (events_src, events_dest, error))
|
||||
goto cleanup;
|
||||
}
|
||||
else if (g_file_test (events_dest, G_FILE_TEST_EXISTS))
|
||||
{
|
||||
if (g_unlink (events_dest) != 0)
|
||||
if (strcmp (parts[i], "..") == 0)
|
||||
{
|
||||
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
|
||||
_("Failed to remove existing event settings."));
|
||||
goto cleanup;
|
||||
safe = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ok = TRUE;
|
||||
g_strfreev (parts);
|
||||
return safe;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
zoitechat_remove_tree (const char *path, GError **error)
|
||||
{
|
||||
GDir *dir;
|
||||
const char *name;
|
||||
|
||||
if (!g_file_test (path, G_FILE_TEST_EXISTS))
|
||||
return TRUE;
|
||||
|
||||
if (!g_file_test (path, G_FILE_TEST_IS_DIR))
|
||||
return g_remove (path) == 0;
|
||||
|
||||
dir = g_dir_open (path, 0, error);
|
||||
if (!dir)
|
||||
return FALSE;
|
||||
|
||||
while ((name = g_dir_read_name (dir)))
|
||||
{
|
||||
char *child = g_build_filename (path, name, NULL);
|
||||
|
||||
if (!zoitechat_remove_tree (child, error))
|
||||
{
|
||||
g_free (child);
|
||||
g_dir_close (dir);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_free (child);
|
||||
}
|
||||
|
||||
g_dir_close (dir);
|
||||
if (g_rmdir (path) != 0)
|
||||
{
|
||||
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
|
||||
_("Failed to remove temporary directory."));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
zoitechat_copy_directory_recursive (const char *src_dir, const char *dest_dir, GError **error)
|
||||
{
|
||||
GDir *dir;
|
||||
const char *name;
|
||||
|
||||
if (g_mkdir_with_parents (dest_dir, 0700) != 0)
|
||||
{
|
||||
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
|
||||
_("Failed to create destination theme directory."));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
dir = g_dir_open (src_dir, 0, error);
|
||||
if (!dir)
|
||||
return FALSE;
|
||||
|
||||
while ((name = g_dir_read_name (dir)))
|
||||
{
|
||||
char *src_path = g_build_filename (src_dir, name, NULL);
|
||||
char *dest_path = g_build_filename (dest_dir, name, NULL);
|
||||
|
||||
if (g_file_test (src_path, G_FILE_TEST_IS_DIR))
|
||||
{
|
||||
if (!zoitechat_copy_directory_recursive (src_path, dest_path, error))
|
||||
{
|
||||
g_free (dest_path);
|
||||
g_free (src_path);
|
||||
g_dir_close (dir);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else if (g_file_test (src_path, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
GFile *src_file = g_file_new_for_path (src_path);
|
||||
GFile *dest_file = g_file_new_for_path (dest_path);
|
||||
gboolean copied = g_file_copy (src_file, dest_file,
|
||||
G_FILE_COPY_OVERWRITE,
|
||||
NULL, NULL, NULL, error);
|
||||
|
||||
g_object_unref (dest_file);
|
||||
g_object_unref (src_file);
|
||||
|
||||
if (!copied)
|
||||
{
|
||||
g_free (dest_path);
|
||||
g_free (src_path);
|
||||
g_dir_close (dir);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
g_free (dest_path);
|
||||
g_free (src_path);
|
||||
}
|
||||
|
||||
g_dir_close (dir);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
zoitechat_find_gtk3_theme_root (const char *search_dir,
|
||||
char **theme_root_out,
|
||||
gboolean *has_dark_css_out,
|
||||
gboolean *missing_gtk_css_out,
|
||||
GError **error)
|
||||
{
|
||||
GDir *dir;
|
||||
const char *name;
|
||||
|
||||
dir = g_dir_open (search_dir, 0, error);
|
||||
if (!dir)
|
||||
return FALSE;
|
||||
|
||||
while ((name = g_dir_read_name (dir)))
|
||||
{
|
||||
char *child = g_build_filename (search_dir, name, NULL);
|
||||
|
||||
if (g_file_test (child, G_FILE_TEST_IS_DIR))
|
||||
{
|
||||
if (g_str_has_prefix (name, "gtk-3."))
|
||||
{
|
||||
char *gtk_css = g_build_filename (child, "gtk.css", NULL);
|
||||
|
||||
if (g_file_test (gtk_css, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
char *dark_css = g_build_filename (child, "gtk-dark.css", NULL);
|
||||
|
||||
if (theme_root_out)
|
||||
*theme_root_out = g_strdup (search_dir);
|
||||
if (has_dark_css_out)
|
||||
*has_dark_css_out = g_file_test (dark_css, G_FILE_TEST_IS_REGULAR);
|
||||
|
||||
g_free (dark_css);
|
||||
g_free (gtk_css);
|
||||
g_free (child);
|
||||
g_dir_close (dir);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_free (gtk_css);
|
||||
if (missing_gtk_css_out)
|
||||
*missing_gtk_css_out = TRUE;
|
||||
}
|
||||
else if (zoitechat_find_gtk3_theme_root (child,
|
||||
theme_root_out,
|
||||
has_dark_css_out,
|
||||
missing_gtk_css_out,
|
||||
error))
|
||||
{
|
||||
g_free (child);
|
||||
g_dir_close (dir);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (error && *error)
|
||||
{
|
||||
g_free (child);
|
||||
g_dir_close (dir);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
g_free (child);
|
||||
}
|
||||
|
||||
g_dir_close (dir);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ZOITECHAT_GTK3_ARCHIVE_UNKNOWN = 0,
|
||||
ZOITECHAT_GTK3_ARCHIVE_ZIP,
|
||||
ZOITECHAT_GTK3_ARCHIVE_TAR
|
||||
} ZoiteChatGtk3ArchiveType;
|
||||
|
||||
static ZoiteChatGtk3ArchiveType
|
||||
zoitechat_detect_gtk3_archive_type (const char *archive_path)
|
||||
{
|
||||
char *archive_path_lower;
|
||||
ZoiteChatGtk3ArchiveType type = ZOITECHAT_GTK3_ARCHIVE_UNKNOWN;
|
||||
|
||||
if (!archive_path)
|
||||
return ZOITECHAT_GTK3_ARCHIVE_UNKNOWN;
|
||||
|
||||
archive_path_lower = g_ascii_strdown (archive_path, -1);
|
||||
|
||||
if (g_str_has_suffix (archive_path_lower, ".zip"))
|
||||
type = ZOITECHAT_GTK3_ARCHIVE_ZIP;
|
||||
else if (g_str_has_suffix (archive_path_lower, ".tar")
|
||||
|| g_str_has_suffix (archive_path_lower, ".tar.gz")
|
||||
|| g_str_has_suffix (archive_path_lower, ".tgz")
|
||||
|| g_str_has_suffix (archive_path_lower, ".tar.xz")
|
||||
|| g_str_has_suffix (archive_path_lower, ".txz"))
|
||||
type = ZOITECHAT_GTK3_ARCHIVE_TAR;
|
||||
|
||||
g_free (archive_path_lower);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
#ifndef WIN32
|
||||
static gboolean
|
||||
zoitechat_validate_zip_entries_unix (const char *archive_path, char **archive_root_out, GError **error)
|
||||
{
|
||||
char *stdout_buf = NULL;
|
||||
char *stderr_buf = NULL;
|
||||
char *argv[] = {"unzip", "-Z1", (char *)archive_path, NULL};
|
||||
char *archive_root = NULL;
|
||||
char **lines;
|
||||
gboolean ok;
|
||||
int status = 0;
|
||||
guint i;
|
||||
|
||||
ok = g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
|
||||
NULL, NULL, &stdout_buf, &stderr_buf, &status, error);
|
||||
if (!ok)
|
||||
goto cleanup;
|
||||
|
||||
if (!g_spawn_check_exit_status (status, error))
|
||||
{
|
||||
ok = FALSE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
lines = g_strsplit (stdout_buf ? stdout_buf : "", "\n", -1);
|
||||
for (i = 0; lines[i] != NULL; i++)
|
||||
{
|
||||
const char *entry = lines[i];
|
||||
char **parts;
|
||||
|
||||
if (!entry[0])
|
||||
continue;
|
||||
|
||||
if (!zoitechat_is_safe_archive_entry (entry))
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||||
_("Archive contains unsafe path: %s"), entry);
|
||||
ok = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
parts = g_strsplit (entry, "/", 2);
|
||||
if (parts[0] && *parts[0])
|
||||
{
|
||||
if (!archive_root)
|
||||
archive_root = g_strdup (parts[0]);
|
||||
else if (strcmp (archive_root, parts[0]) != 0)
|
||||
g_clear_pointer (&archive_root, g_free);
|
||||
}
|
||||
g_strfreev (parts);
|
||||
}
|
||||
g_strfreev (lines);
|
||||
|
||||
if (ok && archive_root_out)
|
||||
*archive_root_out = g_strdup (archive_root);
|
||||
|
||||
cleanup:
|
||||
g_free (events_dest);
|
||||
g_free (events_src);
|
||||
g_free (colors_dest);
|
||||
g_free (colors_src);
|
||||
g_free (theme_dir);
|
||||
g_free (archive_root);
|
||||
g_free (stderr_buf);
|
||||
g_free (stdout_buf);
|
||||
return ok;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
zoitechat_validate_tar_entries_unix (const char *archive_path, char **archive_root_out, GError **error)
|
||||
{
|
||||
char *stdout_buf = NULL;
|
||||
char *stderr_buf = NULL;
|
||||
char *argv[] = {"tar", "-tf", (char *)archive_path, NULL};
|
||||
char *archive_root = NULL;
|
||||
char **lines;
|
||||
gboolean ok;
|
||||
int status = 0;
|
||||
guint i;
|
||||
|
||||
ok = g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
|
||||
NULL, NULL, &stdout_buf, &stderr_buf, &status, error);
|
||||
if (!ok)
|
||||
goto cleanup;
|
||||
|
||||
if (!g_spawn_check_exit_status (status, error))
|
||||
{
|
||||
ok = FALSE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
lines = g_strsplit (stdout_buf ? stdout_buf : "", "\n", -1);
|
||||
for (i = 0; lines[i] != NULL; i++)
|
||||
{
|
||||
const char *entry = lines[i];
|
||||
char **parts;
|
||||
|
||||
if (!entry[0])
|
||||
continue;
|
||||
|
||||
if (!zoitechat_is_safe_archive_entry (entry))
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||||
_("Archive contains unsafe path: %s"), entry);
|
||||
ok = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
parts = g_strsplit_set (entry, "/\\", 2);
|
||||
if (parts[0] && *parts[0])
|
||||
{
|
||||
if (!archive_root)
|
||||
archive_root = g_strdup (parts[0]);
|
||||
else if (strcmp (archive_root, parts[0]) != 0)
|
||||
g_clear_pointer (&archive_root, g_free);
|
||||
}
|
||||
g_strfreev (parts);
|
||||
}
|
||||
g_strfreev (lines);
|
||||
|
||||
if (ok && archive_root_out)
|
||||
*archive_root_out = g_strdup (archive_root);
|
||||
|
||||
cleanup:
|
||||
g_free (archive_root);
|
||||
g_free (stderr_buf);
|
||||
g_free (stdout_buf);
|
||||
return ok;
|
||||
}
|
||||
|
||||
gboolean
|
||||
zoitechat_import_theme (const char *path, GError **error)
|
||||
zoitechat_import_gtk3_theme_archive (const char *archive_path,
|
||||
char **theme_name_out,
|
||||
GError **error)
|
||||
{
|
||||
char *themes_dir;
|
||||
char *basename;
|
||||
char *dot;
|
||||
char *theme_dir;
|
||||
char *argv[] = {"unzip", "-o", (char *)path, "-d", NULL, NULL};
|
||||
ZoiteChatGtk3ArchiveType archive_type;
|
||||
char *temp_dir = NULL;
|
||||
char *archive_root = NULL;
|
||||
char *theme_root = NULL;
|
||||
char *theme_name = NULL;
|
||||
char *store_dir = NULL;
|
||||
char *dest_dir = NULL;
|
||||
int status = 0;
|
||||
gboolean ok;
|
||||
#ifdef WIN32
|
||||
char *command = NULL;
|
||||
char *powershell = NULL;
|
||||
#endif
|
||||
gboolean ok = FALSE;
|
||||
gboolean has_dark_css = FALSE;
|
||||
gboolean missing_gtk_css = FALSE;
|
||||
|
||||
if (!path)
|
||||
if (theme_name_out)
|
||||
*theme_name_out = NULL;
|
||||
|
||||
if (!archive_path)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||
_("No theme file specified."));
|
||||
_("No GTK3 theme archive specified."));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
themes_dir = g_build_filename (get_xdir (), "themes", NULL);
|
||||
basename = g_path_get_basename (path);
|
||||
if (!basename || basename[0] == '\0')
|
||||
archive_type = zoitechat_detect_gtk3_archive_type (archive_path);
|
||||
if (archive_type == ZOITECHAT_GTK3_ARCHIVE_UNKNOWN)
|
||||
{
|
||||
g_free (themes_dir);
|
||||
g_free (basename);
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||
_("Failed to determine theme name."));
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
||||
_("Unsupported archive format. Use .zip, .tar, .tar.gz, .tgz, .tar.xz, or .txz."));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
dot = strrchr (basename, '.');
|
||||
if (dot)
|
||||
*dot = '\0';
|
||||
|
||||
theme_dir = g_build_filename (themes_dir, basename, NULL);
|
||||
if (g_mkdir_with_parents (theme_dir, 0700) != 0)
|
||||
{
|
||||
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
|
||||
_("Failed to create theme directory."));
|
||||
g_free (theme_dir);
|
||||
char *basename = g_path_get_basename (archive_path);
|
||||
char *dot;
|
||||
|
||||
if (basename && *basename)
|
||||
{
|
||||
dot = strrchr (basename, '.');
|
||||
if (dot)
|
||||
*dot = '\0';
|
||||
if (g_str_has_suffix (basename, ".tar"))
|
||||
basename[strlen (basename) - 4] = '\0';
|
||||
if (*basename)
|
||||
archive_root = g_strdup (basename);
|
||||
}
|
||||
g_free (basename);
|
||||
g_free (themes_dir);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
temp_dir = g_dir_make_tmp ("zoitechat-gtk3-theme-XXXXXX", error);
|
||||
if (!temp_dir)
|
||||
return FALSE;
|
||||
|
||||
#ifdef WIN32
|
||||
powershell = g_find_program_in_path ("powershell.exe");
|
||||
if (!powershell)
|
||||
powershell = g_find_program_in_path ("powershell");
|
||||
|
||||
if (!powershell)
|
||||
{
|
||||
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT,
|
||||
_("No archive extractor was found."));
|
||||
ok = FALSE;
|
||||
}
|
||||
else
|
||||
if (archive_type == ZOITECHAT_GTK3_ARCHIVE_ZIP)
|
||||
{
|
||||
char *powershell = NULL;
|
||||
char *command = NULL;
|
||||
GString *escaped_path = g_string_new ("'");
|
||||
GString *escaped_dir = g_string_new ("'");
|
||||
const char *cursor;
|
||||
|
||||
for (cursor = path; *cursor != '\0'; cursor++)
|
||||
powershell = g_find_program_in_path ("powershell.exe");
|
||||
if (!powershell)
|
||||
powershell = g_find_program_in_path ("powershell");
|
||||
if (!powershell)
|
||||
{
|
||||
g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT,
|
||||
_("No archive extractor was found."));
|
||||
g_string_free (escaped_path, TRUE);
|
||||
g_string_free (escaped_dir, TRUE);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (cursor = archive_path; *cursor != '\0'; cursor++)
|
||||
{
|
||||
if (*cursor == '\'')
|
||||
g_string_append (escaped_path, "''");
|
||||
@@ -404,7 +709,7 @@ zoitechat_import_theme (const char *path, GError **error)
|
||||
}
|
||||
g_string_append_c (escaped_path, '\'');
|
||||
|
||||
for (cursor = theme_dir; *cursor != '\0'; cursor++)
|
||||
for (cursor = temp_dir; *cursor != '\0'; cursor++)
|
||||
{
|
||||
if (*cursor == '\'')
|
||||
g_string_append (escaped_dir, "''");
|
||||
@@ -414,25 +719,18 @@ zoitechat_import_theme (const char *path, GError **error)
|
||||
g_string_append_c (escaped_dir, '\'');
|
||||
|
||||
command = g_strdup_printf (
|
||||
"Add-Type -AssemblyName WindowsBase; "
|
||||
"Add-Type -AssemblyName System.IO.Compression.FileSystem; "
|
||||
"$ErrorActionPreference='Stop'; "
|
||||
"$package=[System.IO.Packaging.Package]::Open(%s); "
|
||||
"$archive=[System.IO.Compression.ZipFile]::OpenRead(%s); "
|
||||
"try { "
|
||||
"foreach ($part in $package.GetParts()) { "
|
||||
"$relative=$part.Uri.OriginalString.TrimStart('/'); "
|
||||
"if ([string]::IsNullOrEmpty($relative)) { continue }; "
|
||||
"$destPath=[System.IO.Path]::Combine(%s, $relative); "
|
||||
"$destDir=[System.IO.Path]::GetDirectoryName($destPath); "
|
||||
"if ($destDir -and -not (Test-Path -LiteralPath $destDir)) { "
|
||||
"[System.IO.Directory]::CreateDirectory($destDir) | Out-Null "
|
||||
"foreach ($entry in $archive.Entries) { "
|
||||
"$name=$entry.FullName; "
|
||||
"if ([string]::IsNullOrEmpty($name)) { continue }; "
|
||||
"if ([System.IO.Path]::IsPathRooted($name) -or $name.StartsWith('/') -or $name.StartsWith('\\') -or $name.Contains('..\\') -or $name.Contains('../')) { throw 'Archive contains unsafe path: ' + $name } "
|
||||
"}; "
|
||||
"$partStream=$part.GetStream(); "
|
||||
"$fileStream=[System.IO.File]::Open($destPath,[System.IO.FileMode]::Create,[System.IO.FileAccess]::Write); "
|
||||
"$partStream.CopyTo($fileStream); "
|
||||
"$fileStream.Dispose(); "
|
||||
"$partStream.Dispose(); "
|
||||
"} "
|
||||
"} finally { $package.Close(); }",
|
||||
"[System.IO.Compression.ZipFile]::ExtractToDirectory(%s,%s,$true); "
|
||||
"} finally { $archive.Dispose(); }",
|
||||
escaped_path->str,
|
||||
escaped_path->str,
|
||||
escaped_dir->str);
|
||||
g_string_free (escaped_path, TRUE);
|
||||
@@ -443,47 +741,141 @@ zoitechat_import_theme (const char *path, GError **error)
|
||||
ok = g_spawn_sync (NULL, ps_argv, NULL, 0, NULL, NULL,
|
||||
NULL, NULL, &status, error);
|
||||
}
|
||||
if (ok)
|
||||
ok = g_spawn_check_exit_status (status, error);
|
||||
|
||||
g_free (command);
|
||||
g_free (powershell);
|
||||
if (!ok)
|
||||
goto cleanup;
|
||||
}
|
||||
else
|
||||
{
|
||||
char *argv[] = {"tar", "-xf", (char *)archive_path, "-C", temp_dir, NULL};
|
||||
char *extracted_root = NULL;
|
||||
|
||||
if (!zoitechat_validate_tar_entries_unix (archive_path, &extracted_root, error))
|
||||
goto cleanup;
|
||||
if (extracted_root)
|
||||
{
|
||||
g_free (archive_root);
|
||||
archive_root = extracted_root;
|
||||
}
|
||||
|
||||
ok = g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
|
||||
NULL, NULL, &status, error);
|
||||
if (!ok)
|
||||
goto cleanup;
|
||||
if (!g_spawn_check_exit_status (status, error))
|
||||
goto cleanup;
|
||||
}
|
||||
#else
|
||||
argv[4] = theme_dir;
|
||||
ok = g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
|
||||
NULL, NULL, &status, error);
|
||||
#endif
|
||||
if (!ok)
|
||||
if (archive_type == ZOITECHAT_GTK3_ARCHIVE_ZIP)
|
||||
{
|
||||
#ifdef WIN32
|
||||
g_free (command);
|
||||
g_free (powershell);
|
||||
char *argv[] = {"unzip", "-o", (char *)archive_path, "-d", temp_dir, NULL};
|
||||
char *extracted_root = NULL;
|
||||
|
||||
if (!zoitechat_validate_zip_entries_unix (archive_path, &extracted_root, error))
|
||||
goto cleanup;
|
||||
if (extracted_root)
|
||||
{
|
||||
g_free (archive_root);
|
||||
archive_root = extracted_root;
|
||||
}
|
||||
|
||||
ok = g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
|
||||
NULL, NULL, &status, error);
|
||||
if (!ok)
|
||||
goto cleanup;
|
||||
if (!g_spawn_check_exit_status (status, error))
|
||||
goto cleanup;
|
||||
}
|
||||
else
|
||||
{
|
||||
char *argv[] = {"tar", "-xf", (char *)archive_path, "-C", temp_dir, NULL};
|
||||
char *extracted_root = NULL;
|
||||
|
||||
if (!zoitechat_validate_tar_entries_unix (archive_path, &extracted_root, error))
|
||||
goto cleanup;
|
||||
if (extracted_root)
|
||||
{
|
||||
g_free (archive_root);
|
||||
archive_root = extracted_root;
|
||||
}
|
||||
|
||||
ok = g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
|
||||
NULL, NULL, &status, error);
|
||||
if (!ok)
|
||||
goto cleanup;
|
||||
if (!g_spawn_check_exit_status (status, error))
|
||||
goto cleanup;
|
||||
}
|
||||
#endif
|
||||
g_free (theme_dir);
|
||||
g_free (basename);
|
||||
g_free (themes_dir);
|
||||
return FALSE;
|
||||
|
||||
if (!zoitechat_find_gtk3_theme_root (temp_dir, &theme_root, &has_dark_css, &missing_gtk_css, error))
|
||||
{
|
||||
if (error && *error)
|
||||
goto cleanup;
|
||||
|
||||
if (missing_gtk_css)
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||||
_("Archive contains a gtk-3.x directory, but gtk.css is missing."));
|
||||
else
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||||
_("Archive is not a GTK3 theme. Expected layout: <theme-name>/gtk-3.x/gtk.css."));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!g_spawn_check_exit_status (status, error))
|
||||
theme_name = g_path_get_basename (theme_root);
|
||||
if (!theme_name || !*theme_name)
|
||||
{
|
||||
#ifdef WIN32
|
||||
g_free (command);
|
||||
g_free (powershell);
|
||||
#endif
|
||||
g_free (theme_dir);
|
||||
g_free (basename);
|
||||
g_free (themes_dir);
|
||||
return FALSE;
|
||||
g_clear_pointer (&theme_name, g_free);
|
||||
if (archive_root)
|
||||
theme_name = g_strdup (archive_root);
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
g_free (command);
|
||||
g_free (powershell);
|
||||
#endif
|
||||
if (!theme_name || !*theme_name)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||||
_("Unable to determine GTK3 theme directory name from archive."));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
g_free (theme_dir);
|
||||
g_free (basename);
|
||||
g_free (themes_dir);
|
||||
return TRUE;
|
||||
store_dir = g_build_filename (get_xdir (), "gtk3-themes", NULL);
|
||||
dest_dir = g_build_filename (store_dir, theme_name, NULL);
|
||||
|
||||
if (g_file_test (dest_dir, G_FILE_TEST_EXISTS) && !zoitechat_remove_tree (dest_dir, error))
|
||||
goto cleanup;
|
||||
|
||||
if (!zoitechat_copy_directory_recursive (theme_root, dest_dir, error))
|
||||
goto cleanup;
|
||||
|
||||
if (has_dark_css)
|
||||
fe_message (_("Imported GTK3 theme includes gtk-dark.css."), FE_MSG_INFO);
|
||||
|
||||
if (theme_name_out)
|
||||
*theme_name_out = g_strdup (theme_name);
|
||||
|
||||
ok = TRUE;
|
||||
|
||||
cleanup:
|
||||
if (temp_dir)
|
||||
{
|
||||
GError *cleanup_error = NULL;
|
||||
if (!zoitechat_remove_tree (temp_dir, &cleanup_error))
|
||||
g_clear_error (&cleanup_error);
|
||||
}
|
||||
g_free (dest_dir);
|
||||
g_free (store_dir);
|
||||
g_free (theme_name);
|
||||
g_free (theme_root);
|
||||
g_free (archive_root);
|
||||
g_free (temp_dir);
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Update the priority queue of the "interesting sessions"
|
||||
* (sess_list_by_lastact).
|
||||
@@ -812,37 +1204,29 @@ irc_init (session *sess)
|
||||
if (zoitechat_theme_path_from_arg (arg_url, &theme_path))
|
||||
{
|
||||
GError *error = NULL;
|
||||
char *basename = g_path_get_basename (theme_path);
|
||||
char *dot = strrchr (basename, '.');
|
||||
char *message;
|
||||
char *theme_name = NULL;
|
||||
|
||||
if (dot)
|
||||
*dot = '\0';
|
||||
|
||||
if (zoitechat_import_theme (theme_path, &error))
|
||||
if (zoitechat_import_gtk3_theme_archive (theme_path, &theme_name, &error))
|
||||
{
|
||||
if (zoitechat_apply_theme (basename, &error))
|
||||
if (theme_name)
|
||||
{
|
||||
message = g_strdup_printf (_("Theme \"%s\" imported and applied."), basename);
|
||||
char *message = g_strdup_printf (_("GTK3 theme \"%s\" imported. Use Theme settings to apply it."), theme_name);
|
||||
fe_message (message, FE_MSG_INFO);
|
||||
handle_command (sess, "gui apply", FALSE);
|
||||
g_free (message);
|
||||
}
|
||||
else
|
||||
{
|
||||
fe_message (error ? error->message : _("Theme imported, but failed to apply."),
|
||||
FE_MSG_ERROR);
|
||||
g_clear_error (&error);
|
||||
fe_message (_("GTK3 theme imported. Use Theme settings to apply it."), FE_MSG_INFO);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fe_message (error ? error->message : _("Failed to import theme."),
|
||||
fe_message (error ? error->message : _("Failed to import GTK3 theme archive."),
|
||||
FE_MSG_ERROR);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
g_free (basename);
|
||||
g_free (theme_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -864,37 +1248,29 @@ irc_init (session *sess)
|
||||
if (zoitechat_theme_path_from_arg (arg_urls[i], &theme_path))
|
||||
{
|
||||
GError *error = NULL;
|
||||
char *basename = g_path_get_basename (theme_path);
|
||||
char *dot = strrchr (basename, '.');
|
||||
char *message;
|
||||
char *theme_name = NULL;
|
||||
|
||||
if (dot)
|
||||
*dot = '\0';
|
||||
|
||||
if (zoitechat_import_theme (theme_path, &error))
|
||||
if (zoitechat_import_gtk3_theme_archive (theme_path, &theme_name, &error))
|
||||
{
|
||||
if (zoitechat_apply_theme (basename, &error))
|
||||
if (theme_name)
|
||||
{
|
||||
message = g_strdup_printf (_("Theme \"%s\" imported and applied."), basename);
|
||||
char *message = g_strdup_printf (_("GTK3 theme \"%s\" imported. Use Theme settings to apply it."), theme_name);
|
||||
fe_message (message, FE_MSG_INFO);
|
||||
handle_command (sess, "gui apply", FALSE);
|
||||
g_free (message);
|
||||
}
|
||||
else
|
||||
{
|
||||
fe_message (error ? error->message : _("Theme imported, but failed to apply."),
|
||||
FE_MSG_ERROR);
|
||||
g_clear_error (&error);
|
||||
fe_message (_("GTK3 theme imported. Use Theme settings to apply it."), FE_MSG_INFO);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fe_message (error ? error->message : _("Failed to import theme."),
|
||||
fe_message (error ? error->message : _("Failed to import GTK3 theme archive."),
|
||||
FE_MSG_ERROR);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
g_free (basename);
|
||||
g_free (theme_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1477,6 +1853,10 @@ main (int argc, char *argv[])
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
#ifdef WIN32
|
||||
char **win32_argv = NULL;
|
||||
int win32_argc;
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
HRESULT coinit_result;
|
||||
@@ -1484,6 +1864,23 @@ main (int argc, char *argv[])
|
||||
|
||||
srand ((unsigned int) time (NULL)); /* CL: do this only once! */
|
||||
|
||||
#ifdef WIN32
|
||||
win32_argv = g_win32_get_command_line ();
|
||||
if (win32_argv != NULL)
|
||||
{
|
||||
win32_argc = g_strv_length (win32_argv);
|
||||
if (win32_argc == 0 || win32_argv[0] == NULL || win32_argv[0][0] == '\0')
|
||||
{
|
||||
g_strfreev (win32_argv);
|
||||
win32_argv = g_new0 (char *, 2);
|
||||
win32_argv[0] = g_strdup ("zoitechat");
|
||||
win32_argc = 1;
|
||||
}
|
||||
argv = win32_argv;
|
||||
argc = win32_argc;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* We must check for the config dir parameter, otherwise load_config() will behave incorrectly.
|
||||
* load_config() must come before fe_args() because fe_args() calls gtk_init() which needs to
|
||||
* know the language which is set in the config. The code below is copy-pasted from fe_args()
|
||||
@@ -1534,11 +1931,19 @@ main (int argc, char *argv[])
|
||||
|
||||
ret = fe_args (argc, argv);
|
||||
if (ret != -1)
|
||||
{
|
||||
#ifdef WIN32
|
||||
g_strfreev (win32_argv);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
if (zoitechat_remote_win32 ())
|
||||
{
|
||||
g_strfreev (win32_argv);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_DBUS
|
||||
@@ -1591,5 +1996,9 @@ main (int argc, char *argv[])
|
||||
WSACleanup ();
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
g_strfreev (win32_argv);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,10 @@
|
||||
#define ZOITECHAT_H
|
||||
|
||||
gboolean zoitechat_theme_path_from_arg (const char *arg, char **path_out);
|
||||
gboolean zoitechat_import_theme (const char *path, GError **error);
|
||||
gboolean zoitechat_apply_theme (const char *theme_name, GError **error);
|
||||
/* Imports a GTK3 theme archive into ZoiteChat's own gtk3-themes store. */
|
||||
gboolean zoitechat_import_gtk3_theme_archive (const char *archive_path,
|
||||
char **theme_name_out,
|
||||
GError **error);
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
#ifdef __APPLE__
|
||||
@@ -295,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];
|
||||
|
||||
@@ -562,7 +562,10 @@ banlist_clear (GtkWidget * wid, banlist_info *banl)
|
||||
|
||||
dialog = gtk_message_dialog_new (NULL, 0,
|
||||
GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
|
||||
_("Are you sure you want to remove all listed items in %s?"), banl->sess->channel);
|
||||
_("Are you sure you want to remove all listed items in %s?"), banl->sess->channel);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
|
||||
g_signal_connect (G_OBJECT (dialog), "response",
|
||||
G_CALLBACK (banlist_clear_cb), banl);
|
||||
|
||||
@@ -127,15 +127,15 @@ chanview_apply_theme (chanview *cv)
|
||||
if (input_style)
|
||||
font = input_style->font_desc;
|
||||
|
||||
if (fe_dark_mode_is_enabled () || prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_LIGHT)
|
||||
{
|
||||
gtkutil_apply_palette (w, &colors[COL_BG], &colors[COL_FG], font);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Keep list font in sync while reverting colors to theme defaults. */
|
||||
gtkutil_apply_palette (w, NULL, NULL, font);
|
||||
}
|
||||
/*
|
||||
* setup_apply_to_sess() and palette_apply_dark_mode() treat all dark-mode
|
||||
* preference modes as palette-driven: dark uses curated dark colors, while
|
||||
* light/auto-light use the user's saved palette.
|
||||
*
|
||||
* Keep chanview aligned with that resolved behavior so AUTO doesn't
|
||||
* accidentally revert to theme defaults and clear custom colors.
|
||||
*/
|
||||
gtkutil_apply_palette (w, &colors[COL_BG], &colors[COL_FG], font);
|
||||
}
|
||||
|
||||
static char *
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "fe-gtk.h"
|
||||
|
||||
@@ -105,6 +106,9 @@ create_msg_dialog (gchar *title, gchar *message)
|
||||
GtkWidget *dialog;
|
||||
|
||||
dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", message);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
gtk_window_set_title (GTK_WINDOW (dialog), title);
|
||||
|
||||
/* On Win32 we automatically have the icon. If we try to load it explicitly, it will look ugly for some reason. */
|
||||
@@ -406,7 +410,12 @@ fe_args (int argc, char *argv[])
|
||||
/* of the exe. */
|
||||
{
|
||||
g_free (win32_argv0_dir);
|
||||
win32_argv0_dir = g_path_get_dirname (argv[0]);
|
||||
/* In subsystem:windows builds, argv can be absent/invalid depending on
|
||||
* launch context (e.g. shell URL handlers). Prefer the module path,
|
||||
* then only fall back to argv[0] when it is available. */
|
||||
win32_argv0_dir = g_win32_get_package_installation_directory_of_module (NULL);
|
||||
if (win32_argv0_dir == NULL && argc > 0 && argv != NULL && argv[0] != NULL)
|
||||
win32_argv0_dir = g_path_get_dirname (argv[0]);
|
||||
if (win32_argv0_dir)
|
||||
chdir (win32_argv0_dir);
|
||||
}
|
||||
@@ -537,10 +546,13 @@ static gboolean
|
||||
fe_system_prefers_dark (void)
|
||||
{
|
||||
GtkSettings *settings = gtk_settings_get_default ();
|
||||
gboolean theme_name_prefers_dark = FALSE;
|
||||
gboolean property_prefers_dark = FALSE;
|
||||
gboolean prefer_dark = FALSE;
|
||||
char *theme_name = NULL;
|
||||
#ifdef G_OS_WIN32
|
||||
gboolean have_win_pref = FALSE;
|
||||
gboolean win_prefers_dark = FALSE;
|
||||
|
||||
if (fe_win32_high_contrast_is_enabled ())
|
||||
return FALSE;
|
||||
@@ -549,37 +561,336 @@ fe_system_prefers_dark (void)
|
||||
if (!settings)
|
||||
return FALSE;
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
have_win_pref = fe_win32_try_get_system_dark (&prefer_dark);
|
||||
if (!have_win_pref)
|
||||
#endif
|
||||
g_object_get (settings, "gtk-theme-name", &theme_name, NULL);
|
||||
if (theme_name)
|
||||
{
|
||||
char *lower = g_ascii_strdown (theme_name, -1);
|
||||
if (g_str_has_suffix (lower, "-dark") || g_strrstr (lower, "dark"))
|
||||
theme_name_prefers_dark = TRUE;
|
||||
g_free (lower);
|
||||
g_free (theme_name);
|
||||
}
|
||||
|
||||
if (g_object_class_find_property (G_OBJECT_GET_CLASS (settings),
|
||||
"gtk-application-prefer-dark-theme"))
|
||||
{
|
||||
g_object_get (settings, "gtk-application-prefer-dark-theme", &prefer_dark, NULL);
|
||||
/* Even if we last wrote this property, the toolkit or desktop can update
|
||||
* it later, so AUTO mode should keep reading it as a signal. */
|
||||
g_object_get (settings, "gtk-application-prefer-dark-theme", &property_prefers_dark, NULL);
|
||||
}
|
||||
|
||||
if (!prefer_dark)
|
||||
{
|
||||
g_object_get (settings, "gtk-theme-name", &theme_name, NULL);
|
||||
if (theme_name)
|
||||
{
|
||||
char *lower = g_ascii_strdown (theme_name, -1);
|
||||
if (g_str_has_suffix (lower, "-dark") || g_strrstr (lower, "dark"))
|
||||
prefer_dark = TRUE;
|
||||
g_free (lower);
|
||||
g_free (theme_name);
|
||||
}
|
||||
}
|
||||
#ifdef G_OS_WIN32
|
||||
have_win_pref = fe_win32_try_get_system_dark (&win_prefers_dark);
|
||||
#endif
|
||||
|
||||
/* Deterministic precedence: any explicit dark signal wins. */
|
||||
prefer_dark = theme_name_prefers_dark || property_prefers_dark;
|
||||
#ifdef G_OS_WIN32
|
||||
prefer_dark = prefer_dark || (have_win_pref && win_prefers_dark);
|
||||
#endif
|
||||
|
||||
return prefer_dark;
|
||||
}
|
||||
|
||||
static gboolean auto_dark_mode_enabled = FALSE;
|
||||
static gboolean dark_mode_state_initialized = FALSE;
|
||||
|
||||
|
||||
static gboolean
|
||||
fe_parse_gtk3_minor_from_dirname (const char *name, gint *minor_out)
|
||||
{
|
||||
const char *prefix = "gtk-3.";
|
||||
char *endptr = NULL;
|
||||
long value;
|
||||
|
||||
if (!name || !g_str_has_prefix (name, prefix))
|
||||
return FALSE;
|
||||
|
||||
value = strtol (name + strlen (prefix), &endptr, 10);
|
||||
if (endptr == name + strlen (prefix) || *endptr != '\0' || value < 0 || value > G_MAXINT)
|
||||
return FALSE;
|
||||
|
||||
if (minor_out)
|
||||
*minor_out = (gint) value;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fe_resolve_gtk3_theme_dir (const char *theme_root,
|
||||
char **gtk3_dir_out,
|
||||
gboolean *has_dark_css_out)
|
||||
{
|
||||
GDir *dir;
|
||||
const char *entry;
|
||||
gint runtime_minor = gtk_get_minor_version ();
|
||||
gint best_minor = -1;
|
||||
gint best_distance = G_MAXINT;
|
||||
char *best_dir = NULL;
|
||||
gboolean has_dark = FALSE;
|
||||
|
||||
if (gtk3_dir_out)
|
||||
*gtk3_dir_out = NULL;
|
||||
if (has_dark_css_out)
|
||||
*has_dark_css_out = FALSE;
|
||||
|
||||
if (!theme_root || !*theme_root)
|
||||
return FALSE;
|
||||
|
||||
dir = g_dir_open (theme_root, 0, NULL);
|
||||
if (!dir)
|
||||
return FALSE;
|
||||
|
||||
while ((entry = g_dir_read_name (dir)) != NULL)
|
||||
{
|
||||
char *candidate_dir;
|
||||
char *gtk_css;
|
||||
char *gtk_dark_css;
|
||||
gint minor;
|
||||
gint distance;
|
||||
|
||||
if (!fe_parse_gtk3_minor_from_dirname (entry, &minor))
|
||||
continue;
|
||||
|
||||
candidate_dir = g_build_filename (theme_root, entry, NULL);
|
||||
if (!g_file_test (candidate_dir, G_FILE_TEST_IS_DIR))
|
||||
{
|
||||
g_free (candidate_dir);
|
||||
continue;
|
||||
}
|
||||
|
||||
gtk_css = g_build_filename (candidate_dir, "gtk.css", NULL);
|
||||
if (!g_file_test (gtk_css, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
g_free (gtk_css);
|
||||
g_free (candidate_dir);
|
||||
continue;
|
||||
}
|
||||
g_free (gtk_css);
|
||||
|
||||
distance = (minor <= runtime_minor)
|
||||
? (runtime_minor - minor)
|
||||
: (10000 + (minor - runtime_minor));
|
||||
|
||||
if (!best_dir || distance < best_distance || (distance == best_distance && minor > best_minor))
|
||||
{
|
||||
g_free (best_dir);
|
||||
best_dir = candidate_dir;
|
||||
best_minor = minor;
|
||||
best_distance = distance;
|
||||
|
||||
gtk_dark_css = g_build_filename (best_dir, "gtk-dark.css", NULL);
|
||||
has_dark = g_file_test (gtk_dark_css, G_FILE_TEST_IS_REGULAR);
|
||||
g_free (gtk_dark_css);
|
||||
|
||||
candidate_dir = NULL;
|
||||
}
|
||||
|
||||
g_free (candidate_dir);
|
||||
}
|
||||
|
||||
g_dir_close (dir);
|
||||
|
||||
if (!best_dir)
|
||||
return FALSE;
|
||||
|
||||
if (gtk3_dir_out)
|
||||
*gtk3_dir_out = g_strdup (best_dir);
|
||||
if (has_dark_css_out)
|
||||
*has_dark_css_out = has_dark;
|
||||
g_free (best_dir);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GtkCssProvider *gtk3_theme_provider = NULL;
|
||||
static char *gtk3_theme_provider_name = NULL;
|
||||
static gboolean gtk3_theme_provider_dark = FALSE;
|
||||
static GResource *gtk3_theme_resource = NULL;
|
||||
static char *gtk3_theme_resource_path = NULL;
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
static void fe_apply_windows_theme (gboolean dark);
|
||||
#endif
|
||||
|
||||
static void
|
||||
fe_apply_windows_theme (gboolean dark)
|
||||
fe_gtk3_theme_unregister_resource (void)
|
||||
{
|
||||
if (gtk3_theme_resource)
|
||||
{
|
||||
g_resources_unregister (gtk3_theme_resource);
|
||||
g_clear_pointer (>k3_theme_resource, g_resource_unref);
|
||||
}
|
||||
|
||||
g_clear_pointer (>k3_theme_resource_path, g_free);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fe_gtk3_theme_register_resource (const char *resource_path, GError **error)
|
||||
{
|
||||
GResource *resource;
|
||||
|
||||
if (!resource_path || !*resource_path)
|
||||
{
|
||||
fe_gtk3_theme_unregister_resource ();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (gtk3_theme_resource && gtk3_theme_resource_path
|
||||
&& g_strcmp0 (gtk3_theme_resource_path, resource_path) == 0)
|
||||
return TRUE;
|
||||
|
||||
resource = g_resource_load (resource_path, error);
|
||||
if (!resource)
|
||||
return FALSE;
|
||||
|
||||
fe_gtk3_theme_unregister_resource ();
|
||||
g_resources_register (resource);
|
||||
gtk3_theme_resource = resource;
|
||||
gtk3_theme_resource_path = g_strdup (resource_path);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fe_apply_gtk3_theme_with_reload (const char *theme_name, gboolean force_reload, 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;
|
||||
char *gtk_resource = NULL;
|
||||
const char *selected_css = NULL;
|
||||
gboolean dark = fe_dark_mode_is_enabled ();
|
||||
GList *toplevels, *node;
|
||||
|
||||
if (!theme_name || !*theme_name)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
/* Keep the Win32 fallback provider in sync when returning to the
|
||||
* system theme from Preferences > Themes. */
|
||||
fe_apply_windows_theme (dark);
|
||||
#endif
|
||||
|
||||
if (gtk3_theme_provider && screen)
|
||||
{
|
||||
gtk_style_context_remove_provider_for_screen (
|
||||
screen,
|
||||
GTK_STYLE_PROVIDER (gtk3_theme_provider));
|
||||
gtk_style_context_reset_widgets (screen);
|
||||
}
|
||||
g_clear_object (>k3_theme_provider);
|
||||
g_clear_pointer (>k3_theme_provider_name, g_free);
|
||||
gtk3_theme_provider_dark = FALSE;
|
||||
fe_gtk3_theme_unregister_resource ();
|
||||
|
||||
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 (!force_reload
|
||||
&& 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);
|
||||
if (!fe_resolve_gtk3_theme_dir (theme_dir, >k3_dir, NULL))
|
||||
{
|
||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT,
|
||||
_("GTK3 theme '%s' is missing a valid gtk-3.x/gtk.css directory."), theme_name);
|
||||
g_free (theme_dir);
|
||||
return FALSE;
|
||||
}
|
||||
gtk_css = g_build_filename (gtk3_dir, "gtk.css", NULL);
|
||||
gtk_dark_css = g_build_filename (gtk3_dir, "gtk-dark.css", NULL);
|
||||
gtk_resource = g_build_filename (gtk3_dir, "gtk.gresource", NULL);
|
||||
|
||||
if (dark && g_file_test (gtk_dark_css, G_FILE_TEST_IS_REGULAR))
|
||||
selected_css = gtk_dark_css;
|
||||
else
|
||||
selected_css = gtk_css;
|
||||
|
||||
if (g_file_test (gtk_resource, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
if (!fe_gtk3_theme_register_resource (gtk_resource, error))
|
||||
{
|
||||
g_free (gtk_resource);
|
||||
g_free (gtk_dark_css);
|
||||
g_free (gtk_css);
|
||||
g_free (gtk3_dir);
|
||||
g_free (theme_dir);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fe_gtk3_theme_unregister_resource ();
|
||||
}
|
||||
|
||||
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 (gtk_resource);
|
||||
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),
|
||||
/* Use user priority so imported GTK3 themes can also override
|
||||
* ZoiteChat's own application CSS (for example, button backgrounds). */
|
||||
GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||
gtk_style_context_reset_widgets (screen);
|
||||
}
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
/* Applying a GTK3 theme should immediately disable ZoiteChat's Win32
|
||||
* fallback window CSS so buttons/chat widgets are fully theme-driven. */
|
||||
fe_apply_windows_theme (dark);
|
||||
#endif
|
||||
|
||||
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 (gtk_resource);
|
||||
g_free (gtk3_dir);
|
||||
g_free (theme_dir);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
fe_apply_gtk3_theme (const char *theme_name, GError **error)
|
||||
{
|
||||
return fe_apply_gtk3_theme_with_reload (theme_name, FALSE, error);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void
|
||||
fe_set_gtk_prefer_dark_theme (gboolean dark)
|
||||
{
|
||||
GtkSettings *settings = gtk_settings_get_default ();
|
||||
|
||||
@@ -588,24 +899,94 @@ fe_apply_windows_theme (gboolean dark)
|
||||
{
|
||||
g_object_set (settings, "gtk-application-prefer-dark-theme", dark, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
static void
|
||||
fe_append_window_theme_class_css (GString *css,
|
||||
const char *class_name,
|
||||
const PaletteColor *fg,
|
||||
const PaletteColor *bg)
|
||||
{
|
||||
char *fg_css = gdk_rgba_to_string (fg);
|
||||
char *bg_css = gdk_rgba_to_string (bg);
|
||||
|
||||
g_string_append_printf (css,
|
||||
"window.%s, .%s {"
|
||||
"background-color: %s;"
|
||||
"color: %s;"
|
||||
"}"
|
||||
"window.%s button, .%s button, window.%s entry, .%s entry, "
|
||||
"window.%s treeview, .%s treeview, window.%s scrolledwindow, .%s scrolledwindow {"
|
||||
"background-color: %s;"
|
||||
"color: %s;"
|
||||
"}",
|
||||
class_name,
|
||||
class_name,
|
||||
class_name,
|
||||
class_name,
|
||||
class_name,
|
||||
class_name,
|
||||
class_name,
|
||||
class_name,
|
||||
class_name,
|
||||
class_name,
|
||||
bg_css,
|
||||
fg_css);
|
||||
|
||||
g_free (fg_css);
|
||||
g_free (bg_css);
|
||||
}
|
||||
|
||||
static void
|
||||
fe_apply_windows_theme (gboolean dark)
|
||||
{
|
||||
static GtkCssProvider *win_theme_provider = NULL;
|
||||
fe_set_gtk_prefer_dark_theme (dark);
|
||||
|
||||
{
|
||||
static GtkCssProvider *win_theme_provider = NULL;
|
||||
GdkScreen *screen = gdk_screen_get_default ();
|
||||
const char *css =
|
||||
"window.zoitechat-dark, .zoitechat-dark {"
|
||||
"background-color: #202020;"
|
||||
"color: #f0f0f0;"
|
||||
"}"
|
||||
"window.zoitechat-light, .zoitechat-light {"
|
||||
"background-color: #f6f6f6;"
|
||||
"color: #101010;"
|
||||
"}";
|
||||
const PaletteColor *light_palette = palette_user_colors ();
|
||||
const PaletteColor *dark_palette = palette_dark_colors ();
|
||||
GString *css;
|
||||
|
||||
/* Let imported GTK3 themes own all widget/window colors on Windows.
|
||||
* Otherwise ZoiteChat's fallback dark/light window background CSS can
|
||||
* clash with theme widget colors (for example white buttons on a dark
|
||||
* window background).
|
||||
*
|
||||
* Use the active provider state (not only the configured preference): if
|
||||
* a configured theme fails to load we still want fallback palette CSS so
|
||||
* the app keeps a coherent dark/light appearance on Windows releases.
|
||||
*/
|
||||
if (gtk3_theme_provider != NULL)
|
||||
{
|
||||
if (win_theme_provider && screen)
|
||||
{
|
||||
gtk_style_context_remove_provider_for_screen (
|
||||
screen,
|
||||
GTK_STYLE_PROVIDER (win_theme_provider));
|
||||
gtk_style_context_reset_widgets (screen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
css = g_string_new (NULL);
|
||||
|
||||
if (!win_theme_provider)
|
||||
win_theme_provider = gtk_css_provider_new ();
|
||||
|
||||
gtk_css_provider_load_from_data (win_theme_provider, css, -1, NULL);
|
||||
fe_append_window_theme_class_css (css,
|
||||
"zoitechat-dark",
|
||||
&dark_palette[COL_FG],
|
||||
&dark_palette[COL_BG]);
|
||||
fe_append_window_theme_class_css (css,
|
||||
"zoitechat-light",
|
||||
&light_palette[COL_FG],
|
||||
&light_palette[COL_BG]);
|
||||
|
||||
gtk_css_provider_load_from_data (win_theme_provider, css->str, -1, NULL);
|
||||
g_string_free (css, TRUE);
|
||||
if (screen)
|
||||
gtk_style_context_add_provider_for_screen (
|
||||
screen,
|
||||
@@ -640,6 +1021,13 @@ void
|
||||
fe_set_auto_dark_mode_state (gboolean enabled)
|
||||
{
|
||||
auto_dark_mode_enabled = enabled;
|
||||
dark_mode_state_initialized = TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
fe_dark_mode_state_is_initialized (void)
|
||||
{
|
||||
return dark_mode_state_initialized;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -652,6 +1040,11 @@ gboolean
|
||||
fe_apply_theme_for_mode (unsigned int mode, gboolean *palette_changed)
|
||||
{
|
||||
gboolean enabled = fe_dark_mode_is_enabled_for (mode);
|
||||
GList *toplevels, *node;
|
||||
|
||||
/* Apply the optional global GTK preference first, then reapply palette-driven
|
||||
* chat/input colors so Preferences > Colors continues to take precedence. */
|
||||
fe_set_gtk_prefer_dark_theme (enabled);
|
||||
gboolean changed = palette_apply_dark_mode (enabled);
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
@@ -664,30 +1057,42 @@ 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 ();
|
||||
for (node = toplevels; node; node = node->next)
|
||||
fe_apply_theme_to_toplevel (GTK_WIDGET (node->data));
|
||||
g_list_free (toplevels);
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void
|
||||
fe_apply_theme_to_toplevel (GtkWidget *window)
|
||||
{
|
||||
GtkStyleContext *context;
|
||||
gboolean dark;
|
||||
|
||||
if (!window)
|
||||
return;
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
context = gtk_widget_get_style_context (window);
|
||||
dark = fe_dark_mode_is_enabled ();
|
||||
|
||||
if (context)
|
||||
{
|
||||
GtkStyleContext *context = gtk_widget_get_style_context (window);
|
||||
gboolean dark = fe_dark_mode_is_enabled ();
|
||||
|
||||
if (context)
|
||||
{
|
||||
gtk_style_context_remove_class (context, "zoitechat-dark");
|
||||
gtk_style_context_remove_class (context, "zoitechat-light");
|
||||
gtk_style_context_add_class (context, dark ? "zoitechat-dark" : "zoitechat-light");
|
||||
}
|
||||
gtk_style_context_remove_class (context, "zoitechat-dark");
|
||||
gtk_style_context_remove_class (context, "zoitechat-light");
|
||||
gtk_style_context_add_class (context, dark ? "zoitechat-dark" : "zoitechat-light");
|
||||
}
|
||||
#endif
|
||||
|
||||
fe_win32_apply_native_titlebar (window, fe_dark_mode_is_enabled ());
|
||||
fe_win32_apply_native_titlebar (window, dark);
|
||||
}
|
||||
|
||||
gboolean
|
||||
@@ -727,6 +1132,12 @@ create_input_style (InputStyle *style)
|
||||
static guint16 last_bg_red;
|
||||
static guint16 last_bg_green;
|
||||
static guint16 last_bg_blue;
|
||||
static guint16 last_sel_fg_red;
|
||||
static guint16 last_sel_fg_green;
|
||||
static guint16 last_sel_fg_blue;
|
||||
static guint16 last_sel_bg_red;
|
||||
static guint16 last_sel_bg_green;
|
||||
static guint16 last_sel_bg_blue;
|
||||
|
||||
if (!style)
|
||||
style = g_new0 (InputStyle, 1);
|
||||
@@ -758,6 +1169,12 @@ create_input_style (InputStyle *style)
|
||||
guint16 bg_red;
|
||||
guint16 bg_green;
|
||||
guint16 bg_blue;
|
||||
guint16 sel_fg_red;
|
||||
guint16 sel_fg_green;
|
||||
guint16 sel_fg_blue;
|
||||
guint16 sel_bg_red;
|
||||
guint16 sel_bg_green;
|
||||
guint16 sel_bg_blue;
|
||||
gboolean dark_mode = fe_dark_mode_is_enabled ();
|
||||
gboolean needs_reload;
|
||||
|
||||
@@ -765,6 +1182,8 @@ create_input_style (InputStyle *style)
|
||||
|
||||
palette_color_get_rgb16 (&colors[COL_FG], &fg_red, &fg_green, &fg_blue);
|
||||
palette_color_get_rgb16 (&colors[COL_BG], &bg_red, &bg_green, &bg_blue);
|
||||
palette_color_get_rgb16 (&colors[COL_MARK_FG], &sel_fg_red, &sel_fg_green, &sel_fg_blue);
|
||||
palette_color_get_rgb16 (&colors[COL_MARK_BG], &sel_bg_red, &sel_bg_green, &sel_bg_blue);
|
||||
needs_reload = !done_rc
|
||||
|| !last_input_style
|
||||
|| last_dark_mode != dark_mode
|
||||
@@ -775,7 +1194,13 @@ create_input_style (InputStyle *style)
|
||||
|| last_fg_blue != fg_blue
|
||||
|| last_bg_red != bg_red
|
||||
|| last_bg_green != bg_green
|
||||
|| last_bg_blue != bg_blue;
|
||||
|| last_bg_blue != bg_blue
|
||||
|| last_sel_fg_red != sel_fg_red
|
||||
|| last_sel_fg_green != sel_fg_green
|
||||
|| last_sel_fg_blue != sel_fg_blue
|
||||
|| last_sel_bg_red != sel_bg_red
|
||||
|| last_sel_bg_green != sel_bg_green
|
||||
|| last_sel_bg_blue != sel_bg_blue;
|
||||
|
||||
if (needs_reload)
|
||||
{
|
||||
@@ -799,28 +1224,107 @@ create_input_style (InputStyle *style)
|
||||
}
|
||||
{
|
||||
GString *css = g_string_new ("#zoitechat-inputbox {");
|
||||
GtkWidget *tmp_entry = NULL;
|
||||
GtkStyleContext *tmp_context = NULL;
|
||||
GdkRGBA selected_fg = { 0.0, 0.0, 0.0, 1.0 };
|
||||
GdkRGBA selected_bg = { 0.0, 0.0, 0.0, 1.0 };
|
||||
gboolean have_palette_selected_colors;
|
||||
const char *selection_fg_css = NULL;
|
||||
const char *selection_bg_css = NULL;
|
||||
char selection_fg_hex[8];
|
||||
char selection_bg_hex[8];
|
||||
char *selection_fg_fallback = NULL;
|
||||
char *selection_bg_fallback = NULL;
|
||||
|
||||
/* GTK3 equivalents for adwaita_workaround_rc/cursor_color_rc. */
|
||||
if (adwaita_workaround_rc[0] != '\0'
|
||||
&& theme_name
|
||||
&& (g_str_has_prefix (theme_name, "Adwaita")
|
||||
|| g_str_has_prefix (theme_name, "Yaru")))
|
||||
g_string_append (css, "background-image: none;");
|
||||
|
||||
have_palette_selected_colors =
|
||||
isfinite (colors[COL_MARK_FG].red)
|
||||
&& isfinite (colors[COL_MARK_FG].green)
|
||||
&& isfinite (colors[COL_MARK_FG].blue)
|
||||
&& isfinite (colors[COL_MARK_BG].red)
|
||||
&& isfinite (colors[COL_MARK_BG].green)
|
||||
&& isfinite (colors[COL_MARK_BG].blue);
|
||||
|
||||
if (have_palette_selected_colors)
|
||||
{
|
||||
g_snprintf (selection_fg_hex, sizeof (selection_fg_hex), "#%02x%02x%02x",
|
||||
(sel_fg_red >> 8), (sel_fg_green >> 8), (sel_fg_blue >> 8));
|
||||
g_snprintf (selection_bg_hex, sizeof (selection_bg_hex), "#%02x%02x%02x",
|
||||
(sel_bg_red >> 8), (sel_bg_green >> 8), (sel_bg_blue >> 8));
|
||||
selection_fg_css = selection_fg_hex;
|
||||
selection_bg_css = selection_bg_hex;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp_entry = gtk_entry_new ();
|
||||
tmp_context = tmp_entry ? gtk_widget_get_style_context (tmp_entry) : NULL;
|
||||
|
||||
if (tmp_context)
|
||||
{
|
||||
if (!gtk_style_context_lookup_color (
|
||||
tmp_context,
|
||||
"theme_selected_fg_color",
|
||||
&selected_fg))
|
||||
selected_fg = colors[COL_MARK_FG];
|
||||
|
||||
if (!gtk_style_context_lookup_color (
|
||||
tmp_context,
|
||||
"theme_selected_bg_color",
|
||||
&selected_bg))
|
||||
selected_bg = colors[COL_MARK_BG];
|
||||
}
|
||||
else
|
||||
{
|
||||
selected_fg = colors[COL_MARK_FG];
|
||||
selected_bg = colors[COL_MARK_BG];
|
||||
}
|
||||
|
||||
selection_fg_fallback = gdk_rgba_to_string (&selected_fg);
|
||||
selection_bg_fallback = gdk_rgba_to_string (&selected_bg);
|
||||
selection_fg_css = selection_fg_fallback ? selection_fg_fallback : "@theme_selected_fg_color";
|
||||
selection_bg_css = selection_bg_fallback ? selection_bg_fallback : "@theme_selected_bg_color";
|
||||
}
|
||||
|
||||
g_string_append_printf (
|
||||
css,
|
||||
"background-color: #%02x%02x%02x;"
|
||||
"color: #%02x%02x%02x;"
|
||||
"caret-color: %s;"
|
||||
"}"
|
||||
"#zoitechat-inputbox text {"
|
||||
"color: #%02x%02x%02x;"
|
||||
"caret-color: %s;"
|
||||
"}"
|
||||
"#zoitechat-inputbox:focus text,"
|
||||
"#zoitechat-inputbox:backdrop text {"
|
||||
"color: #%02x%02x%02x;"
|
||||
"caret-color: %s;"
|
||||
"}"
|
||||
"#zoitechat-inputbox:disabled text {"
|
||||
"color: alpha(#%02x%02x%02x, 0.7);"
|
||||
"}"
|
||||
"#zoitechat-inputbox text selection {"
|
||||
"color: %s;"
|
||||
"background-color: %s;"
|
||||
"}",
|
||||
(bg_red >> 8), (bg_green >> 8), (bg_blue >> 8),
|
||||
cursor_color,
|
||||
(fg_red >> 8), (fg_green >> 8), (fg_blue >> 8),
|
||||
cursor_color,
|
||||
(fg_red >> 8), (fg_green >> 8), (fg_blue >> 8),
|
||||
cursor_color);
|
||||
cursor_color,
|
||||
(fg_red >> 8), (fg_green >> 8), (fg_blue >> 8),
|
||||
selection_fg_css,
|
||||
selection_bg_css);
|
||||
if (tmp_entry)
|
||||
gtk_widget_destroy (tmp_entry);
|
||||
g_clear_pointer (&selection_fg_fallback, g_free);
|
||||
g_clear_pointer (&selection_bg_fallback, g_free);
|
||||
gtk_css_provider_load_from_data (input_css_provider, css->str, -1, NULL);
|
||||
g_string_free (css, TRUE);
|
||||
}
|
||||
@@ -841,6 +1345,12 @@ create_input_style (InputStyle *style)
|
||||
last_bg_red = bg_red;
|
||||
last_bg_green = bg_green;
|
||||
last_bg_blue = bg_blue;
|
||||
last_sel_fg_red = sel_fg_red;
|
||||
last_sel_fg_green = sel_fg_green;
|
||||
last_sel_fg_blue = sel_fg_blue;
|
||||
last_sel_bg_red = sel_bg_red;
|
||||
last_sel_bg_green = sel_bg_green;
|
||||
last_sel_bg_blue = sel_bg_blue;
|
||||
g_free (last_theme_name);
|
||||
last_theme_name = g_strdup (theme_name);
|
||||
}
|
||||
@@ -876,7 +1386,10 @@ fe_init (void)
|
||||
palette_load ();
|
||||
settings = gtk_settings_get_default ();
|
||||
if (settings)
|
||||
{
|
||||
auto_dark_mode_enabled = fe_system_prefers_dark ();
|
||||
dark_mode_state_initialized = TRUE;
|
||||
}
|
||||
|
||||
fe_apply_theme_for_mode (prefs.hex_gui_dark_mode, NULL);
|
||||
key_init ();
|
||||
@@ -1039,7 +1552,10 @@ fe_message (char *msg, int flags)
|
||||
type = GTK_MESSAGE_INFO;
|
||||
|
||||
dialog = gtk_message_dialog_new (GTK_WINDOW (parent_window), 0, type,
|
||||
GTK_BUTTONS_OK, "%s", msg);
|
||||
GTK_BUTTONS_OK, "%s", msg);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
if (flags & FE_MSG_MARKUP)
|
||||
gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), msg);
|
||||
g_signal_connect (G_OBJECT (dialog), "response",
|
||||
@@ -1440,6 +1956,9 @@ fe_ctrl_gui (session *sess, fe_gui_action action, int arg)
|
||||
mg_detach (sess, arg); /* arg: 0=toggle 1=detach 2=attach */
|
||||
break;
|
||||
case FE_GUI_APPLY:
|
||||
/* Keep parity with Preferences -> Theme apply path (setup_theme_apply_cb). */
|
||||
palette_load ();
|
||||
fe_apply_theme_for_mode (prefs.hex_gui_dark_mode, NULL);
|
||||
setup_apply_real (TRUE, TRUE, TRUE, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,9 +189,16 @@ extern cairo_surface_t *dialogwin_pix;
|
||||
|
||||
gboolean fe_dark_mode_is_enabled (void);
|
||||
gboolean fe_dark_mode_is_enabled_for (unsigned int mode);
|
||||
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);
|
||||
gboolean fe_apply_gtk3_theme_with_reload (const char *theme_name, gboolean force_reload,
|
||||
GError **error);
|
||||
gboolean fe_resolve_gtk3_theme_dir (const char *theme_root,
|
||||
char **gtk3_dir_out,
|
||||
gboolean *has_dark_css_out);
|
||||
void fe_apply_theme_to_toplevel (GtkWidget *window);
|
||||
|
||||
#define SPELL_ENTRY_GET_TEXT(e) ((char *)(gtk_entry_get_text (GTK_ENTRY(e))))
|
||||
|
||||
@@ -163,28 +163,6 @@ gtkutil_menu_custom_icon_from_icon_name (const char *icon_name)
|
||||
}
|
||||
|
||||
|
||||
static GdkPixbuf *
|
||||
gtkutil_menu_icon_pixbuf_new (const char *icon_name)
|
||||
{
|
||||
GdkPixbuf *pixbuf = NULL;
|
||||
char *resource_path;
|
||||
|
||||
if (!icon_name || !g_str_has_prefix (icon_name, "zc-menu-"))
|
||||
return NULL;
|
||||
|
||||
resource_path = g_strdup_printf ("/icons/menu/light/%s.png", icon_name + strlen ("zc-menu-"));
|
||||
pixbuf = gdk_pixbuf_new_from_resource (resource_path, NULL);
|
||||
if (!pixbuf)
|
||||
{
|
||||
g_free (resource_path);
|
||||
resource_path = g_strdup_printf ("/icons/menu/light/%s.svg", icon_name + strlen ("zc-menu-"));
|
||||
pixbuf = gdk_pixbuf_new_from_resource (resource_path, NULL);
|
||||
}
|
||||
g_free (resource_path);
|
||||
|
||||
return pixbuf;
|
||||
}
|
||||
|
||||
const char *
|
||||
gtkutil_icon_name_from_stock (const char *stock_name)
|
||||
{
|
||||
@@ -251,6 +229,11 @@ gtkutil_menu_icon_theme_variant (void)
|
||||
char *theme_name_lower = NULL;
|
||||
const char *theme_variant = "light";
|
||||
|
||||
/* Prefer ZoiteChat's explicit dark-mode selection when available so icon
|
||||
* variants stay in sync with the app mode, not only the system theme. */
|
||||
if (fe_dark_mode_state_is_initialized () || prefs.hex_gui_dark_mode != ZOITECHAT_DARK_MODE_AUTO)
|
||||
return fe_dark_mode_is_enabled () ? "dark" : "light";
|
||||
|
||||
settings = gtk_settings_get_default ();
|
||||
if (settings)
|
||||
{
|
||||
@@ -269,41 +252,68 @@ gtkutil_menu_icon_theme_variant (void)
|
||||
return theme_variant;
|
||||
}
|
||||
|
||||
static char *
|
||||
gtkutil_menu_icon_resource_path (const char *icon_name, const char *extension)
|
||||
{
|
||||
char *resource_path;
|
||||
const char *variant;
|
||||
|
||||
if (!icon_name || !extension || !g_str_has_prefix (icon_name, "zc-menu-"))
|
||||
return NULL;
|
||||
|
||||
variant = gtkutil_menu_icon_theme_variant ();
|
||||
resource_path = g_strdup_printf ("/icons/menu/%s/%s.%s", variant,
|
||||
icon_name + strlen ("zc-menu-"), extension);
|
||||
if (!g_resources_get_info (resource_path, G_RESOURCE_LOOKUP_FLAGS_NONE, NULL, NULL, NULL))
|
||||
{
|
||||
g_free (resource_path);
|
||||
resource_path = g_strdup_printf ("/icons/menu/light/%s.%s",
|
||||
icon_name + strlen ("zc-menu-"), extension);
|
||||
if (!g_resources_get_info (resource_path, G_RESOURCE_LOOKUP_FLAGS_NONE, NULL, NULL, NULL))
|
||||
{
|
||||
g_free (resource_path);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return resource_path;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gtkutil_menu_icon_exists (const char *icon_name)
|
||||
{
|
||||
char *resource_path;
|
||||
gboolean found;
|
||||
|
||||
resource_path = gtkutil_menu_icon_resource_path (icon_name, "png");
|
||||
if (!resource_path)
|
||||
resource_path = gtkutil_menu_icon_resource_path (icon_name, "svg");
|
||||
|
||||
found = resource_path != NULL;
|
||||
g_free (resource_path);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
gtkutil_menu_icon_image_new (const char *icon_name, GtkIconSize size)
|
||||
{
|
||||
GtkWidget *image = NULL;
|
||||
GdkPixbuf *pixbuf = NULL;
|
||||
char *resource_path;
|
||||
const char *variant;
|
||||
|
||||
if (!icon_name || !g_str_has_prefix (icon_name, "zc-menu-"))
|
||||
return NULL;
|
||||
resource_path = gtkutil_menu_icon_resource_path (icon_name, "png");
|
||||
if (!resource_path)
|
||||
resource_path = gtkutil_menu_icon_resource_path (icon_name, "svg");
|
||||
|
||||
variant = gtkutil_menu_icon_theme_variant ();
|
||||
resource_path = g_strdup_printf ("/icons/menu/%s/%s.png", variant, icon_name + strlen ("zc-menu-"));
|
||||
if (!g_resources_get_info (resource_path, G_RESOURCE_LOOKUP_FLAGS_NONE, NULL, NULL, NULL))
|
||||
if (resource_path)
|
||||
{
|
||||
g_free (resource_path);
|
||||
resource_path = g_strdup_printf ("/icons/menu/light/%s.png", icon_name + strlen ("zc-menu-"));
|
||||
}
|
||||
|
||||
pixbuf = gdk_pixbuf_new_from_resource (resource_path, NULL);
|
||||
if (!pixbuf)
|
||||
{
|
||||
g_free (resource_path);
|
||||
resource_path = g_strdup_printf ("/icons/menu/%s/%s.svg", variant, icon_name + strlen ("zc-menu-"));
|
||||
if (!g_resources_get_info (resource_path, G_RESOURCE_LOOKUP_FLAGS_NONE, NULL, NULL, NULL))
|
||||
{
|
||||
g_free (resource_path);
|
||||
resource_path = g_strdup_printf ("/icons/menu/light/%s.svg", icon_name + strlen ("zc-menu-"));
|
||||
}
|
||||
pixbuf = gdk_pixbuf_new_from_resource (resource_path, NULL);
|
||||
}
|
||||
if (pixbuf)
|
||||
{
|
||||
image = gtk_image_new_from_pixbuf (pixbuf);
|
||||
g_object_unref (pixbuf);
|
||||
if (pixbuf)
|
||||
{
|
||||
image = gtk_image_new_from_pixbuf (pixbuf);
|
||||
g_object_unref (pixbuf);
|
||||
}
|
||||
}
|
||||
|
||||
g_free (resource_path);
|
||||
@@ -327,20 +337,19 @@ gtkutil_image_new_from_stock (const char *stock, GtkIconSize size)
|
||||
{
|
||||
GtkWidget *image;
|
||||
const char *icon_name;
|
||||
const char *custom_icon_name;
|
||||
|
||||
icon_name = gtkutil_icon_name_from_stock (stock);
|
||||
if (!icon_name && stock && g_str_has_prefix (stock, "zc-menu-"))
|
||||
icon_name = stock;
|
||||
if (size == GTK_ICON_SIZE_MENU)
|
||||
{
|
||||
const char *menu_icon_name = gtkutil_menu_custom_icon_from_stock (stock);
|
||||
|
||||
if (!menu_icon_name)
|
||||
menu_icon_name = gtkutil_menu_custom_icon_from_icon_name (icon_name);
|
||||
|
||||
if (menu_icon_name)
|
||||
icon_name = menu_icon_name;
|
||||
}
|
||||
/* Use ZoiteChat's themed icon resources consistently across menu and button
|
||||
* images so dark/light mode swaps all app icons together. */
|
||||
custom_icon_name = gtkutil_menu_custom_icon_from_stock (stock);
|
||||
if (!custom_icon_name)
|
||||
custom_icon_name = gtkutil_menu_custom_icon_from_icon_name (icon_name);
|
||||
if (custom_icon_name)
|
||||
icon_name = custom_icon_name;
|
||||
|
||||
image = gtkutil_menu_icon_image_new (icon_name, size);
|
||||
if (image)
|
||||
@@ -803,21 +812,25 @@ gtkutil_file_req (GtkWindow *parent, const char *title, void *callback, void *us
|
||||
|
||||
if (flags & FRF_WRITE)
|
||||
{
|
||||
dialog = gtk_file_chooser_dialog_new (title, NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
||||
_("_Save"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
dialog = gtk_file_chooser_dialog_new (title, effective_parent,
|
||||
GTK_FILE_CHOOSER_ACTION_SAVE,
|
||||
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
||||
_("_Save"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
|
||||
if (!(flags & FRF_NOASKOVERWRITE))
|
||||
gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
|
||||
}
|
||||
else
|
||||
dialog = gtk_file_chooser_dialog_new (title, NULL,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
||||
_("_Open"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
dialog = gtk_file_chooser_dialog_new (title, effective_parent,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
||||
_("_Open"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
|
||||
if (filter && filter[0] && (flags & FRF_FILTERISINITIAL))
|
||||
{
|
||||
@@ -888,7 +901,8 @@ gtkutil_file_req (GtkWindow *parent, const char *title, void *callback, void *us
|
||||
g_signal_connect (G_OBJECT (dialog), "destroy",
|
||||
G_CALLBACK (gtkutil_file_req_destroy), (gpointer) freq);
|
||||
|
||||
if (effective_parent)
|
||||
if (effective_parent &&
|
||||
gtk_window_get_transient_for (GTK_WINDOW (dialog)) != effective_parent)
|
||||
gtk_window_set_transient_for (GTK_WINDOW (dialog), effective_parent);
|
||||
|
||||
if (flags & FRF_MODAL)
|
||||
@@ -977,6 +991,9 @@ fe_get_str (char *msg, char *def, void *callback, void *userdata)
|
||||
_("_Cancel"), GTK_RESPONSE_REJECT,
|
||||
_("_OK"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
|
||||
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
|
||||
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
|
||||
@@ -1072,6 +1089,9 @@ fe_get_int (char *msg, int def, void *callback, void *userdata)
|
||||
_("_Cancel"), GTK_RESPONSE_REJECT,
|
||||
_("_OK"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
|
||||
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
|
||||
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
|
||||
@@ -1112,6 +1132,9 @@ fe_get_bool (char *title, char *prompt, void *callback, void *userdata)
|
||||
_("_No"), GTK_RESPONSE_REJECT,
|
||||
_("_Yes"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE);
|
||||
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
|
||||
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
|
||||
@@ -1235,6 +1258,7 @@ gtkutil_window_new (char *title, char *role, int width, int height, int flags)
|
||||
gtk_window_set_title (GTK_WINDOW (win), title);
|
||||
gtk_window_set_default_size (GTK_WINDOW (win), width, height);
|
||||
gtk_window_set_role (GTK_WINDOW (win), role);
|
||||
fe_apply_theme_to_toplevel (win);
|
||||
if (flags & 1)
|
||||
gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_MOUSE);
|
||||
if ((flags & 2) && parent_window)
|
||||
|
||||
@@ -41,6 +41,7 @@ GtkWidget *gtkutil_button (GtkWidget *box, char *stock, char *tip, void *callbac
|
||||
void *userdata, char *labeltext);
|
||||
GtkWidget *gtkutil_image_new_from_stock (const char *stock, GtkIconSize size);
|
||||
GtkWidget *gtkutil_button_new_from_stock (const char *stock, const char *label);
|
||||
gboolean gtkutil_menu_icon_exists (const char *icon_name);
|
||||
const char *gtkutil_icon_name_from_stock (const char *stock_name);
|
||||
void gtkutil_label_new (char *text, GtkWidget * box);
|
||||
GtkWidget *gtkutil_entry_new (int max, GtkWidget * box, void *callback,
|
||||
|
||||
@@ -294,7 +294,10 @@ ignore_clear_entry_clicked (GtkWidget * wid)
|
||||
|
||||
dialog = gtk_message_dialog_new (NULL, 0,
|
||||
GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
|
||||
_("Are you sure you want to remove all ignores?"));
|
||||
_("Are you sure you want to remove all ignores?"));
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
g_signal_connect (G_OBJECT (dialog), "response",
|
||||
G_CALLBACK (ignore_clear_cb), NULL);
|
||||
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
|
||||
|
||||
@@ -129,6 +129,9 @@ joind_show_dialog (server *serv)
|
||||
char buf2[256];
|
||||
|
||||
serv->gui->joind_win = dialog1 = gtk_dialog_new ();
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog1);
|
||||
g_snprintf(buf, sizeof(buf), _("Connection Complete - %s"), _(DISPLAY_NAME));
|
||||
gtk_window_set_title (GTK_WINDOW (dialog1), buf);
|
||||
gtk_window_set_type_hint (GTK_WINDOW (dialog1), GDK_WINDOW_TYPE_HINT_DIALOG);
|
||||
|
||||
@@ -1362,6 +1362,9 @@ mg_tab_close (session *sess)
|
||||
GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL,
|
||||
_("This server still has %d channels or dialogs associated with it. "
|
||||
"Close them all?"), i);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
g_signal_connect (G_OBJECT (dialog), "response",
|
||||
G_CALLBACK (mg_tab_close_cb), sess);
|
||||
if (prefs.hex_gui_tab_layout)
|
||||
@@ -1459,6 +1462,9 @@ mg_open_quit_dialog (gboolean minimize_button)
|
||||
}
|
||||
|
||||
dialog = gtk_dialog_new ();
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
|
||||
gtk_window_set_title (GTK_WINDOW (dialog), _("Quit ZoiteChat?"));
|
||||
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
|
||||
@@ -1863,7 +1869,7 @@ mg_create_tabmenu (session *sess, GdkEventButton *event, chan *ch)
|
||||
if (sess)
|
||||
{
|
||||
char *name = g_markup_escape_text (sess->channel[0] ? sess->channel : _("<none>"), -1);
|
||||
g_snprintf (buf, sizeof (buf), "<span foreground=\"#3344cc\"><b>%s</b></span>", name);
|
||||
g_snprintf (buf, sizeof (buf), "<b>%s</b>", name);
|
||||
g_free (name);
|
||||
|
||||
item = gtk_menu_item_new_with_label ("");
|
||||
@@ -3681,6 +3687,8 @@ mg_create_topwindow (session *sess)
|
||||
mg_place_userlist_and_chanview (sess->gui);
|
||||
|
||||
gtk_widget_show (win);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (win);
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
@@ -3857,6 +3865,8 @@ mg_create_tabwindow (session *sess)
|
||||
mg_place_userlist_and_chanview (sess->gui);
|
||||
|
||||
gtk_widget_show (win);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (win);
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
@@ -3868,6 +3878,7 @@ mg_create_tabwindow (session *sess)
|
||||
void
|
||||
mg_apply_setup (void)
|
||||
{
|
||||
GList *toplevels, *node;
|
||||
GSList *list = sess_list;
|
||||
session *sess;
|
||||
int done_main = FALSE;
|
||||
@@ -3887,6 +3898,11 @@ mg_apply_setup (void)
|
||||
done_main = TRUE;
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static chan *
|
||||
|
||||
@@ -65,28 +65,6 @@
|
||||
|
||||
static GSList *submenu_list;
|
||||
|
||||
static gboolean
|
||||
menu_icon_exists_in_resource (const char *icon_name)
|
||||
{
|
||||
char *resource_path;
|
||||
gboolean found;
|
||||
|
||||
if (!icon_name || !g_str_has_prefix (icon_name, "zc-menu-"))
|
||||
return FALSE;
|
||||
|
||||
resource_path = g_strdup_printf ("/icons/menu/light/%s.png", icon_name + strlen ("zc-menu-"));
|
||||
found = g_resources_get_info (resource_path, G_RESOURCE_LOOKUP_FLAGS_NONE, NULL, NULL, NULL);
|
||||
if (!found)
|
||||
{
|
||||
g_free (resource_path);
|
||||
resource_path = g_strdup_printf ("/icons/menu/light/%s.svg", icon_name + strlen ("zc-menu-"));
|
||||
found = g_resources_get_info (resource_path, G_RESOURCE_LOOKUP_FLAGS_NONE, NULL, NULL, NULL);
|
||||
}
|
||||
g_free (resource_path);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
menu_icon_widget_new (const char *icon)
|
||||
{
|
||||
@@ -112,7 +90,7 @@ menu_icon_widget_new (const char *icon)
|
||||
{
|
||||
char *menu_icon_name = g_strdup_printf ("zc-menu-%s", icon);
|
||||
|
||||
if (menu_icon_exists_in_resource (menu_icon_name))
|
||||
if (gtkutil_menu_icon_exists (menu_icon_name))
|
||||
img = gtkutil_image_new_from_stock (menu_icon_name, GTK_ICON_SIZE_MENU);
|
||||
else
|
||||
img = gtkutil_image_new_from_stock (icon, GTK_ICON_SIZE_MENU);
|
||||
@@ -1520,6 +1498,9 @@ menu_join (GtkWidget * wid, gpointer none)
|
||||
_("_Cancel"), GTK_RESPONSE_REJECT,
|
||||
_("_OK"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
{
|
||||
GtkWidget *button;
|
||||
|
||||
@@ -1856,6 +1837,9 @@ static void
|
||||
menu_about (GtkWidget *wid, gpointer sess)
|
||||
{
|
||||
GtkAboutDialog *dialog = GTK_ABOUT_DIALOG(gtk_about_dialog_new());
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark/.zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (GTK_WIDGET (dialog));
|
||||
char comment[512];
|
||||
char *license = "This program is free software; you can redistribute it and/or modify\n" \
|
||||
"it under the terms of the GNU General Public License as published by\n" \
|
||||
|
||||
@@ -373,6 +373,9 @@ fe_notify_ask (char *nick, char *networks)
|
||||
LABEL_NOTIFY_CANCEL, GTK_RESPONSE_REJECT,
|
||||
LABEL_NOTIFY_OK, GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
if (parent_window)
|
||||
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
|
||||
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
|
||||
|
||||
@@ -224,6 +224,27 @@ palette_dark_set_color (int idx, const PaletteColor *col)
|
||||
dark_user_colors[idx] = *col;
|
||||
}
|
||||
|
||||
const PaletteColor *
|
||||
palette_user_colors (void)
|
||||
{
|
||||
if (!user_colors_valid)
|
||||
{
|
||||
memcpy (user_colors, colors, sizeof (user_colors));
|
||||
user_colors_valid = TRUE;
|
||||
}
|
||||
|
||||
return user_colors;
|
||||
}
|
||||
|
||||
const PaletteColor *
|
||||
palette_dark_colors (void)
|
||||
{
|
||||
if (!dark_user_colors_valid)
|
||||
return dark_colors;
|
||||
|
||||
return dark_user_colors;
|
||||
}
|
||||
|
||||
void
|
||||
palette_alloc (GtkWidget * widget)
|
||||
{
|
||||
|
||||
@@ -57,6 +57,8 @@ void palette_save (void);
|
||||
/* Keep a copy of the user's palette so dark mode can be toggled without losing it. */
|
||||
void palette_user_set_color (int idx, const PaletteColor *col);
|
||||
void palette_dark_set_color (int idx, const PaletteColor *col);
|
||||
const PaletteColor *palette_user_colors (void);
|
||||
const PaletteColor *palette_dark_colors (void);
|
||||
|
||||
/*
|
||||
* Apply ZoiteChat's built-in "dark mode" palette.
|
||||
|
||||
@@ -781,12 +781,15 @@ servlist_deletenet_cb (GtkWidget *item, ircnet *net)
|
||||
if (!net)
|
||||
return;
|
||||
dialog = gtk_message_dialog_new (GTK_WINDOW (serverlist_win),
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT |
|
||||
GTK_DIALOG_MODAL,
|
||||
GTK_MESSAGE_QUESTION,
|
||||
GTK_BUTTONS_OK_CANCEL,
|
||||
_("Really remove network \"%s\" and all its servers?"),
|
||||
net->name);
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT |
|
||||
GTK_DIALOG_MODAL,
|
||||
GTK_MESSAGE_QUESTION,
|
||||
GTK_BUTTONS_OK_CANCEL,
|
||||
_("Really remove network \"%s\" and all its servers?"),
|
||||
net->name);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
g_signal_connect (dialog, "response",
|
||||
G_CALLBACK (servlist_deletenetdialog_cb), net);
|
||||
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
|
||||
@@ -1799,6 +1802,9 @@ servlist_open_edit (GtkWidget *parent, ircnet *net)
|
||||
gtk_window_set_modal (GTK_WINDOW (editwindow), TRUE);
|
||||
gtk_window_set_type_hint (GTK_WINDOW (editwindow), GDK_WINDOW_TYPE_HINT_DIALOG);
|
||||
gtk_window_set_role (GTK_WINDOW (editwindow), "editserv");
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (editwindow);
|
||||
|
||||
vbox5 = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
|
||||
gtk_container_add (GTK_CONTAINER (editwindow), vbox5);
|
||||
@@ -2078,6 +2084,9 @@ servlist_open_networks (void)
|
||||
gtk_window_set_default_size (GTK_WINDOW (servlist), netlist_win_width, netlist_win_height);
|
||||
gtk_window_set_role (GTK_WINDOW (servlist), "servlist");
|
||||
gtk_window_set_type_hint (GTK_WINDOW (servlist), GDK_WINDOW_TYPE_HINT_DIALOG);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (servlist);
|
||||
if (current_sess)
|
||||
gtk_window_set_transient_for (GTK_WINDOW (servlist), GTK_WINDOW (current_sess->gui->window));
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ static GtkWidget *setup_window = NULL;
|
||||
static int last_selected_page = 0;
|
||||
static int last_selected_row = 0; /* sound row */
|
||||
static gboolean color_change;
|
||||
static gboolean setup_color_edit_dark_palette;
|
||||
static const PaletteColor *setup_color_edit_source_colors;
|
||||
static struct zoitechatprefs setup_prefs;
|
||||
static GSList *color_selector_widgets;
|
||||
static GtkWidget *cancel_button;
|
||||
@@ -64,11 +66,30 @@ void setup_apply_real (int new_pix, int do_ulist, int do_layout, int do_identd);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GtkWidget *combo;
|
||||
GtkWidget *apply_button;
|
||||
GtkWidget *status_label;
|
||||
GtkWidget *gtk3_combo;
|
||||
GtkWidget *gtk3_import_button;
|
||||
GtkWidget *gtk3_apply_button;
|
||||
GtkWidget *gtk3_use_system_button;
|
||||
GtkWidget *gtk3_status_label;
|
||||
GPtrArray *gtk3_theme_paths;
|
||||
gboolean gtk3_force_reload_next_apply;
|
||||
} setup_theme_ui;
|
||||
|
||||
|
||||
static void
|
||||
setup_theme_ui_free (gpointer data)
|
||||
{
|
||||
setup_theme_ui *ui = data;
|
||||
|
||||
if (!ui)
|
||||
return;
|
||||
|
||||
if (ui->gtk3_theme_paths)
|
||||
g_ptr_array_free (ui->gtk3_theme_paths, TRUE);
|
||||
|
||||
g_free (ui);
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
ST_END,
|
||||
@@ -372,6 +393,13 @@ static const setting dark_mode_setting =
|
||||
0
|
||||
};
|
||||
|
||||
static const char *const color_edit_target_modes[] =
|
||||
{
|
||||
N_("Light"),
|
||||
N_("Dark"),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const dccaccept[] =
|
||||
{
|
||||
N_("Ask for confirmation"),
|
||||
@@ -1233,6 +1261,9 @@ setup_browsefont_cb (GtkWidget *button, GtkWidget *entry)
|
||||
const char *font_name;
|
||||
|
||||
dialog = gtk_font_chooser_dialog_new (_("Select font"), GTK_WINDOW (setup_window));
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark/.zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
font_dialog = dialog; /* global var */
|
||||
|
||||
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
|
||||
@@ -1474,6 +1505,9 @@ setup_create_page (const setting *set)
|
||||
return tab;
|
||||
}
|
||||
|
||||
static void
|
||||
setup_color_button_apply (GtkWidget *button, const PaletteColor *color);
|
||||
|
||||
static void
|
||||
setup_color_selectors_set_sensitive (gboolean sensitive)
|
||||
{
|
||||
@@ -1487,14 +1521,98 @@ setup_color_selectors_set_sensitive (gboolean sensitive)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
setup_color_update_source_palette (void)
|
||||
{
|
||||
setup_color_edit_source_colors = setup_color_edit_dark_palette
|
||||
? palette_dark_colors ()
|
||||
: palette_user_colors ();
|
||||
}
|
||||
|
||||
static void
|
||||
setup_refresh_color_selector_widgets (void)
|
||||
{
|
||||
GSList *l = color_selector_widgets;
|
||||
|
||||
while (l)
|
||||
{
|
||||
GtkWidget *w = (GtkWidget *) l->data;
|
||||
gpointer color_index_ptr;
|
||||
int color_index;
|
||||
|
||||
if (!GTK_IS_WIDGET (w))
|
||||
{
|
||||
l = l->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
color_index_ptr = g_object_get_data (G_OBJECT (w), "zoitechat-color-index");
|
||||
if (!color_index_ptr)
|
||||
{
|
||||
l = l->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
color_index = GPOINTER_TO_INT (color_index_ptr);
|
||||
if (setup_color_edit_source_colors && color_index >= 0 && color_index <= MAX_COL)
|
||||
setup_color_button_apply (w, &setup_color_edit_source_colors[color_index]);
|
||||
|
||||
l = l->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
setup_dark_mode_menu_cb (GtkWidget *cbox, const setting *set)
|
||||
{
|
||||
setup_menu_cb (cbox, set);
|
||||
setup_color_update_source_palette ();
|
||||
setup_refresh_color_selector_widgets ();
|
||||
/* Keep color selectors usable even when dark mode is enabled. */
|
||||
setup_color_selectors_set_sensitive (TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_color_edit_target_menu_cb (GtkComboBox *cbox, gpointer userdata)
|
||||
{
|
||||
(void) userdata;
|
||||
|
||||
setup_color_edit_dark_palette = gtk_combo_box_get_active (cbox) == 1;
|
||||
setup_color_update_source_palette ();
|
||||
setup_refresh_color_selector_widgets ();
|
||||
setup_color_selectors_set_sensitive (TRUE);
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
setup_create_color_edit_target_menu (GtkWidget *table, int row)
|
||||
{
|
||||
GtkWidget *wid;
|
||||
GtkWidget *cbox;
|
||||
GtkWidget *box;
|
||||
int i;
|
||||
|
||||
wid = gtk_label_new (_("Editing:"));
|
||||
gtk_widget_set_halign (wid, GTK_ALIGN_START);
|
||||
gtk_widget_set_valign (wid, GTK_ALIGN_CENTER);
|
||||
setup_table_attach (table, wid, 2, 3, row, row + 1, FALSE, FALSE,
|
||||
SETUP_ALIGN_START, SETUP_ALIGN_CENTER,
|
||||
LABEL_INDENT, 0);
|
||||
|
||||
cbox = gtk_combo_box_text_new ();
|
||||
for (i = 0; color_edit_target_modes[i]; i++)
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (cbox), _(color_edit_target_modes[i]));
|
||||
|
||||
gtk_combo_box_set_active (GTK_COMBO_BOX (cbox), setup_color_edit_dark_palette ? 1 : 0);
|
||||
g_signal_connect (G_OBJECT (cbox), "changed",
|
||||
G_CALLBACK (setup_color_edit_target_menu_cb), NULL);
|
||||
|
||||
box = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
|
||||
gtk_box_pack_start (GTK_BOX (box), cbox, 0, 0, 0);
|
||||
setup_table_attach (table, box, 3, 4, row, row + 1, TRUE, FALSE,
|
||||
SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);
|
||||
|
||||
return cbox;
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
setup_create_dark_mode_menu (GtkWidget *table, int row, const setting *set)
|
||||
{
|
||||
@@ -1543,8 +1661,7 @@ setup_color_button_apply (GtkWidget *button, const PaletteColor *color)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GtkWidget *button;
|
||||
PaletteColor *color;
|
||||
int color_index;
|
||||
} setup_color_dialog_data;
|
||||
|
||||
static void
|
||||
@@ -1575,14 +1692,15 @@ setup_color_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data
|
||||
GdkRGBA rgba;
|
||||
|
||||
gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (dialog), &rgba);
|
||||
*data->color = rgba;
|
||||
color_change = TRUE;
|
||||
setup_color_button_apply (data->button, data->color);
|
||||
|
||||
if (fe_dark_mode_is_enabled_for (setup_prefs.hex_gui_dark_mode))
|
||||
palette_dark_set_color ((int)(data->color - colors), data->color);
|
||||
if (setup_color_edit_dark_palette)
|
||||
palette_dark_set_color (data->color_index, &rgba);
|
||||
else
|
||||
palette_user_set_color ((int)(data->color - colors), data->color);
|
||||
palette_user_set_color (data->color_index, &rgba);
|
||||
|
||||
color_change = TRUE;
|
||||
setup_color_update_source_palette ();
|
||||
setup_refresh_color_selector_widgets ();
|
||||
}
|
||||
|
||||
gtk_widget_destroy (GTK_WIDGET (dialog));
|
||||
@@ -1593,20 +1711,26 @@ static void
|
||||
setup_color_cb (GtkWidget *button, gpointer userdata)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
PaletteColor *color;
|
||||
int color_index;
|
||||
GdkRGBA rgba;
|
||||
setup_color_dialog_data *data;
|
||||
|
||||
color = &colors[GPOINTER_TO_INT (userdata)];
|
||||
(void) button;
|
||||
color_index = GPOINTER_TO_INT (userdata);
|
||||
|
||||
dialog = gtk_color_chooser_dialog_new (_("Select color"), GTK_WINDOW (setup_window));
|
||||
setup_rgba_from_palette (color, &rgba);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark/.zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
if (setup_color_edit_source_colors && color_index >= 0 && color_index <= MAX_COL)
|
||||
setup_rgba_from_palette (&setup_color_edit_source_colors[color_index], &rgba);
|
||||
else
|
||||
setup_rgba_from_palette (&colors[color_index], &rgba);
|
||||
gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (dialog), &rgba);
|
||||
gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
|
||||
|
||||
data = g_new0 (setup_color_dialog_data, 1);
|
||||
data->button = button;
|
||||
data->color = color;
|
||||
data->color_index = color_index;
|
||||
g_signal_connect (dialog, "response", G_CALLBACK (setup_color_response_cb), data);
|
||||
gtk_widget_show (dialog);
|
||||
}
|
||||
@@ -1640,11 +1764,15 @@ setup_create_color_button (GtkWidget *table, int num, int row, int col)
|
||||
/* win32 build uses this to turn off themeing */
|
||||
g_object_set_data (G_OBJECT (but), "zoitechat-color", (gpointer)1);
|
||||
g_object_set_data (G_OBJECT (but), "zoitechat-color-box", box);
|
||||
g_object_set_data (G_OBJECT (but), "zoitechat-color-index", GINT_TO_POINTER (num));
|
||||
setup_table_attach (table, but, col, col + 1, row, row + 1, FALSE, FALSE,
|
||||
SETUP_ALIGN_CENTER, SETUP_ALIGN_CENTER, 0, 0);
|
||||
g_signal_connect (G_OBJECT (but), "clicked",
|
||||
G_CALLBACK (setup_color_cb), GINT_TO_POINTER (num));
|
||||
setup_color_button_apply (but, &colors[num]);
|
||||
if (setup_color_edit_source_colors)
|
||||
setup_color_button_apply (but, &setup_color_edit_source_colors[num]);
|
||||
else
|
||||
setup_color_button_apply (but, &colors[num]);
|
||||
|
||||
/* Track all color selector widgets (used for dark mode UI behavior). */
|
||||
color_selector_widgets = g_slist_prepend (color_selector_widgets, but);
|
||||
@@ -1682,6 +1810,8 @@ static GtkWidget *
|
||||
setup_create_color_page (void)
|
||||
{
|
||||
color_selector_widgets = NULL;
|
||||
setup_color_edit_dark_palette = setup_prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_DARK;
|
||||
setup_color_update_source_palette ();
|
||||
|
||||
GtkWidget *tab, *box, *label;
|
||||
int i;
|
||||
@@ -1734,8 +1864,10 @@ setup_create_color_page (void)
|
||||
setup_create_other_color (_("Highlight:"), COL_HILIGHT, 11, tab);
|
||||
setup_create_other_colorR (_("Spell checker:"), COL_SPELL, 11, tab);
|
||||
setup_create_dark_mode_menu (tab, 13, &dark_mode_setting);
|
||||
setup_create_color_edit_target_menu (tab, 14);
|
||||
setup_refresh_color_selector_widgets ();
|
||||
setup_color_selectors_set_sensitive (TRUE);
|
||||
setup_create_header (tab, 15, N_("Color Stripping"));
|
||||
setup_create_header (tab, 16, N_("Color Stripping"));
|
||||
|
||||
/* label = gtk_label_new (_("Strip colors from:"));
|
||||
gtk_widget_set_halign (label, GTK_ALIGN_START);
|
||||
@@ -1746,7 +1878,7 @@ setup_create_color_page (void)
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
setup_create_toggleL (tab, i + 16, &color_settings[i]);
|
||||
setup_create_toggleL (tab, i + 17, &color_settings[i]);
|
||||
}
|
||||
|
||||
return box;
|
||||
@@ -1759,121 +1891,230 @@ setup_theme_show_message (GtkMessageType message_type, const char *primary)
|
||||
|
||||
dialog = gtk_message_dialog_new (GTK_WINDOW (setup_window), GTK_DIALOG_MODAL,
|
||||
message_type, GTK_BUTTONS_CLOSE, "%s", primary);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
gtk_dialog_run (GTK_DIALOG (dialog));
|
||||
gtk_widget_destroy (dialog);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_theme_populate (setup_theme_ui *ui)
|
||||
setup_gtk3_theme_populate (setup_theme_ui *ui)
|
||||
{
|
||||
char *themes_dir;
|
||||
GDir *dir;
|
||||
const char *name;
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter iter;
|
||||
int count;
|
||||
char *themes_dir;
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter iter;
|
||||
GDir *dir;
|
||||
const char *name;
|
||||
gboolean have_valid_theme = FALSE;
|
||||
gint active = -1;
|
||||
guint i;
|
||||
|
||||
model = gtk_combo_box_get_model (GTK_COMBO_BOX (ui->combo));
|
||||
while (gtk_tree_model_get_iter_first (model, &iter))
|
||||
gtk_combo_box_text_remove (GTK_COMBO_BOX_TEXT (ui->combo), 0);
|
||||
themes_dir = g_build_filename (get_xdir (), "gtk3-themes", NULL);
|
||||
g_mkdir_with_parents (themes_dir, 0700);
|
||||
|
||||
themes_dir = g_build_filename (get_xdir (), "themes", NULL);
|
||||
if (!g_file_test (themes_dir, G_FILE_TEST_IS_DIR))
|
||||
g_mkdir_with_parents (themes_dir, 0700);
|
||||
model = gtk_combo_box_get_model (GTK_COMBO_BOX (ui->gtk3_combo));
|
||||
while (gtk_tree_model_get_iter_first (model, &iter))
|
||||
gtk_combo_box_text_remove (GTK_COMBO_BOX_TEXT (ui->gtk3_combo), 0);
|
||||
|
||||
dir = g_dir_open (themes_dir, 0, NULL);
|
||||
if (dir)
|
||||
{
|
||||
while ((name = g_dir_read_name (dir)))
|
||||
{
|
||||
char *path = g_build_filename (themes_dir, name, NULL);
|
||||
if (g_file_test (path, G_FILE_TEST_IS_DIR))
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (ui->combo), name);
|
||||
g_free (path);
|
||||
}
|
||||
g_dir_close (dir);
|
||||
}
|
||||
if (!ui->gtk3_theme_paths)
|
||||
ui->gtk3_theme_paths = g_ptr_array_new_with_free_func (g_free);
|
||||
else
|
||||
g_ptr_array_set_size (ui->gtk3_theme_paths, 0);
|
||||
|
||||
count = gtk_tree_model_iter_n_children (gtk_combo_box_get_model (GTK_COMBO_BOX (ui->combo)), NULL);
|
||||
if (count > 0)
|
||||
gtk_combo_box_set_active (GTK_COMBO_BOX (ui->combo), 0);
|
||||
dir = g_dir_open (themes_dir, 0, NULL);
|
||||
if (dir)
|
||||
{
|
||||
while ((name = g_dir_read_name (dir)))
|
||||
{
|
||||
char *theme_path;
|
||||
char *gtk3_dir = NULL;
|
||||
|
||||
gtk_widget_set_sensitive (ui->apply_button, count > 0);
|
||||
gtk_label_set_text (GTK_LABEL (ui->status_label),
|
||||
count > 0 ? _("Select a theme to apply.") : _("No themes found."));
|
||||
theme_path = g_build_filename (themes_dir, name, NULL);
|
||||
|
||||
g_free (themes_dir);
|
||||
if (g_file_test (theme_path, G_FILE_TEST_IS_DIR)
|
||||
&& fe_resolve_gtk3_theme_dir (theme_path, >k3_dir, NULL))
|
||||
{
|
||||
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (ui->gtk3_combo), name);
|
||||
g_ptr_array_add (ui->gtk3_theme_paths, theme_path);
|
||||
have_valid_theme = TRUE;
|
||||
theme_path = NULL;
|
||||
}
|
||||
|
||||
g_free (gtk3_dir);
|
||||
g_free (theme_path);
|
||||
}
|
||||
|
||||
g_dir_close (dir);
|
||||
}
|
||||
|
||||
if (!have_valid_theme)
|
||||
{
|
||||
gtk_label_set_text (GTK_LABEL (ui->gtk3_status_label), _("No valid GTK3 themes found."));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < ui->gtk3_theme_paths->len; i++)
|
||||
{
|
||||
const char *theme_path = g_ptr_array_index (ui->gtk3_theme_paths, i);
|
||||
char *theme_name = g_path_get_basename (theme_path);
|
||||
|
||||
if (g_strcmp0 (prefs.hex_gui_gtk3_theme_name, theme_name) == 0)
|
||||
{
|
||||
active = (gint) i;
|
||||
g_free (theme_name);
|
||||
break;
|
||||
}
|
||||
|
||||
g_free (theme_name);
|
||||
}
|
||||
|
||||
if (active >= 0)
|
||||
gtk_combo_box_set_active (GTK_COMBO_BOX (ui->gtk3_combo), active);
|
||||
else if (prefs.hex_gui_gtk3_theme_name[0] == '\0')
|
||||
gtk_combo_box_set_active (GTK_COMBO_BOX (ui->gtk3_combo), 0);
|
||||
else
|
||||
gtk_combo_box_set_active (GTK_COMBO_BOX (ui->gtk3_combo), -1);
|
||||
|
||||
if (gtk_combo_box_get_active (GTK_COMBO_BOX (ui->gtk3_combo)) >= 0)
|
||||
gtk_label_set_text (GTK_LABEL (ui->gtk3_status_label), _("Select a GTK3 theme to apply."));
|
||||
else
|
||||
gtk_label_set_text (GTK_LABEL (ui->gtk3_status_label), _("No valid GTK3 themes found."));
|
||||
}
|
||||
|
||||
gtk_widget_set_sensitive (ui->gtk3_apply_button,
|
||||
gtk_combo_box_get_active (GTK_COMBO_BOX (ui->gtk3_combo)) >= 0);
|
||||
|
||||
g_free (themes_dir);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_theme_refresh_cb (GtkWidget *button, gpointer user_data)
|
||||
setup_theme_gtk3_selection_changed (GtkComboBox *combo, gpointer user_data)
|
||||
{
|
||||
setup_theme_ui *ui = user_data;
|
||||
setup_theme_ui *ui = user_data;
|
||||
gboolean has_selection = gtk_combo_box_get_active (combo) >= 0;
|
||||
|
||||
setup_theme_populate (ui);
|
||||
gtk_widget_set_sensitive (ui->gtk3_apply_button, has_selection);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_theme_open_folder_cb (GtkWidget *button, gpointer user_data)
|
||||
setup_theme_gtk3_import_cb (GtkWidget *button, gpointer user_data)
|
||||
{
|
||||
char *themes_dir;
|
||||
setup_theme_ui *ui = user_data;
|
||||
GtkWidget *dialog;
|
||||
GtkFileFilter *filter;
|
||||
gint response;
|
||||
char *archive_path;
|
||||
GError *error = NULL;
|
||||
|
||||
themes_dir = g_build_filename (get_xdir (), "themes", NULL);
|
||||
g_mkdir_with_parents (themes_dir, 0700);
|
||||
fe_open_url (themes_dir);
|
||||
g_free (themes_dir);
|
||||
dialog = gtk_file_chooser_dialog_new (_("Import GTK3 Theme Archive"), GTK_WINDOW (setup_window),
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
_ ("_Cancel"), GTK_RESPONSE_CANCEL,
|
||||
_ ("_Open"), GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
/* Window classes are required for GTK CSS selectors like
|
||||
* .zoitechat-dark / .zoitechat-light. */
|
||||
fe_apply_theme_to_toplevel (dialog);
|
||||
|
||||
filter = gtk_file_filter_new ();
|
||||
gtk_file_filter_set_name (filter, _("Theme archives (.zip, .tar.xz, .tar.gz, .tar)"));
|
||||
gtk_file_filter_add_pattern (filter, "*.zip");
|
||||
gtk_file_filter_add_pattern (filter, "*.ZIP");
|
||||
gtk_file_filter_add_pattern (filter, "*.tar");
|
||||
gtk_file_filter_add_pattern (filter, "*.tar.gz");
|
||||
gtk_file_filter_add_pattern (filter, "*.tgz");
|
||||
gtk_file_filter_add_pattern (filter, "*.tar.xz");
|
||||
gtk_file_filter_add_pattern (filter, "*.txz");
|
||||
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
|
||||
gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
|
||||
|
||||
response = gtk_dialog_run (GTK_DIALOG (dialog));
|
||||
if (response != GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
gtk_widget_destroy (dialog);
|
||||
return;
|
||||
}
|
||||
|
||||
archive_path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
|
||||
gtk_widget_destroy (dialog);
|
||||
|
||||
if (!archive_path)
|
||||
return;
|
||||
|
||||
if (!zoitechat_import_gtk3_theme_archive (archive_path, NULL, &error))
|
||||
{
|
||||
setup_theme_show_message (GTK_MESSAGE_ERROR,
|
||||
error ? error->message : _("Failed to import GTK3 theme archive."));
|
||||
g_clear_error (&error);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->gtk3_force_reload_next_apply = TRUE;
|
||||
setup_gtk3_theme_populate (ui);
|
||||
gtk_label_set_text (GTK_LABEL (ui->gtk3_status_label), _("GTK3 theme archive imported successfully."));
|
||||
setup_theme_show_message (GTK_MESSAGE_INFO, _("GTK3 theme archive imported successfully."));
|
||||
}
|
||||
|
||||
g_free (archive_path);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
setup_theme_apply_gtk3_cb (GtkWidget *button, gpointer user_data)
|
||||
{
|
||||
setup_theme_ui *ui = user_data;
|
||||
gint active;
|
||||
const char *theme_path;
|
||||
char *theme;
|
||||
GError *error = NULL;
|
||||
|
||||
active = gtk_combo_box_get_active (GTK_COMBO_BOX (ui->gtk3_combo));
|
||||
if (active < 0 || !ui->gtk3_theme_paths || (guint) active >= ui->gtk3_theme_paths->len)
|
||||
return;
|
||||
|
||||
theme_path = g_ptr_array_index (ui->gtk3_theme_paths, active);
|
||||
theme = g_path_get_basename (theme_path);
|
||||
if (!theme || !*theme)
|
||||
{
|
||||
g_free (theme);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fe_apply_gtk3_theme_with_reload (theme, ui->gtk3_force_reload_next_apply, &error))
|
||||
{
|
||||
setup_theme_show_message (GTK_MESSAGE_ERROR,
|
||||
error ? error->message : _("Failed to apply GTK3 theme."));
|
||||
g_clear_error (&error);
|
||||
g_free (theme);
|
||||
return;
|
||||
}
|
||||
|
||||
ui->gtk3_force_reload_next_apply = FALSE;
|
||||
|
||||
safe_strcpy (prefs.hex_gui_gtk3_theme_name, theme, sizeof (prefs.hex_gui_gtk3_theme_name));
|
||||
/* Keep the Preferences working copy in sync so pressing OK does not
|
||||
* overwrite the just-selected theme with stale setup_prefs data. */
|
||||
safe_strcpy (setup_prefs.hex_gui_gtk3_theme_name, theme,
|
||||
sizeof (setup_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_selection_changed (GtkComboBox *combo, gpointer user_data)
|
||||
setup_theme_gtk3_use_system_cb (GtkWidget *button, gpointer user_data)
|
||||
{
|
||||
setup_theme_ui *ui = user_data;
|
||||
gboolean has_selection = gtk_combo_box_get_active (combo) >= 0;
|
||||
setup_theme_ui *ui = user_data;
|
||||
|
||||
gtk_widget_set_sensitive (ui->apply_button, has_selection);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_theme_apply_cb (GtkWidget *button, gpointer user_data)
|
||||
{
|
||||
setup_theme_ui *ui = user_data;
|
||||
GtkWidget *dialog;
|
||||
gint response;
|
||||
char *theme;
|
||||
GError *error = NULL;
|
||||
|
||||
theme = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (ui->combo));
|
||||
if (!theme)
|
||||
return;
|
||||
|
||||
dialog = gtk_message_dialog_new (GTK_WINDOW (setup_window), GTK_DIALOG_MODAL,
|
||||
GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL,
|
||||
"%s", _("Applying a theme will overwrite your current colors and event settings.\nContinue?"));
|
||||
response = gtk_dialog_run (GTK_DIALOG (dialog));
|
||||
gtk_widget_destroy (dialog);
|
||||
|
||||
if (response != GTK_RESPONSE_OK)
|
||||
{
|
||||
g_free (theme);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!zoitechat_apply_theme (theme, &error))
|
||||
{
|
||||
setup_theme_show_message (GTK_MESSAGE_ERROR, error ? error->message : _("Failed to apply theme."));
|
||||
g_clear_error (&error);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
palette_load ();
|
||||
palette_apply_dark_mode (fe_dark_mode_is_enabled ());
|
||||
color_change = TRUE;
|
||||
setup_apply_real (0, TRUE, FALSE, FALSE);
|
||||
|
||||
setup_theme_show_message (GTK_MESSAGE_INFO, _("Theme applied. Some changes may require a restart to take full effect."));
|
||||
|
||||
cleanup:
|
||||
g_free (theme);
|
||||
fe_apply_gtk3_theme (NULL, NULL);
|
||||
ui->gtk3_force_reload_next_apply = FALSE;
|
||||
prefs.hex_gui_gtk3_theme_name[0] = '\0';
|
||||
setup_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 *
|
||||
@@ -1881,62 +2122,59 @@ setup_create_theme_page (void)
|
||||
{
|
||||
setup_theme_ui *ui;
|
||||
GtkWidget *box;
|
||||
GtkWidget *label;
|
||||
GtkWidget *hbox;
|
||||
GtkWidget *button_box;
|
||||
char *themes_dir;
|
||||
char *markup;
|
||||
GtkWidget *label;
|
||||
GtkWidget *hbox;
|
||||
GtkWidget *button_box;
|
||||
GtkWidget *frame;
|
||||
|
||||
ui = g_new0 (setup_theme_ui, 1);
|
||||
|
||||
box = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 6);
|
||||
gtk_container_set_border_width (GTK_CONTAINER (box), 6);
|
||||
|
||||
themes_dir = g_build_filename (get_xdir (), "themes", NULL);
|
||||
markup = g_markup_printf_escaped (_("Theme files are loaded from <tt>%s</tt>."), themes_dir);
|
||||
label = gtk_label_new (NULL);
|
||||
gtk_label_set_markup (GTK_LABEL (label), markup);
|
||||
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
|
||||
gtk_widget_set_halign (label, GTK_ALIGN_START);
|
||||
gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
|
||||
gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
|
||||
g_free (markup);
|
||||
g_free (themes_dir);
|
||||
frame = gtk_frame_new (_("GTK3 Theme"));
|
||||
gtk_box_pack_start (GTK_BOX (box), frame, FALSE, FALSE, 0);
|
||||
|
||||
hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
|
||||
gtk_box_pack_start (GTK_BOX (box), hbox, FALSE, FALSE, 0);
|
||||
hbox = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 6);
|
||||
gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
|
||||
gtk_container_add (GTK_CONTAINER (frame), hbox);
|
||||
|
||||
ui->combo = gtk_combo_box_text_new ();
|
||||
gtk_box_pack_start (GTK_BOX (hbox), ui->combo, TRUE, TRUE, 0);
|
||||
g_signal_connect (G_OBJECT (ui->combo), "changed",
|
||||
G_CALLBACK (setup_theme_selection_changed), ui);
|
||||
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);
|
||||
|
||||
button_box = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), button_box, FALSE, FALSE, 0);
|
||||
button_box = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), button_box, FALSE, FALSE, 0);
|
||||
|
||||
ui->apply_button = gtk_button_new_with_mnemonic (_("_Apply Theme"));
|
||||
gtk_box_pack_start (GTK_BOX (button_box), ui->apply_button, FALSE, FALSE, 0);
|
||||
g_signal_connect (G_OBJECT (ui->apply_button), "clicked",
|
||||
G_CALLBACK (setup_theme_apply_cb), ui);
|
||||
ui->gtk3_combo = gtk_combo_box_text_new ();
|
||||
gtk_box_pack_start (GTK_BOX (button_box), ui->gtk3_combo, TRUE, TRUE, 0);
|
||||
g_signal_connect (G_OBJECT (ui->gtk3_combo), "changed",
|
||||
G_CALLBACK (setup_theme_gtk3_selection_changed), ui);
|
||||
|
||||
label = gtk_button_new_with_mnemonic (_("_Refresh"));
|
||||
gtk_box_pack_start (GTK_BOX (button_box), label, FALSE, FALSE, 0);
|
||||
g_signal_connect (G_OBJECT (label), "clicked",
|
||||
G_CALLBACK (setup_theme_refresh_cb), ui);
|
||||
ui->gtk3_import_button = gtk_button_new_with_mnemonic (_("_Import GTK3 Theme Archive"));
|
||||
gtk_box_pack_start (GTK_BOX (button_box), ui->gtk3_import_button, FALSE, FALSE, 0);
|
||||
g_signal_connect (G_OBJECT (ui->gtk3_import_button), "clicked",
|
||||
G_CALLBACK (setup_theme_gtk3_import_cb), ui);
|
||||
|
||||
label = gtk_button_new_with_mnemonic (_("_Open Folder"));
|
||||
gtk_box_pack_start (GTK_BOX (button_box), label, FALSE, FALSE, 0);
|
||||
g_signal_connect (G_OBJECT (label), "clicked",
|
||||
G_CALLBACK (setup_theme_open_folder_cb), ui);
|
||||
ui->gtk3_apply_button = gtk_button_new_with_mnemonic (_("Apply GTK_3 Theme"));
|
||||
gtk_box_pack_start (GTK_BOX (button_box), ui->gtk3_apply_button, FALSE, FALSE, 0);
|
||||
g_signal_connect (G_OBJECT (ui->gtk3_apply_button), "clicked",
|
||||
G_CALLBACK (setup_theme_apply_gtk3_cb), ui);
|
||||
|
||||
ui->status_label = gtk_label_new (NULL);
|
||||
gtk_widget_set_halign (ui->status_label, GTK_ALIGN_START);
|
||||
gtk_widget_set_valign (ui->status_label, GTK_ALIGN_CENTER);
|
||||
gtk_box_pack_start (GTK_BOX (box), ui->status_label, FALSE, FALSE, 0);
|
||||
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);
|
||||
|
||||
setup_theme_populate (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);
|
||||
gtk_box_pack_start (GTK_BOX (hbox), ui->gtk3_status_label, FALSE, FALSE, 0);
|
||||
|
||||
g_object_set_data_full (G_OBJECT (box), "setup-theme-ui", ui, g_free);
|
||||
setup_gtk3_theme_populate (ui);
|
||||
|
||||
g_object_set_data_full (G_OBJECT (box), "setup-theme-ui", ui, setup_theme_ui_free);
|
||||
|
||||
return box;
|
||||
}
|
||||
@@ -2412,6 +2650,47 @@ setup_apply_entry_style (GtkWidget *entry)
|
||||
input_style->font_desc);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_apply_input_caret_provider (GtkWidget *widget, const char *css)
|
||||
{
|
||||
GtkCssProvider *provider;
|
||||
GtkStyleContext *context;
|
||||
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
context = gtk_widget_get_style_context (widget);
|
||||
provider = g_object_get_data (G_OBJECT (widget), "zoitechat-input-caret-provider");
|
||||
if (!provider)
|
||||
{
|
||||
provider = gtk_css_provider_new ();
|
||||
g_object_set_data_full (G_OBJECT (widget), "zoitechat-input-caret-provider",
|
||||
provider, g_object_unref);
|
||||
gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
}
|
||||
|
||||
gtk_css_provider_load_from_data (provider, css, -1, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_remove_input_caret_provider (GtkWidget *widget)
|
||||
{
|
||||
GtkCssProvider *provider;
|
||||
GtkStyleContext *context;
|
||||
|
||||
if (!widget)
|
||||
return;
|
||||
|
||||
context = gtk_widget_get_style_context (widget);
|
||||
provider = g_object_get_data (G_OBJECT (widget), "zoitechat-input-caret-provider");
|
||||
if (!provider)
|
||||
return;
|
||||
|
||||
gtk_style_context_remove_provider (context, GTK_STYLE_PROVIDER (provider));
|
||||
g_object_set_data (G_OBJECT (widget), "zoitechat-input-caret-provider", NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_apply_to_sess (session_gui *gui)
|
||||
{
|
||||
@@ -2437,35 +2716,29 @@ setup_apply_to_sess (session_gui *gui)
|
||||
if (prefs.hex_gui_input_style)
|
||||
{
|
||||
char buf[128];
|
||||
GtkCssProvider *provider = gtk_css_provider_new ();
|
||||
GtkStyleContext *context;
|
||||
char *color_string = gdk_rgba_to_string (&colors[COL_FG]);
|
||||
|
||||
g_snprintf (buf, sizeof (buf), ".zoitechat-inputbox { caret-color: %s; }",
|
||||
g_snprintf (buf, sizeof (buf), "#zoitechat-inputbox { caret-color: %s; }",
|
||||
color_string);
|
||||
gtk_css_provider_load_from_data (provider, buf, -1, NULL);
|
||||
g_free (color_string);
|
||||
|
||||
context = gtk_widget_get_style_context (gui->input_box);
|
||||
gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
context = gtk_widget_get_style_context (gui->limit_entry);
|
||||
gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
context = gtk_widget_get_style_context (gui->key_entry);
|
||||
gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
context = gtk_widget_get_style_context (gui->topic_entry);
|
||||
gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
g_object_unref (provider);
|
||||
setup_apply_input_caret_provider (gui->input_box, buf);
|
||||
setup_apply_input_caret_provider (gui->limit_entry, buf);
|
||||
setup_apply_input_caret_provider (gui->key_entry, buf);
|
||||
setup_apply_input_caret_provider (gui->topic_entry, buf);
|
||||
|
||||
setup_apply_entry_style (gui->input_box);
|
||||
setup_apply_entry_style (gui->limit_entry);
|
||||
setup_apply_entry_style (gui->key_entry);
|
||||
setup_apply_entry_style (gui->topic_entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
setup_remove_input_caret_provider (gui->input_box);
|
||||
setup_remove_input_caret_provider (gui->limit_entry);
|
||||
setup_remove_input_caret_provider (gui->key_entry);
|
||||
setup_remove_input_caret_provider (gui->topic_entry);
|
||||
}
|
||||
|
||||
if (prefs.hex_gui_ulist_buttons)
|
||||
gtk_widget_show (gui->button_box);
|
||||
@@ -2742,6 +3015,7 @@ setup_window_open (void)
|
||||
gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0);
|
||||
|
||||
gtk_widget_show_all (win);
|
||||
fe_apply_theme_to_toplevel (win);
|
||||
|
||||
return win;
|
||||
}
|
||||
@@ -2757,6 +3031,8 @@ setup_close_cb (GtkWidget *win, GtkWidget **swin)
|
||||
color_selector_widgets = NULL;
|
||||
}
|
||||
|
||||
setup_color_edit_source_colors = NULL;
|
||||
|
||||
if (font_dialog)
|
||||
{
|
||||
gtk_widget_destroy (font_dialog);
|
||||
|
||||
@@ -27,6 +27,8 @@ DefaultDirName={pf64}\ZoiteChat
|
||||
#else
|
||||
DefaultDirName={pf32}\ZoiteChat
|
||||
#endif
|
||||
DisableDirPage=no
|
||||
UsePreviousAppDir=no
|
||||
DefaultGroupName=ZoiteChat
|
||||
AllowNoIcons=yes
|
||||
SolidCompression=yes
|
||||
|
||||
Reference in New Issue
Block a user