* Pidgin is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA #include "conversation.h" GSList *smiley_themes = NULL; struct smiley_theme *current_smiley_theme; static void pidgin_themes_destroy_smiley_theme_smileys(struct smiley_theme *theme); gboolean pidgin_themes_smileys_disabled() if (!current_smiley_theme) return strcmp(current_smiley_theme->name, "none") == 0; pidgin_themes_destroy_smiley_theme(struct smiley_theme *theme) pidgin_themes_destroy_smiley_theme_smileys(theme); static void pidgin_themes_remove_theme_dir(const char *theme_dir_name) const char *file_name = NULL; if ((theme_dir = g_dir_open(theme_dir_name, 0, NULL)) != NULL) { if ((str = g_string_new(theme_dir_name)) != NULL) { while ((file_name = g_dir_read_name(theme_dir)) != NULL) { g_string_printf(str, "%s%s%s", theme_dir_name, G_DIR_SEPARATOR_S, file_name); g_string_free(str, TRUE); void pidgin_themes_remove_smiley_theme(const char *file) char *theme_dir = NULL, *last_slash = NULL; g_return_if_fail(NULL != file); if (!g_file_test(file, G_FILE_TEST_EXISTS)) return; if ((theme_dir = g_strdup(file)) == NULL) return ; if ((last_slash = g_strrstr(theme_dir, G_DIR_SEPARATOR_S)) != NULL) { struct smiley_theme *theme = NULL, *new_theme = NULL; /* Delete files on disk */ pidgin_themes_remove_theme_dir(theme_dir); /* Find theme in themes list and remove it */ for (iter = smiley_themes ; iter ; iter = iter->next) { theme = ((struct smiley_theme *)(iter->data)); if (!strcmp(theme->path, file)) if (theme == current_smiley_theme) { new_theme = ((struct smiley_theme *)(NULL == iter->next ? (smiley_themes == iter ? NULL : smiley_themes->data) : iter->next->data)); purple_prefs_set_string(PIDGIN_PREFS_ROOT "/smileys/theme", new_theme->name); current_smiley_theme = NULL; smiley_themes = g_slist_delete_link(smiley_themes, iter); /* Destroy theme structure */ pidgin_themes_destroy_smiley_theme(theme); static void _pidgin_themes_smiley_themeize(GtkWidget *imhtml, gboolean custom) struct smiley_list *list; if (!current_smiley_theme) gtk_imhtml_remove_smileys(GTK_IMHTML(imhtml)); list = current_smiley_theme->list; char *sml = !strcmp(list->sml, "default") ? NULL : list->sml; GSList *icons = list->smileys; gtk_imhtml_associate_smiley(GTK_IMHTML(imhtml), sml, icons->data); icons = pidgin_smileys_get_all(); gtk_imhtml_associate_smiley(GTK_IMHTML(imhtml), sml, icons->data); void pidgin_themes_smiley_themeize(GtkWidget *imhtml) _pidgin_themes_smiley_themeize(imhtml, FALSE); void pidgin_themes_smiley_themeize_custom(GtkWidget *imhtml) _pidgin_themes_smiley_themeize(imhtml, TRUE); pidgin_themes_destroy_smiley_theme_smileys(struct smiley_theme *theme) GHashTable *already_freed; already_freed = g_hash_table_new(g_direct_hash, g_direct_equal); for (wer = theme->list; wer != NULL; wer = theme->list) { GtkIMHtmlSmiley *uio = wer->smileys->data; g_signal_handlers_disconnect_matched(uio->imhtml, G_SIGNAL_MATCH_DATA, g_object_unref(uio->icon); if (g_hash_table_lookup(already_freed, uio->file) == NULL) { g_hash_table_insert(already_freed, uio->file, GINT_TO_POINTER(1)); wer->smileys = g_slist_delete_link(wer->smileys, wer->smileys); g_hash_table_destroy(already_freed); pidgin_smiley_themes_remove_non_existing(void) static struct smiley_theme *theme = NULL; if (!smiley_themes) return ; for (iter = smiley_themes ; iter ; iter = iter->next) { theme = ((struct smiley_theme *)(iter->data)); if (!g_file_test(theme->path, G_FILE_TEST_EXISTS)) { if (theme == current_smiley_theme) current_smiley_theme = ((struct smiley_theme *)(NULL == iter->next ? NULL : iter->next->data)); pidgin_themes_destroy_smiley_theme(theme); /* Remove all elements whose data is NULL */ smiley_themes = g_slist_remove_all(smiley_themes, NULL); if (!current_smiley_theme && smiley_themes) { struct smiley_theme *smile = g_slist_last(smiley_themes)->data; pidgin_themes_load_smiley_theme(smile->path, TRUE); void pidgin_themes_load_smiley_theme(const char *file, gboolean load) FILE *f = g_fopen(file, "rb"); struct smiley_theme *theme=NULL; struct smiley_list *list = NULL; GSList *lst = smiley_themes; struct smiley_theme *thm = lst->data; if (!strcmp(thm->path, file)) { if (theme != NULL && theme == current_smiley_theme) { /* Don't reload the theme if it is already loaded */ theme = g_new0(struct smiley_theme, 1); theme->path = g_strdup(file); smiley_themes = g_slist_prepend(smiley_themes, theme); dirname = g_path_get_dirname(file); if (!fgets(buf, sizeof(buf), f)) { if (buf[0] == '#' || buf[0] == '\0') while (len && (buf[len - 1] == '\r' || buf[len - 1] == '\n')) if (! g_utf8_validate(buf, -1, NULL)) { purple_debug_error("gtkthemes", "%s:%" G_GSIZE_FORMAT " is invalid UTF-8\n", file, line_nbr); if (*i == '[' && strchr(i, ']') && load) { struct smiley_list *child = g_new0(struct smiley_list, 1); child->sml = g_strndup(i+1, strchr(i, ']') - i - 1); /* Reverse the Smiley list since it was built in reverse order for efficiency reasons */ list->smileys = g_slist_reverse(list->smileys); } else if (!g_ascii_strncasecmp(i, "Name=", strlen("Name="))) { theme->name = g_strdup(i + strlen("Name=")); } else if (!g_ascii_strncasecmp(i, "Description=", strlen("Description="))) { theme->desc = g_strdup(i + strlen("Description=")); } else if (!g_ascii_strncasecmp(i, "Icon=", strlen("Icon="))) { theme->icon = g_build_filename(dirname, i + strlen("Icon="), NULL); } else if (!g_ascii_strncasecmp(i, "Author=", strlen("Author="))) { theme->author = g_strdup(i + strlen("Author=")); } else if (load && list) { if (*i == '!' && *(i + 1) == ' ') { while (*i && !isspace(*i) && li < sizeof(l) - 1) { if (*i == '\\' && *(i+1) != '\0') /* coverity[tainted_data] */ next = g_utf8_next_char(i); if ((next - i) > (sizeof(l) - li -1)) { sfile = g_build_filename(dirname, l, NULL); GtkIMHtmlSmiley *smiley = gtk_imhtml_smiley_create(sfile, l, hidden, 0); list->smileys = g_slist_prepend(list->smileys, smiley); /* Reverse the Smiley list since it was built in reverse order for efficiency reasons */ list->smileys = g_slist_reverse(list->smileys); if (!theme->name || !theme->desc || !theme->author) { purple_debug_error("gtkthemes", "Invalid file format, not loading smiley theme from '%s'\n", file); smiley_themes = g_slist_remove(smiley_themes, theme); pidgin_themes_destroy_smiley_theme(theme); if (current_smiley_theme) pidgin_themes_destroy_smiley_theme_smileys(current_smiley_theme); current_smiley_theme = theme; for (cnv = purple_get_conversations(); cnv != NULL; cnv = cnv->next) { PurpleConversation *conv = cnv->data; if (PIDGIN_IS_PIDGIN_CONVERSATION(conv)) { /* We want to see our custom smileys on our entry if we write the shortcut */ pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml); pidgin_themes_smiley_themeize_custom(PIDGIN_CONVERSATION(conv)->entry); void pidgin_themes_smiley_theme_probe() pidgin_smiley_themes_remove_non_existing(); probedirs[0] = g_build_filename(DATADIR, "pixmaps", "pidgin", "emotes", NULL); probedirs[1] = g_build_filename(purple_user_dir(), "smileys", NULL); for (l=0; probedirs[l]; l++) { dir = g_dir_open(probedirs[l], 0, NULL); while ((file = g_dir_read_name(dir))) { test_path = g_build_filename(probedirs[l], file, NULL); if (g_file_test(test_path, G_FILE_TEST_IS_DIR)) { path = g_build_filename(probedirs[l], file, "theme", NULL); /* Here we check to see that the theme has proper syntax. * We set the second argument to FALSE so that it doesn't load pidgin_themes_load_smiley_theme(path, FALSE); if (g_mkdir(probedirs[l], S_IRUSR | S_IWUSR | S_IXUSR) != 0) { purple_debug_error("gtkthemes", "couldn't create smileys dir\n"); if (!current_smiley_theme && smiley_themes) { struct smiley_theme *smile = smiley_themes->data; pidgin_themes_load_smiley_theme(smile->path, TRUE); GSList *pidgin_themes_get_proto_smileys(const char *id) { struct smiley_list *list, *def; if ((current_smiley_theme == NULL) || (current_smiley_theme->list == NULL)) def = list = current_smiley_theme->list; proto = purple_find_prpl(id); if (!strcmp(list->sml, "default")) else if (proto && !strcmp(proto->info->name, list->sml)) return list ? list->smileys : def->smileys; void pidgin_themes_init() const char *current_theme = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/smileys/theme"); pidgin_themes_smiley_theme_probe(); for (l = smiley_themes; l; l = l->next) { struct smiley_theme *smile = l->data; if (smile->name && strcmp(current_theme, smile->name) == 0) { pidgin_themes_load_smiley_theme(smile->path, TRUE); /* If we still don't have a smiley theme, choose the first one */ if (!current_smiley_theme && smiley_themes) { struct smiley_theme *smile = smiley_themes->data; pidgin_themes_load_smiley_theme(smile->path, TRUE);