mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-03-10 07:50:19 +00:00
1153 lines
27 KiB
C
1153 lines
27 KiB
C
|
|
#include "gtk3-theme-service.h"
|
||
|
|
|
||
|
|
#ifndef G_OS_WIN32
|
||
|
|
#if defined(__has_include)
|
||
|
|
#if __has_include(<archive.h>)
|
||
|
|
#include <archive.h>
|
||
|
|
#include <archive_entry.h>
|
||
|
|
#elif __has_include(<libarchive/archive.h>)
|
||
|
|
#include <libarchive/archive.h>
|
||
|
|
#include <libarchive/archive_entry.h>
|
||
|
|
#elif __has_include(<archive/archive.h>)
|
||
|
|
#include <archive/archive.h>
|
||
|
|
#include <archive/archive_entry.h>
|
||
|
|
#else
|
||
|
|
#error "libarchive headers not found"
|
||
|
|
#endif
|
||
|
|
#else
|
||
|
|
#include <archive.h>
|
||
|
|
#include <archive_entry.h>
|
||
|
|
#endif
|
||
|
|
#endif
|
||
|
|
#include <glib/gstdio.h>
|
||
|
|
#include <gio/gio.h>
|
||
|
|
#include <errno.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include "util.h"
|
||
|
|
#include "cfgfiles.h"
|
||
|
|
|
||
|
|
static void
|
||
|
|
remove_tree (const char *path)
|
||
|
|
{
|
||
|
|
GDir *dir;
|
||
|
|
const char *name;
|
||
|
|
|
||
|
|
if (!g_file_test (path, G_FILE_TEST_EXISTS))
|
||
|
|
return;
|
||
|
|
if (!g_file_test (path, G_FILE_TEST_IS_DIR))
|
||
|
|
{
|
||
|
|
g_remove (path);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
dir = g_dir_open (path, 0, NULL);
|
||
|
|
if (dir)
|
||
|
|
{
|
||
|
|
while ((name = g_dir_read_name (dir)) != NULL)
|
||
|
|
{
|
||
|
|
char *child = g_build_filename (path, name, NULL);
|
||
|
|
remove_tree (child);
|
||
|
|
g_free (child);
|
||
|
|
}
|
||
|
|
g_dir_close (dir);
|
||
|
|
}
|
||
|
|
g_rmdir (path);
|
||
|
|
}
|
||
|
|
|
||
|
|
static gboolean
|
||
|
|
gtk3_css_dir_parse_minor (const char *name, gint *minor)
|
||
|
|
{
|
||
|
|
gint parsed_minor = 0;
|
||
|
|
|
||
|
|
if (!g_str_has_prefix (name, "gtk-3"))
|
||
|
|
return FALSE;
|
||
|
|
|
||
|
|
if (name[5] == '\0')
|
||
|
|
{
|
||
|
|
*minor = 0;
|
||
|
|
return TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (name[5] != '.')
|
||
|
|
return FALSE;
|
||
|
|
|
||
|
|
if (!name[6])
|
||
|
|
return FALSE;
|
||
|
|
|
||
|
|
for (const char *p = name + 6; *p; p++)
|
||
|
|
{
|
||
|
|
if (!g_ascii_isdigit (*p))
|
||
|
|
return FALSE;
|
||
|
|
parsed_minor = (parsed_minor * 10) + (*p - '0');
|
||
|
|
}
|
||
|
|
|
||
|
|
*minor = parsed_minor;
|
||
|
|
return TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
char *
|
||
|
|
zoitechat_gtk3_theme_pick_css_dir_for_minor (const char *theme_root, int preferred_minor)
|
||
|
|
{
|
||
|
|
GDir *dir;
|
||
|
|
const char *name;
|
||
|
|
char *best_supported = NULL;
|
||
|
|
char *best_fallback = NULL;
|
||
|
|
gint best_supported_minor = G_MININT;
|
||
|
|
gint best_fallback_minor = G_MININT;
|
||
|
|
|
||
|
|
if (!theme_root || !g_file_test (theme_root, G_FILE_TEST_IS_DIR))
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
dir = g_dir_open (theme_root, 0, NULL);
|
||
|
|
if (!dir)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
while ((name = g_dir_read_name (dir)) != NULL)
|
||
|
|
{
|
||
|
|
char *css_path;
|
||
|
|
gint minor = 0;
|
||
|
|
|
||
|
|
if (!gtk3_css_dir_parse_minor (name, &minor))
|
||
|
|
continue;
|
||
|
|
|
||
|
|
css_path = g_build_filename (theme_root, name, "gtk.css", NULL);
|
||
|
|
if (!g_file_test (css_path, G_FILE_TEST_IS_REGULAR))
|
||
|
|
{
|
||
|
|
g_free (css_path);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
g_free (css_path);
|
||
|
|
|
||
|
|
if (preferred_minor >= 0 && minor <= preferred_minor)
|
||
|
|
{
|
||
|
|
if (minor > best_supported_minor)
|
||
|
|
{
|
||
|
|
g_free (best_supported);
|
||
|
|
best_supported = g_strdup (name);
|
||
|
|
best_supported_minor = minor;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (minor > best_fallback_minor)
|
||
|
|
{
|
||
|
|
g_free (best_fallback);
|
||
|
|
best_fallback = g_strdup (name);
|
||
|
|
best_fallback_minor = minor;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
g_dir_close (dir);
|
||
|
|
|
||
|
|
if (best_supported)
|
||
|
|
{
|
||
|
|
g_free (best_fallback);
|
||
|
|
return best_supported;
|
||
|
|
}
|
||
|
|
|
||
|
|
return best_fallback;
|
||
|
|
}
|
||
|
|
|
||
|
|
char *
|
||
|
|
zoitechat_gtk3_theme_pick_css_dir (const char *theme_root)
|
||
|
|
{
|
||
|
|
return zoitechat_gtk3_theme_pick_css_dir_for_minor (theme_root, -1);
|
||
|
|
}
|
||
|
|
|
||
|
|
static gboolean
|
||
|
|
path_has_gtk3_css (const char *root)
|
||
|
|
{
|
||
|
|
char *css_dir = zoitechat_gtk3_theme_pick_css_dir (root);
|
||
|
|
gboolean ok = css_dir != NULL;
|
||
|
|
g_free (css_dir);
|
||
|
|
return ok;
|
||
|
|
}
|
||
|
|
|
||
|
|
static char **
|
||
|
|
path_read_inherits (const char *theme_root)
|
||
|
|
{
|
||
|
|
char *index_theme;
|
||
|
|
GKeyFile *keyfile;
|
||
|
|
char *raw;
|
||
|
|
char **tokens;
|
||
|
|
GPtrArray *parents;
|
||
|
|
guint i;
|
||
|
|
|
||
|
|
if (!theme_root)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
index_theme = g_build_filename (theme_root, "index.theme", NULL);
|
||
|
|
if (!g_file_test (index_theme, G_FILE_TEST_IS_REGULAR))
|
||
|
|
{
|
||
|
|
g_free (index_theme);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
keyfile = g_key_file_new ();
|
||
|
|
if (!g_key_file_load_from_file (keyfile, index_theme, G_KEY_FILE_NONE, NULL))
|
||
|
|
{
|
||
|
|
g_key_file_unref (keyfile);
|
||
|
|
g_free (index_theme);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
raw = g_key_file_get_string (keyfile, "Desktop Entry", "Inherits", NULL);
|
||
|
|
g_key_file_unref (keyfile);
|
||
|
|
g_free (index_theme);
|
||
|
|
if (!raw)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
tokens = g_strsplit_set (raw, ",;", -1);
|
||
|
|
g_free (raw);
|
||
|
|
parents = g_ptr_array_new_with_free_func (g_free);
|
||
|
|
|
||
|
|
for (i = 0; tokens && tokens[i]; i++)
|
||
|
|
{
|
||
|
|
char *name = g_strstrip (tokens[i]);
|
||
|
|
if (name[0] == '\0')
|
||
|
|
continue;
|
||
|
|
g_ptr_array_add (parents, g_strdup (name));
|
||
|
|
}
|
||
|
|
|
||
|
|
g_strfreev (tokens);
|
||
|
|
g_ptr_array_add (parents, NULL);
|
||
|
|
return (char **) g_ptr_array_free (parents, FALSE);
|
||
|
|
}
|
||
|
|
|
||
|
|
static gboolean
|
||
|
|
path_exists_as_dir (const char *path)
|
||
|
|
{
|
||
|
|
return path && g_file_test (path, G_FILE_TEST_IS_DIR);
|
||
|
|
}
|
||
|
|
|
||
|
|
static char *
|
||
|
|
resolve_parent_theme_root (const char *child_theme_root, const char *parent_name)
|
||
|
|
{
|
||
|
|
char *candidate;
|
||
|
|
char *child_parent;
|
||
|
|
char *user_dir;
|
||
|
|
char *home_themes;
|
||
|
|
char *home_local;
|
||
|
|
|
||
|
|
if (!parent_name || !parent_name[0])
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
if (g_path_is_absolute (parent_name) && path_exists_as_dir (parent_name))
|
||
|
|
return g_strdup (parent_name);
|
||
|
|
|
||
|
|
child_parent = g_path_get_dirname (child_theme_root);
|
||
|
|
candidate = g_build_filename (child_parent, parent_name, NULL);
|
||
|
|
g_free (child_parent);
|
||
|
|
if (path_exists_as_dir (candidate))
|
||
|
|
return candidate;
|
||
|
|
g_free (candidate);
|
||
|
|
|
||
|
|
candidate = g_build_filename ("/usr/share/themes", parent_name, NULL);
|
||
|
|
if (path_exists_as_dir (candidate))
|
||
|
|
return candidate;
|
||
|
|
g_free (candidate);
|
||
|
|
|
||
|
|
home_themes = g_build_filename (g_get_home_dir (), ".themes", parent_name, NULL);
|
||
|
|
if (path_exists_as_dir (home_themes))
|
||
|
|
return home_themes;
|
||
|
|
g_free (home_themes);
|
||
|
|
|
||
|
|
home_local = g_build_filename (g_get_home_dir (), ".local", "share", "themes", parent_name, NULL);
|
||
|
|
if (path_exists_as_dir (home_local))
|
||
|
|
return home_local;
|
||
|
|
g_free (home_local);
|
||
|
|
|
||
|
|
user_dir = zoitechat_gtk3_theme_service_get_user_themes_dir ();
|
||
|
|
candidate = g_build_filename (user_dir, parent_name, NULL);
|
||
|
|
g_free (user_dir);
|
||
|
|
if (path_exists_as_dir (candidate))
|
||
|
|
return candidate;
|
||
|
|
g_free (candidate);
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
build_inheritance_chain_recursive (const char *theme_root,
|
||
|
|
GPtrArray *ordered_roots,
|
||
|
|
GHashTable *visited)
|
||
|
|
{
|
||
|
|
char **parents;
|
||
|
|
guint i;
|
||
|
|
|
||
|
|
if (!theme_root || g_hash_table_contains (visited, theme_root))
|
||
|
|
return;
|
||
|
|
|
||
|
|
g_hash_table_add (visited, g_strdup (theme_root));
|
||
|
|
parents = path_read_inherits (theme_root);
|
||
|
|
for (i = 0; parents && parents[i]; i++)
|
||
|
|
{
|
||
|
|
char *parent_root = resolve_parent_theme_root (theme_root, parents[i]);
|
||
|
|
if (!parent_root)
|
||
|
|
continue;
|
||
|
|
build_inheritance_chain_recursive (parent_root, ordered_roots, visited);
|
||
|
|
g_free (parent_root);
|
||
|
|
}
|
||
|
|
g_strfreev (parents);
|
||
|
|
|
||
|
|
if (path_has_gtk3_css (theme_root))
|
||
|
|
g_ptr_array_add (ordered_roots, g_strdup (theme_root));
|
||
|
|
}
|
||
|
|
|
||
|
|
GPtrArray *
|
||
|
|
zoitechat_gtk3_theme_build_inheritance_chain (const char *theme_root)
|
||
|
|
{
|
||
|
|
GPtrArray *ordered_roots;
|
||
|
|
GHashTable *visited;
|
||
|
|
|
||
|
|
if (!theme_root || !path_exists_as_dir (theme_root))
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
ordered_roots = g_ptr_array_new_with_free_func (g_free);
|
||
|
|
visited = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||
|
|
build_inheritance_chain_recursive (theme_root, ordered_roots, visited);
|
||
|
|
g_hash_table_destroy (visited);
|
||
|
|
|
||
|
|
if (ordered_roots->len == 0)
|
||
|
|
{
|
||
|
|
g_ptr_array_unref (ordered_roots);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
return ordered_roots;
|
||
|
|
}
|
||
|
|
|
||
|
|
static char *
|
||
|
|
path_build_id (const char *path, ZoitechatGtk3ThemeSource source)
|
||
|
|
{
|
||
|
|
char *digest = g_compute_checksum_for_string (G_CHECKSUM_SHA1, path, -1);
|
||
|
|
char *id = g_strdup_printf ("%s:%s", source == ZOITECHAT_GTK3_THEME_SOURCE_USER ? "user" : "system", digest);
|
||
|
|
g_free (digest);
|
||
|
|
return id;
|
||
|
|
}
|
||
|
|
|
||
|
|
static char *
|
||
|
|
path_pick_thumbnail (const char *root)
|
||
|
|
{
|
||
|
|
static const char *const names[] = {
|
||
|
|
"thumbnail.png",
|
||
|
|
"preview.png",
|
||
|
|
"screenshot.png",
|
||
|
|
NULL
|
||
|
|
};
|
||
|
|
int i;
|
||
|
|
|
||
|
|
for (i = 0; names[i] != NULL; i++)
|
||
|
|
{
|
||
|
|
char *candidate = g_build_filename (root, names[i], NULL);
|
||
|
|
if (g_file_test (candidate, G_FILE_TEST_IS_REGULAR))
|
||
|
|
return candidate;
|
||
|
|
g_free (candidate);
|
||
|
|
|
||
|
|
char *css_dir = zoitechat_gtk3_theme_pick_css_dir (root);
|
||
|
|
if (css_dir)
|
||
|
|
{
|
||
|
|
candidate = g_build_filename (root, css_dir, names[i], NULL);
|
||
|
|
g_free (css_dir);
|
||
|
|
if (g_file_test (candidate, G_FILE_TEST_IS_REGULAR))
|
||
|
|
return candidate;
|
||
|
|
g_free (candidate);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static char *
|
||
|
|
path_read_display_name (const char *root)
|
||
|
|
{
|
||
|
|
char *index_theme = g_build_filename (root, "index.theme", NULL);
|
||
|
|
GKeyFile *keyfile = g_key_file_new ();
|
||
|
|
char *name = NULL;
|
||
|
|
|
||
|
|
if (g_file_test (index_theme, G_FILE_TEST_IS_REGULAR) &&
|
||
|
|
g_key_file_load_from_file (keyfile, index_theme, G_KEY_FILE_NONE, NULL))
|
||
|
|
{
|
||
|
|
name = g_key_file_get_string (keyfile, "Desktop Entry", "Name", NULL);
|
||
|
|
if (!name)
|
||
|
|
name = g_key_file_get_string (keyfile, "X-GNOME-Metatheme", "Name", NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!name)
|
||
|
|
name = g_path_get_basename (root);
|
||
|
|
|
||
|
|
g_key_file_unref (keyfile);
|
||
|
|
g_free (index_theme);
|
||
|
|
return name;
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
zoitechat_gtk3_theme_free (ZoitechatGtk3Theme *theme)
|
||
|
|
{
|
||
|
|
if (!theme)
|
||
|
|
return;
|
||
|
|
g_free (theme->id);
|
||
|
|
g_free (theme->display_name);
|
||
|
|
g_free (theme->path);
|
||
|
|
g_free (theme->thumbnail_path);
|
||
|
|
g_free (theme);
|
||
|
|
}
|
||
|
|
|
||
|
|
char *
|
||
|
|
zoitechat_gtk3_theme_service_get_user_themes_dir (void)
|
||
|
|
{
|
||
|
|
return g_build_filename (get_xdir (), "gtk3-themes", NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
static char *path_normalize_theme_root (const char *path);
|
||
|
|
|
||
|
|
static void
|
||
|
|
discover_dir (GPtrArray *themes, GHashTable *seen_theme_roots, const char *base_dir, ZoitechatGtk3ThemeSource source)
|
||
|
|
{
|
||
|
|
GDir *dir;
|
||
|
|
const char *name;
|
||
|
|
|
||
|
|
if (!g_file_test (base_dir, G_FILE_TEST_IS_DIR))
|
||
|
|
return;
|
||
|
|
|
||
|
|
dir = g_dir_open (base_dir, 0, NULL);
|
||
|
|
if (!dir)
|
||
|
|
return;
|
||
|
|
|
||
|
|
while ((name = g_dir_read_name (dir)) != NULL)
|
||
|
|
{
|
||
|
|
ZoitechatGtk3Theme *theme;
|
||
|
|
char *root;
|
||
|
|
char *dark;
|
||
|
|
char *css_dir;
|
||
|
|
|
||
|
|
if (name[0] == '.')
|
||
|
|
continue;
|
||
|
|
|
||
|
|
root = g_build_filename (base_dir, name, NULL);
|
||
|
|
if (!g_file_test (root, G_FILE_TEST_IS_DIR) || !path_has_gtk3_css (root))
|
||
|
|
{
|
||
|
|
g_free (root);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (seen_theme_roots)
|
||
|
|
{
|
||
|
|
char *canonical_root = path_normalize_theme_root (root);
|
||
|
|
if (g_hash_table_contains (seen_theme_roots, canonical_root))
|
||
|
|
{
|
||
|
|
g_free (canonical_root);
|
||
|
|
g_free (root);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
g_hash_table_add (seen_theme_roots, canonical_root);
|
||
|
|
}
|
||
|
|
|
||
|
|
theme = g_new0 (ZoitechatGtk3Theme, 1);
|
||
|
|
theme->path = root;
|
||
|
|
theme->source = source;
|
||
|
|
theme->id = path_build_id (root, source);
|
||
|
|
theme->display_name = path_read_display_name (root);
|
||
|
|
theme->thumbnail_path = path_pick_thumbnail (root);
|
||
|
|
css_dir = zoitechat_gtk3_theme_pick_css_dir (root);
|
||
|
|
dark = css_dir ? g_build_filename (root, css_dir, "gtk-dark.css", NULL) : NULL;
|
||
|
|
theme->has_dark_variant = g_file_test (dark, G_FILE_TEST_IS_REGULAR);
|
||
|
|
g_free (css_dir);
|
||
|
|
g_free (dark);
|
||
|
|
g_ptr_array_add (themes, theme);
|
||
|
|
}
|
||
|
|
|
||
|
|
g_dir_close (dir);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static char *
|
||
|
|
path_normalize_theme_root (const char *path)
|
||
|
|
{
|
||
|
|
char *canonical;
|
||
|
|
char *target;
|
||
|
|
|
||
|
|
if (!path || path[0] == '\0')
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
canonical = g_canonicalize_filename (path, NULL);
|
||
|
|
target = g_file_read_link (canonical, NULL);
|
||
|
|
if (target && target[0])
|
||
|
|
{
|
||
|
|
char *base = g_path_get_dirname (canonical);
|
||
|
|
char *resolved = g_path_is_absolute (target)
|
||
|
|
? g_strdup (target)
|
||
|
|
: g_build_filename (base, target, NULL);
|
||
|
|
g_free (canonical);
|
||
|
|
canonical = g_canonicalize_filename (resolved, NULL);
|
||
|
|
g_free (resolved);
|
||
|
|
g_free (base);
|
||
|
|
}
|
||
|
|
g_free (target);
|
||
|
|
return canonical;
|
||
|
|
}
|
||
|
|
|
||
|
|
static gint
|
||
|
|
theme_cmp (gconstpointer a, gconstpointer b)
|
||
|
|
{
|
||
|
|
const ZoitechatGtk3Theme *ta = *(const ZoitechatGtk3Theme **) a;
|
||
|
|
const ZoitechatGtk3Theme *tb = *(const ZoitechatGtk3Theme **) b;
|
||
|
|
return g_ascii_strcasecmp (ta->display_name, tb->display_name);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
add_theme_root (GPtrArray *roots, GHashTable *seen, const char *path)
|
||
|
|
{
|
||
|
|
char *normalized;
|
||
|
|
|
||
|
|
if (!path || path[0] == '\0')
|
||
|
|
return;
|
||
|
|
|
||
|
|
normalized = g_canonicalize_filename (path, NULL);
|
||
|
|
if (g_hash_table_contains (seen, normalized))
|
||
|
|
{
|
||
|
|
g_free (normalized);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
g_hash_table_add (seen, normalized);
|
||
|
|
g_ptr_array_add (roots, g_strdup (path));
|
||
|
|
}
|
||
|
|
|
||
|
|
GPtrArray *
|
||
|
|
zoitechat_gtk3_theme_service_discover (void)
|
||
|
|
{
|
||
|
|
GPtrArray *themes = g_ptr_array_new_with_free_func ((GDestroyNotify) zoitechat_gtk3_theme_free);
|
||
|
|
GPtrArray *system_roots = g_ptr_array_new_with_free_func (g_free);
|
||
|
|
GHashTable *seen_system_roots = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||
|
|
GPtrArray *user_roots = g_ptr_array_new_with_free_func (g_free);
|
||
|
|
GHashTable *seen_user_roots = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||
|
|
GHashTable *seen_theme_roots = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||
|
|
const gchar *const *system_data_dirs;
|
||
|
|
guint i;
|
||
|
|
char *user_data_themes;
|
||
|
|
char *user_dir = zoitechat_gtk3_theme_service_get_user_themes_dir ();
|
||
|
|
char *home_themes = g_build_filename (g_get_home_dir (), ".themes", NULL);
|
||
|
|
|
||
|
|
g_mkdir_with_parents (user_dir, 0700);
|
||
|
|
|
||
|
|
user_data_themes = g_build_filename (g_get_user_data_dir (), "themes", NULL);
|
||
|
|
add_theme_root (user_roots, seen_user_roots, user_data_themes);
|
||
|
|
g_free (user_data_themes);
|
||
|
|
add_theme_root (user_roots, seen_user_roots, home_themes);
|
||
|
|
add_theme_root (user_roots, seen_user_roots, user_dir);
|
||
|
|
|
||
|
|
system_data_dirs = g_get_system_data_dirs ();
|
||
|
|
for (i = 0; system_data_dirs && system_data_dirs[i]; i++)
|
||
|
|
{
|
||
|
|
char *system_themes = g_build_filename (system_data_dirs[i], "themes", NULL);
|
||
|
|
add_theme_root (system_roots, seen_system_roots, system_themes);
|
||
|
|
g_free (system_themes);
|
||
|
|
}
|
||
|
|
|
||
|
|
for (i = 0; i < system_roots->len; i++)
|
||
|
|
discover_dir (themes, seen_theme_roots, g_ptr_array_index (system_roots, i), ZOITECHAT_GTK3_THEME_SOURCE_SYSTEM);
|
||
|
|
for (i = 0; i < user_roots->len; i++)
|
||
|
|
discover_dir (themes, seen_theme_roots, g_ptr_array_index (user_roots, i), ZOITECHAT_GTK3_THEME_SOURCE_USER);
|
||
|
|
g_ptr_array_sort (themes, theme_cmp);
|
||
|
|
|
||
|
|
g_hash_table_destroy (seen_theme_roots);
|
||
|
|
g_hash_table_destroy (seen_user_roots);
|
||
|
|
g_ptr_array_unref (user_roots);
|
||
|
|
g_hash_table_destroy (seen_system_roots);
|
||
|
|
g_ptr_array_unref (system_roots);
|
||
|
|
g_free (home_themes);
|
||
|
|
g_free (user_dir);
|
||
|
|
return themes;
|
||
|
|
}
|
||
|
|
|
||
|
|
ZoitechatGtk3Theme *
|
||
|
|
zoitechat_gtk3_theme_find_by_id (const char *theme_id)
|
||
|
|
{
|
||
|
|
GPtrArray *themes;
|
||
|
|
ZoitechatGtk3Theme *result = NULL;
|
||
|
|
guint i;
|
||
|
|
|
||
|
|
if (!theme_id || !*theme_id)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
themes = zoitechat_gtk3_theme_service_discover ();
|
||
|
|
for (i = 0; i < themes->len; i++)
|
||
|
|
{
|
||
|
|
ZoitechatGtk3Theme *theme = g_ptr_array_index (themes, i);
|
||
|
|
if (g_strcmp0 (theme->id, theme_id) == 0)
|
||
|
|
{
|
||
|
|
result = g_new0 (ZoitechatGtk3Theme, 1);
|
||
|
|
result->id = g_strdup (theme->id);
|
||
|
|
result->display_name = g_strdup (theme->display_name);
|
||
|
|
result->path = g_strdup (theme->path);
|
||
|
|
result->thumbnail_path = g_strdup (theme->thumbnail_path);
|
||
|
|
result->has_dark_variant = theme->has_dark_variant;
|
||
|
|
result->source = theme->source;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
g_ptr_array_unref (themes);
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
collect_theme_roots (const char *root, GPtrArray *found, int depth)
|
||
|
|
{
|
||
|
|
GDir *dir;
|
||
|
|
const char *name;
|
||
|
|
|
||
|
|
if (depth > 4)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (path_has_gtk3_css (root))
|
||
|
|
{
|
||
|
|
g_ptr_array_add (found, g_strdup (root));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
dir = g_dir_open (root, 0, NULL);
|
||
|
|
if (!dir)
|
||
|
|
return;
|
||
|
|
|
||
|
|
while ((name = g_dir_read_name (dir)) != NULL)
|
||
|
|
{
|
||
|
|
char *child;
|
||
|
|
if (name[0] == '.')
|
||
|
|
continue;
|
||
|
|
child = g_build_filename (root, name, NULL);
|
||
|
|
if (g_file_test (child, G_FILE_TEST_IS_DIR))
|
||
|
|
collect_theme_roots (child, found, depth + 1);
|
||
|
|
g_free (child);
|
||
|
|
}
|
||
|
|
|
||
|
|
g_dir_close (dir);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
typedef struct
|
||
|
|
{
|
||
|
|
char *path;
|
||
|
|
int depth;
|
||
|
|
gboolean has_index_theme;
|
||
|
|
} ThemeRootCandidate;
|
||
|
|
|
||
|
|
static void
|
||
|
|
theme_root_candidate_free (ThemeRootCandidate *candidate)
|
||
|
|
{
|
||
|
|
if (!candidate)
|
||
|
|
return;
|
||
|
|
g_free (candidate->path);
|
||
|
|
g_free (candidate);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
path_depth_from_root (const char *base, const char *path)
|
||
|
|
{
|
||
|
|
int depth = 0;
|
||
|
|
const char *cursor;
|
||
|
|
size_t base_len;
|
||
|
|
|
||
|
|
if (!base || !path)
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
base_len = strlen (base);
|
||
|
|
cursor = path + base_len;
|
||
|
|
while (*cursor)
|
||
|
|
{
|
||
|
|
if (*cursor == G_DIR_SEPARATOR)
|
||
|
|
depth++;
|
||
|
|
cursor++;
|
||
|
|
}
|
||
|
|
|
||
|
|
return depth;
|
||
|
|
}
|
||
|
|
|
||
|
|
static gint
|
||
|
|
theme_root_candidate_compare (gconstpointer a, gconstpointer b)
|
||
|
|
{
|
||
|
|
const ThemeRootCandidate *ca = a;
|
||
|
|
const ThemeRootCandidate *cb = b;
|
||
|
|
if (ca->has_index_theme != cb->has_index_theme)
|
||
|
|
return ca->has_index_theme ? -1 : 1;
|
||
|
|
if (ca->depth != cb->depth)
|
||
|
|
return ca->depth - cb->depth;
|
||
|
|
return g_ascii_strcasecmp (ca->path, cb->path);
|
||
|
|
}
|
||
|
|
|
||
|
|
static char *
|
||
|
|
select_theme_root (GPtrArray *roots, const char *input_root)
|
||
|
|
{
|
||
|
|
GPtrArray *candidates;
|
||
|
|
guint i;
|
||
|
|
char *selected;
|
||
|
|
|
||
|
|
if (!roots || roots->len == 0)
|
||
|
|
return NULL;
|
||
|
|
if (roots->len == 1)
|
||
|
|
return g_strdup (g_ptr_array_index (roots, 0));
|
||
|
|
|
||
|
|
candidates = g_ptr_array_new_with_free_func ((GDestroyNotify) theme_root_candidate_free);
|
||
|
|
for (i = 0; i < roots->len; i++)
|
||
|
|
{
|
||
|
|
ThemeRootCandidate *candidate = g_new0 (ThemeRootCandidate, 1);
|
||
|
|
char *index_theme;
|
||
|
|
|
||
|
|
candidate->path = g_strdup (g_ptr_array_index (roots, i));
|
||
|
|
candidate->depth = path_depth_from_root (input_root, candidate->path);
|
||
|
|
index_theme = g_build_filename (candidate->path, "index.theme", NULL);
|
||
|
|
candidate->has_index_theme = g_file_test (index_theme, G_FILE_TEST_IS_REGULAR);
|
||
|
|
g_free (index_theme);
|
||
|
|
g_ptr_array_add (candidates, candidate);
|
||
|
|
}
|
||
|
|
|
||
|
|
g_ptr_array_sort (candidates, theme_root_candidate_compare);
|
||
|
|
selected = g_strdup (((ThemeRootCandidate *) g_ptr_array_index (candidates, 0))->path);
|
||
|
|
g_ptr_array_unref (candidates);
|
||
|
|
return selected;
|
||
|
|
}
|
||
|
|
|
||
|
|
static gboolean
|
||
|
|
copy_tree (const char *src, const char *dest, GError **error)
|
||
|
|
{
|
||
|
|
GDir *dir;
|
||
|
|
const char *name;
|
||
|
|
|
||
|
|
if (g_mkdir_with_parents (dest, 0700) != 0)
|
||
|
|
return g_set_error_literal (error, G_FILE_ERROR, g_file_error_from_errno (errno), "Failed to create theme directory."), FALSE;
|
||
|
|
|
||
|
|
dir = g_dir_open (src, 0, error);
|
||
|
|
if (!dir)
|
||
|
|
return FALSE;
|
||
|
|
|
||
|
|
while ((name = g_dir_read_name (dir)) != NULL)
|
||
|
|
{
|
||
|
|
char *s = g_build_filename (src, name, NULL);
|
||
|
|
char *d = g_build_filename (dest, name, NULL);
|
||
|
|
if (g_file_test (s, G_FILE_TEST_IS_DIR))
|
||
|
|
{
|
||
|
|
if (!copy_tree (s, d, error))
|
||
|
|
{
|
||
|
|
g_free (s);
|
||
|
|
g_free (d);
|
||
|
|
g_dir_close (dir);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
GFile *sf = g_file_new_for_path (s);
|
||
|
|
GFile *df = g_file_new_for_path (d);
|
||
|
|
if (!g_file_copy (sf, df, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, error))
|
||
|
|
{
|
||
|
|
g_object_unref (sf);
|
||
|
|
g_object_unref (df);
|
||
|
|
g_free (s);
|
||
|
|
g_free (d);
|
||
|
|
g_dir_close (dir);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
g_object_unref (sf);
|
||
|
|
g_object_unref (df);
|
||
|
|
}
|
||
|
|
g_free (s);
|
||
|
|
g_free (d);
|
||
|
|
}
|
||
|
|
|
||
|
|
g_dir_close (dir);
|
||
|
|
return TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
static gboolean
|
||
|
|
validate_theme_root_for_import (const char *theme_root, GError **error)
|
||
|
|
{
|
||
|
|
char *index_theme;
|
||
|
|
GKeyFile *keyfile;
|
||
|
|
char *css_dir;
|
||
|
|
char *css_path;
|
||
|
|
char *raw_inherits;
|
||
|
|
char **inherits;
|
||
|
|
guint i;
|
||
|
|
GError *load_error = NULL;
|
||
|
|
|
||
|
|
index_theme = g_build_filename (theme_root, "index.theme", NULL);
|
||
|
|
if (!g_file_test (index_theme, G_FILE_TEST_IS_REGULAR))
|
||
|
|
{
|
||
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||
|
|
"Invalid GTK3 theme at '%s': missing required index.theme at '%s'.",
|
||
|
|
theme_root, index_theme);
|
||
|
|
g_free (index_theme);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
keyfile = g_key_file_new ();
|
||
|
|
if (!g_key_file_load_from_file (keyfile, index_theme, G_KEY_FILE_NONE, &load_error))
|
||
|
|
{
|
||
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||
|
|
"Invalid GTK3 theme at '%s': failed to parse index.theme '%s': %s.",
|
||
|
|
theme_root, index_theme, load_error->message);
|
||
|
|
g_error_free (load_error);
|
||
|
|
g_key_file_unref (keyfile);
|
||
|
|
g_free (index_theme);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!g_key_file_has_group (keyfile, "Desktop Entry"))
|
||
|
|
{
|
||
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||
|
|
"Invalid GTK3 theme at '%s': index.theme '%s' is missing the [Desktop Entry] section.",
|
||
|
|
theme_root, index_theme);
|
||
|
|
g_key_file_unref (keyfile);
|
||
|
|
g_free (index_theme);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
css_dir = zoitechat_gtk3_theme_pick_css_dir (theme_root);
|
||
|
|
if (!css_dir)
|
||
|
|
{
|
||
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||
|
|
"Invalid GTK3 theme at '%s': could not resolve a GTK CSS directory (expected gtk-3.x/gtk.css).",
|
||
|
|
theme_root);
|
||
|
|
g_key_file_unref (keyfile);
|
||
|
|
g_free (index_theme);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
css_path = g_build_filename (theme_root, css_dir, "gtk.css", NULL);
|
||
|
|
if (!g_file_test (css_path, G_FILE_TEST_IS_REGULAR))
|
||
|
|
{
|
||
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||
|
|
"Invalid GTK3 theme at '%s': missing primary gtk.css at '%s'.",
|
||
|
|
theme_root, css_path);
|
||
|
|
g_free (css_path);
|
||
|
|
g_free (css_dir);
|
||
|
|
g_key_file_unref (keyfile);
|
||
|
|
g_free (index_theme);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
g_free (css_path);
|
||
|
|
g_free (css_dir);
|
||
|
|
|
||
|
|
raw_inherits = g_key_file_get_string (keyfile, "Desktop Entry", "Inherits", NULL);
|
||
|
|
g_key_file_unref (keyfile);
|
||
|
|
g_free (index_theme);
|
||
|
|
if (!raw_inherits)
|
||
|
|
return TRUE;
|
||
|
|
|
||
|
|
inherits = g_strsplit_set (raw_inherits, ",;", -1);
|
||
|
|
g_free (raw_inherits);
|
||
|
|
|
||
|
|
for (i = 0; inherits && inherits[i]; i++)
|
||
|
|
{
|
||
|
|
char *parent_name = g_strstrip (inherits[i]);
|
||
|
|
char *parent_root;
|
||
|
|
|
||
|
|
if (parent_name[0] == '\0')
|
||
|
|
continue;
|
||
|
|
|
||
|
|
parent_root = resolve_parent_theme_root (theme_root, parent_name);
|
||
|
|
if (!parent_root)
|
||
|
|
{
|
||
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||
|
|
"Invalid GTK3 theme at '%s': parent theme '%s' from Inherits could not be resolved.",
|
||
|
|
theme_root, parent_name);
|
||
|
|
g_strfreev (inherits);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
g_free (parent_root);
|
||
|
|
}
|
||
|
|
|
||
|
|
g_strfreev (inherits);
|
||
|
|
return TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
static char *
|
||
|
|
extract_archive (const char *source, GError **error)
|
||
|
|
{
|
||
|
|
char *tmp = g_dir_make_tmp ("zoitechat-gtk3-theme-XXXXXX", error);
|
||
|
|
#ifdef G_OS_WIN32
|
||
|
|
char *stdout_text = NULL;
|
||
|
|
char *stderr_text = NULL;
|
||
|
|
char *system_tar = NULL;
|
||
|
|
char *system_root = NULL;
|
||
|
|
char *tar_program = NULL;
|
||
|
|
int status = 0;
|
||
|
|
gboolean extracted = FALSE;
|
||
|
|
const char *ext;
|
||
|
|
|
||
|
|
if (!tmp)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
ext = strrchr (source, '.');
|
||
|
|
if (ext && g_ascii_strcasecmp (ext, ".zip") == 0)
|
||
|
|
{
|
||
|
|
char *argv[] = {
|
||
|
|
"powershell",
|
||
|
|
"-NoProfile",
|
||
|
|
"-NonInteractive",
|
||
|
|
"-ExecutionPolicy",
|
||
|
|
"Bypass",
|
||
|
|
"-Command",
|
||
|
|
"Expand-Archive -LiteralPath $args[0] -DestinationPath $args[1] -Force",
|
||
|
|
(char *)source,
|
||
|
|
tmp,
|
||
|
|
NULL
|
||
|
|
};
|
||
|
|
|
||
|
|
extracted = g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
|
||
|
|
&stdout_text, &stderr_text, &status, NULL);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
tar_program = g_find_program_in_path ("tar.exe");
|
||
|
|
if (!tar_program)
|
||
|
|
{
|
||
|
|
system_root = g_strdup (g_getenv ("SystemRoot"));
|
||
|
|
if (system_root)
|
||
|
|
{
|
||
|
|
system_tar = g_build_filename (system_root, "System32", "tar.exe", NULL);
|
||
|
|
if (g_file_test (system_tar, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_EXECUTABLE))
|
||
|
|
tar_program = g_strdup (system_tar);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!tar_program)
|
||
|
|
{
|
||
|
|
char *argv[] = {
|
||
|
|
"powershell",
|
||
|
|
"-NoProfile",
|
||
|
|
"-NonInteractive",
|
||
|
|
"-ExecutionPolicy",
|
||
|
|
"Bypass",
|
||
|
|
"-Command",
|
||
|
|
"tar -xf $args[0] -C $args[1]",
|
||
|
|
(char *)source,
|
||
|
|
tmp,
|
||
|
|
NULL
|
||
|
|
};
|
||
|
|
|
||
|
|
extracted = g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
|
||
|
|
&stdout_text, &stderr_text, &status, NULL);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
char *argv[] = {
|
||
|
|
tar_program,
|
||
|
|
"-xf",
|
||
|
|
(char *)source,
|
||
|
|
"-C",
|
||
|
|
tmp,
|
||
|
|
NULL
|
||
|
|
};
|
||
|
|
|
||
|
|
extracted = g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
|
||
|
|
&stdout_text, &stderr_text, &status, NULL);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
g_free (tar_program);
|
||
|
|
g_free (system_tar);
|
||
|
|
g_free (system_root);
|
||
|
|
g_free (stdout_text);
|
||
|
|
g_free (stderr_text);
|
||
|
|
if (!extracted || status != 0)
|
||
|
|
{
|
||
|
|
remove_tree (tmp);
|
||
|
|
g_free (tmp);
|
||
|
|
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Failed to extract theme archive.");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
return tmp;
|
||
|
|
#else
|
||
|
|
struct archive *archive = NULL;
|
||
|
|
struct archive *disk = NULL;
|
||
|
|
struct archive_entry *entry;
|
||
|
|
int r;
|
||
|
|
|
||
|
|
if (!tmp)
|
||
|
|
return NULL;
|
||
|
|
|
||
|
|
archive = archive_read_new ();
|
||
|
|
disk = archive_write_disk_new ();
|
||
|
|
archive_read_support_filter_all (archive);
|
||
|
|
archive_read_support_format_all (archive);
|
||
|
|
archive_write_disk_set_options (disk, ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS);
|
||
|
|
archive_write_disk_set_standard_lookup (disk);
|
||
|
|
|
||
|
|
r = archive_read_open_filename (archive, source, 10240);
|
||
|
|
if (r != ARCHIVE_OK)
|
||
|
|
{
|
||
|
|
archive_read_free (archive);
|
||
|
|
archive_write_free (disk);
|
||
|
|
remove_tree (tmp);
|
||
|
|
g_free (tmp);
|
||
|
|
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Failed to extract theme archive.");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
while ((r = archive_read_next_header (archive, &entry)) == ARCHIVE_OK)
|
||
|
|
{
|
||
|
|
const char *entry_path = archive_entry_pathname (entry);
|
||
|
|
char *dest;
|
||
|
|
|
||
|
|
if (!entry_path)
|
||
|
|
{
|
||
|
|
r = ARCHIVE_FAILED;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
dest = g_build_filename (tmp, entry_path, NULL);
|
||
|
|
archive_entry_set_pathname (entry, dest);
|
||
|
|
g_free (dest);
|
||
|
|
|
||
|
|
r = archive_write_header (disk, entry);
|
||
|
|
if (r < ARCHIVE_OK)
|
||
|
|
break;
|
||
|
|
|
||
|
|
if (archive_entry_size (entry) > 0)
|
||
|
|
{
|
||
|
|
const void *buff;
|
||
|
|
size_t size;
|
||
|
|
la_int64_t offset;
|
||
|
|
|
||
|
|
for (;;)
|
||
|
|
{
|
||
|
|
r = archive_read_data_block (archive, &buff, &size, &offset);
|
||
|
|
if (r == ARCHIVE_EOF)
|
||
|
|
break;
|
||
|
|
if (r != ARCHIVE_OK)
|
||
|
|
break;
|
||
|
|
r = archive_write_data_block (disk, buff, size, offset);
|
||
|
|
if (r != ARCHIVE_OK)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
if (r != ARCHIVE_EOF && r != ARCHIVE_OK)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
r = archive_write_finish_entry (disk);
|
||
|
|
if (r != ARCHIVE_OK)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (r == ARCHIVE_EOF)
|
||
|
|
r = ARCHIVE_OK;
|
||
|
|
|
||
|
|
archive_read_free (archive);
|
||
|
|
archive_write_free (disk);
|
||
|
|
|
||
|
|
if (r != ARCHIVE_OK)
|
||
|
|
{
|
||
|
|
remove_tree (tmp);
|
||
|
|
g_free (tmp);
|
||
|
|
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Failed to extract theme archive.");
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
return tmp;
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
gboolean
|
||
|
|
zoitechat_gtk3_theme_service_import (const char *source_path, char **imported_id, GError **error)
|
||
|
|
{
|
||
|
|
char *input_root = NULL;
|
||
|
|
gboolean cleanup_input = FALSE;
|
||
|
|
GPtrArray *roots;
|
||
|
|
char *selected = NULL;
|
||
|
|
char *base;
|
||
|
|
char *dest_root;
|
||
|
|
char *user_dir;
|
||
|
|
char *candidate;
|
||
|
|
int suffix = 0;
|
||
|
|
gboolean ok;
|
||
|
|
|
||
|
|
if (!source_path || !*source_path)
|
||
|
|
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "No theme path provided."), FALSE;
|
||
|
|
|
||
|
|
if (g_file_test (source_path, G_FILE_TEST_IS_DIR))
|
||
|
|
input_root = g_strdup (source_path);
|
||
|
|
else
|
||
|
|
{
|
||
|
|
input_root = extract_archive (source_path, error);
|
||
|
|
cleanup_input = TRUE;
|
||
|
|
if (!input_root)
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
roots = g_ptr_array_new_with_free_func (g_free);
|
||
|
|
collect_theme_roots (input_root, roots, 0);
|
||
|
|
if (roots->len == 0)
|
||
|
|
{
|
||
|
|
if (cleanup_input)
|
||
|
|
remove_tree (input_root);
|
||
|
|
g_free (input_root);
|
||
|
|
g_ptr_array_unref (roots);
|
||
|
|
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, "No GTK3 gtk.css file found in the selected theme."), FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
selected = select_theme_root (roots, input_root);
|
||
|
|
if (!validate_theme_root_for_import (selected, error))
|
||
|
|
{
|
||
|
|
g_free (selected);
|
||
|
|
g_ptr_array_unref (roots);
|
||
|
|
if (cleanup_input)
|
||
|
|
remove_tree (input_root);
|
||
|
|
g_free (input_root);
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
base = g_path_get_basename (selected);
|
||
|
|
user_dir = zoitechat_gtk3_theme_service_get_user_themes_dir ();
|
||
|
|
g_mkdir_with_parents (user_dir, 0700);
|
||
|
|
|
||
|
|
candidate = g_strdup (base);
|
||
|
|
dest_root = g_build_filename (user_dir, candidate, NULL);
|
||
|
|
while (g_file_test (dest_root, G_FILE_TEST_EXISTS))
|
||
|
|
{
|
||
|
|
suffix++;
|
||
|
|
g_free (candidate);
|
||
|
|
g_free (dest_root);
|
||
|
|
candidate = g_strdup_printf ("%s-%d", base, suffix);
|
||
|
|
dest_root = g_build_filename (user_dir, candidate, NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
ok = copy_tree (selected, dest_root, error);
|
||
|
|
if (ok && imported_id)
|
||
|
|
*imported_id = path_build_id (dest_root, ZOITECHAT_GTK3_THEME_SOURCE_USER);
|
||
|
|
|
||
|
|
g_free (dest_root);
|
||
|
|
g_free (candidate);
|
||
|
|
g_free (user_dir);
|
||
|
|
g_free (base);
|
||
|
|
g_free (selected);
|
||
|
|
g_ptr_array_unref (roots);
|
||
|
|
if (cleanup_input)
|
||
|
|
remove_tree (input_root);
|
||
|
|
g_free (input_root);
|
||
|
|
return ok;
|
||
|
|
}
|
||
|
|
|
||
|
|
gboolean
|
||
|
|
zoitechat_gtk3_theme_service_remove_user_theme (const char *theme_id, GError **error)
|
||
|
|
{
|
||
|
|
ZoitechatGtk3Theme *theme = zoitechat_gtk3_theme_find_by_id (theme_id);
|
||
|
|
gboolean ok;
|
||
|
|
|
||
|
|
if (!theme)
|
||
|
|
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "Theme not found."), FALSE;
|
||
|
|
if (theme->source != ZOITECHAT_GTK3_THEME_SOURCE_USER)
|
||
|
|
{
|
||
|
|
zoitechat_gtk3_theme_free (theme);
|
||
|
|
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_PERM, "Only user-imported themes can be removed."), FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
remove_tree (theme->path);
|
||
|
|
ok = !g_file_test (theme->path, G_FILE_TEST_EXISTS);
|
||
|
|
zoitechat_gtk3_theme_free (theme);
|
||
|
|
if (!ok)
|
||
|
|
return g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Failed to remove theme."), FALSE;
|
||
|
|
return TRUE;
|
||
|
|
}
|