diff --git a/ChangeLog b/ChangeLog index c1a7c9d89a..e04e49c052 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,29 @@ +2004-10-19 Matthias Clasen + + Implement icon theme caching. (#154034, Martijn Vernooij, + caching schema proposed by Owen Taylor, initial implementation + by Anders Carlsson) + + * gtk/gtkdebug.h: + * gtk/gtkmain.c: Add a "icontheme" debug flag. + + * gtk/Makefile.am (gtk_c_sources): Add gtkiconcache.c + (gtk_private_h_sources): Add gtkiconcache.h + (bin_PROGRAMS): Add gtk-update-icon-cache + + * gtk/gtkicontheme.c: Use icon caches if they are available. + Currently, GTK+ uses the cache to get information about the + available sizes, image file formats and .icon files. The + actual image data, and the .icon file contents are not + cached yet. + + * gtk/updateiconcache.c: A cmdline utility for generating + icon cache files. + + * gtk/gtkiconcache.h: + * gtk/gtkiconcache.c: The glue code to mmap an icon cache + file and manage the information it contains. + 2004-10-19 Matthias Clasen * tests/testicontheme.c: Set the locale, tidy up output. diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index c1a7c9d89a..e04e49c052 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,29 @@ +2004-10-19 Matthias Clasen + + Implement icon theme caching. (#154034, Martijn Vernooij, + caching schema proposed by Owen Taylor, initial implementation + by Anders Carlsson) + + * gtk/gtkdebug.h: + * gtk/gtkmain.c: Add a "icontheme" debug flag. + + * gtk/Makefile.am (gtk_c_sources): Add gtkiconcache.c + (gtk_private_h_sources): Add gtkiconcache.h + (bin_PROGRAMS): Add gtk-update-icon-cache + + * gtk/gtkicontheme.c: Use icon caches if they are available. + Currently, GTK+ uses the cache to get information about the + available sizes, image file formats and .icon files. The + actual image data, and the .icon file contents are not + cached yet. + + * gtk/updateiconcache.c: A cmdline utility for generating + icon cache files. + + * gtk/gtkiconcache.h: + * gtk/gtkiconcache.c: The glue code to mmap an icon cache + file and manage the information it contains. + 2004-10-19 Matthias Clasen * tests/testicontheme.c: Set the locale, tidy up output. diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index c1a7c9d89a..e04e49c052 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,29 @@ +2004-10-19 Matthias Clasen + + Implement icon theme caching. (#154034, Martijn Vernooij, + caching schema proposed by Owen Taylor, initial implementation + by Anders Carlsson) + + * gtk/gtkdebug.h: + * gtk/gtkmain.c: Add a "icontheme" debug flag. + + * gtk/Makefile.am (gtk_c_sources): Add gtkiconcache.c + (gtk_private_h_sources): Add gtkiconcache.h + (bin_PROGRAMS): Add gtk-update-icon-cache + + * gtk/gtkicontheme.c: Use icon caches if they are available. + Currently, GTK+ uses the cache to get information about the + available sizes, image file formats and .icon files. The + actual image data, and the .icon file contents are not + cached yet. + + * gtk/updateiconcache.c: A cmdline utility for generating + icon cache files. + + * gtk/gtkiconcache.h: + * gtk/gtkiconcache.c: The glue code to mmap an icon cache + file and manage the information it contains. + 2004-10-19 Matthias Clasen * tests/testicontheme.c: Set the locale, tidy up output. diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index c1a7c9d89a..e04e49c052 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,29 @@ +2004-10-19 Matthias Clasen + + Implement icon theme caching. (#154034, Martijn Vernooij, + caching schema proposed by Owen Taylor, initial implementation + by Anders Carlsson) + + * gtk/gtkdebug.h: + * gtk/gtkmain.c: Add a "icontheme" debug flag. + + * gtk/Makefile.am (gtk_c_sources): Add gtkiconcache.c + (gtk_private_h_sources): Add gtkiconcache.h + (bin_PROGRAMS): Add gtk-update-icon-cache + + * gtk/gtkicontheme.c: Use icon caches if they are available. + Currently, GTK+ uses the cache to get information about the + available sizes, image file formats and .icon files. The + actual image data, and the .icon file contents are not + cached yet. + + * gtk/updateiconcache.c: A cmdline utility for generating + icon cache files. + + * gtk/gtkiconcache.h: + * gtk/gtkiconcache.c: The glue code to mmap an icon cache + file and manage the information it contains. + 2004-10-19 Matthias Clasen * tests/testicontheme.c: Set the locale, tidy up output. diff --git a/docs/reference/ChangeLog b/docs/reference/ChangeLog index f7f9929bfb..b5b11b1040 100644 --- a/docs/reference/ChangeLog +++ b/docs/reference/ChangeLog @@ -1,3 +1,11 @@ +2004-10-19 Matthias Clasen + + * gtk/gtk-update-icon-cache.xml: A man page for gtk-update-icon-cache. + + * gtk/gtk-docs.sgml: Add it here + + * gtk/Makefile.am (man_MANS): ...and here. + 2004-10-16 Matthias Clasen * gtk/glossary.xml: Additions. diff --git a/docs/reference/gtk/Makefile.am b/docs/reference/gtk/Makefile.am index 52b66bf38a..57ab11c30c 100644 --- a/docs/reference/gtk/Makefile.am +++ b/docs/reference/gtk/Makefile.am @@ -104,6 +104,7 @@ content_files = \ windows.sgml \ x11.sgml \ gtk-query-immodules-2.0.xml \ + gtk-update-icon-cache.xml \ visual_index.xml # Images to copy into HTML directory @@ -240,7 +241,7 @@ EXTRA_DIST += version.xml.in ######################################################################## -man_MANS = gtk-query-immodules-2.0.1 +man_MANS = gtk-query-immodules-2.0.1 gtk-update-icon-cache.1 if ENABLE_MAN diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml index 2ced4af893..462afea8a5 100644 --- a/docs/reference/gtk/gtk-docs.sgml +++ b/docs/reference/gtk/gtk-docs.sgml @@ -193,6 +193,7 @@ + ]> @@ -576,6 +577,7 @@ that is, GUI components such as GtkButton or GTK+ Tools >k-query-immodules; + >k-update-icon-cache; >k-glossary; diff --git a/docs/reference/gtk/gtk-update-icon-cache.1 b/docs/reference/gtk/gtk-update-icon-cache.1 new file mode 100644 index 0000000000..162a97fafc --- /dev/null +++ b/docs/reference/gtk/gtk-update-icon-cache.1 @@ -0,0 +1,49 @@ +.\"Generated by db2man.xsl. Don't modify this, modify the source. +.de Sh \" Subsection +.br +.if t .Sp +.ne 5 +.PP +\fB\\$1\fR +.PP +.. +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Ip \" List item +.br +.ie \\n(.$>=3 .ne \\$3 +.el .ne 3 +.IP "\\$1" \\$2 +.. +.TH "GTK-UPDATE-ICON-CA" 1 "" "" "" +.SH NAME +gtk-update-icon-cache \- Icon theme caching utility +.SH "SYNOPSIS" +.ad l +.hy 0 +.HP 22 +\fBgtk\-update\-icon\-cache\fR [\-\-force] {iconpath} +.ad +.hy + +.SH "DESCRIPTION" + +.PP + \fBgtk\-update\-icon\-cache\fR creates mmap()able cache files for icon themes\&. + +.PP +If expects to be given the path to a icon theme directory, e\&.g\&. \fI/usr/share/icons/hicolor\fR, and writes a \fIicon\-theme\&.cache\fR containing cached information about the icons in the directory tree below the given directory\&. + +.PP +GTK+ can use the cache files created by \fBgtk\-update\-icon\-cache\fR to avoid a lot of system call and disk seek overhead when the application starts\&. Since the format of the cache files allows them to be mmap()ed shared between multiple applications, the overall memory consumption is reduced as well\&. + +.PP +If called with the [\-\-force] argument, \fBgtk\-update\-icon\-cache\fR will overwrite an existing cache file even if it appears to be uptodate\&. + +.SH "BUGS" + +.PP +None known yet\&. + diff --git a/docs/reference/gtk/gtk-update-icon-cache.xml b/docs/reference/gtk/gtk-update-icon-cache.xml new file mode 100644 index 0000000000..e7baad89d5 --- /dev/null +++ b/docs/reference/gtk/gtk-update-icon-cache.xml @@ -0,0 +1,53 @@ + + + +gtk-update-icon-cache +1 + + + +gtk-update-icon-cache +Icon theme caching utility + + + + +gtk-update-icon-cache +--force +iconpath + + + +Description + +gtk-update-icon-cache creates mmap()able cache files for +icon themes. + + +If expects to be given the path to a icon theme directory, e.g. +/usr/share/icons/hicolor, and writes a +icon-theme.cache containing cached information +about the icons in the directory tree below the given directory. + + +GTK+ can use the cache files created by gtk-update-icon-cache +to avoid a lot of system call and disk seek overhead when the application starts. +Since the format of the cache files allows them to be mmap()ed shared between +multiple applications, the overall memory consumption is reduced as well. + + +If called with the --force argument, +gtk-update-icon-cache will overwrite an existing cache +file even if it appears to be uptodate. + + + +Bugs + +None known yet. + + + + + + diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 0a6987d690..8dcbc343f8 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -297,6 +297,7 @@ gtk_private_h_sources = \ gtkfilechooserutils.h \ gtkfilesystemunix.h \ gtkfilesystemmodel.h \ + gtkiconcache.h \ gtkpathbar.h \ gtkrbtree.h \ gtksequence.h \ @@ -393,6 +394,7 @@ gtk_c_sources = \ gtkhsv.c \ gtkhsv.h \ gtkiconfactory.c \ + gtkiconcache.c \ gtkicontheme.c \ gtkiconthemeparser.c \ gtkiconthemeparser.h \ @@ -701,13 +703,19 @@ LDADDS = \ # # Installed tools # -bin_PROGRAMS = gtk-query-immodules-2.0 +bin_PROGRAMS = gtk-query-immodules-2.0 gtk-update-icon-cache gtk_query_immodules_2_0_DEPENDENCIES = $(DEPS) gtk_query_immodules_2_0_LDADD = $(LDADDS) gtk_query_immodules_2_0_SOURCES = queryimmodules.c + +gtk_update_icon_cache_DEPENDENCIES = $(DEPS) +gtk_update_icon_cache_LDADD = $(LDADDS) + +gtk_update_icon_cache_SOURCES = updateiconcache.c + .PHONY: files test test-debug files: diff --git a/gtk/gtkdebug.h b/gtk/gtkdebug.h index ff1c0e74a2..2edfb41575 100644 --- a/gtk/gtkdebug.h +++ b/gtk/gtkdebug.h @@ -40,7 +40,8 @@ typedef enum { GTK_DEBUG_KEYBINDINGS = 1 << 5, GTK_DEBUG_MULTIHEAD = 1 << 6, GTK_DEBUG_MODULES = 1 << 7, - GTK_DEBUG_GEOMETRY = 1 << 8 + GTK_DEBUG_GEOMETRY = 1 << 8, + GTK_DEBUG_ICONTHEME = 1 << 9 } GtkDebugFlag; #ifdef G_ENABLE_DEBUG diff --git a/gtk/gtkiconcache.c b/gtk/gtkiconcache.c new file mode 100644 index 0000000000..766dfa0e23 --- /dev/null +++ b/gtk/gtkiconcache.c @@ -0,0 +1,280 @@ +/* gtkiconcache.c + * Copyright (C) 2004 Anders Carlsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include "gtkdebug.h" +#include "gtkiconcache.h" + +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include + +#define MAJOR_VERSION 1 +#define MINOR_VERSION 0 + +#define GET_UINT16(cache, offset) (GUINT16_FROM_BE (*(guint16 *)((cache) + (offset)))) +#define GET_UINT32(cache, offset) (GUINT32_FROM_BE (*(guint32 *)((cache) + (offset)))) + +struct _GtkIconCache { + gint ref_count; + + gsize size; + gchar *buffer; +}; + +GtkIconCache * +_gtk_icon_cache_ref (GtkIconCache *cache) +{ + cache->ref_count ++; + + return cache; +} + +void +_gtk_icon_cache_unref (GtkIconCache *cache) +{ + cache->ref_count --; + + if (cache->ref_count == 0) + { + GTK_NOTE (ICONTHEME, + g_print ("unmapping icon cache\n")); + + munmap (cache->buffer, cache->size); + g_free (cache); + } +} + +GtkIconCache * +_gtk_icon_cache_new_for_path (const gchar *path) +{ + gchar *cache_filename; + gint fd; + struct stat st; + struct stat path_st; + gchar *buffer; + GtkIconCache *cache = NULL; + + if (g_getenv ("GTK_NO_ICON_CACHE")) + return NULL; + + /* Check if we have a cache file */ + cache_filename = g_build_filename (path, "icon-theme.cache", NULL); + + GTK_NOTE (ICONTHEME, + g_print ("look for cache in %s\n", path)); + + if (!g_file_test (cache_filename, G_FILE_TEST_IS_REGULAR)) + { + g_free (cache_filename); + return NULL; + }; + + /* Open the file and mmap it */ + fd = open (cache_filename, O_RDONLY); + + g_free (cache_filename); + + if (fd < 0) + return NULL; + + if (fstat (fd, &st) < 0) + goto done; + + if (stat (path, &path_st) < 0) + goto done; + + /* Verify cache is uptodate */ + if (st.st_mtime < path_st.st_mtime) + { + GTK_NOTE (ICONTHEME, + g_print ("cache outdated\n")); + goto done; + } + + buffer = mmap (0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + + if (buffer == MAP_FAILED) + goto done; + + /* Verify version */ + if (GET_UINT16 (buffer, 0) != MAJOR_VERSION || + GET_UINT16 (buffer, 2) != MINOR_VERSION) + { + munmap (buffer, st.st_size); + GTK_NOTE (ICONTHEME, + g_print ("wrong cache version\n")); + goto done; + } + + GTK_NOTE (ICONTHEME, + g_print ("found cache for %s\n", path)); + + cache = g_new0 (GtkIconCache, 1); + cache->ref_count = 1; + cache->buffer = buffer; + cache->size = st.st_size; + + done: + close (fd); + + return cache; +} + +static int +get_directory_index (GtkIconCache *cache, + const gchar *directory) +{ + guint32 dir_list_offset; + int n_dirs; + int i; + + dir_list_offset = GET_UINT32 (cache->buffer, 8); + + n_dirs = GET_UINT32 (cache->buffer, dir_list_offset); + + for (i = 0; i < n_dirs; i++) + { + guint32 name_offset = GET_UINT32 (cache->buffer, dir_list_offset + 4 + 4 * i); + gchar *name = cache->buffer + name_offset; + if (strcmp (name, directory) == 0) + return i; + } + + return -1; +} + +gboolean +_gtk_icon_cache_has_directory (GtkIconCache *cache, + const gchar *directory) +{ + return get_directory_index (cache, directory) != -1; +} + +static guint +icon_name_hash (gconstpointer key) +{ + const char *p = key; + guint h = *p; + + if (h) + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + *p; + + return h; +} + +gint +_gtk_icon_cache_get_icon_flags (GtkIconCache *cache, + const gchar *icon_name, + const gchar *directory) +{ + guint32 hash_offset; + guint32 n_buckets; + guint32 chain_offset; + int hash, directory_index; + guint32 image_list_offset, n_images; + gboolean found = FALSE; + int i; + + hash_offset = GET_UINT32 (cache->buffer, 4); + n_buckets = GET_UINT32 (cache->buffer, hash_offset); + + hash = icon_name_hash (icon_name) % n_buckets; + + chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash); + while (chain_offset != 0xffffffff) + { + guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4); + gchar *name = cache->buffer + name_offset; + + if (strcmp (name, icon_name) == 0) + { + found = TRUE; + break; + } + + chain_offset = GET_UINT32 (cache->buffer, chain_offset); + } + + if (!found) + return 0; + + /* We've found an icon list, now check if we have the right icon in it */ + directory_index = get_directory_index (cache, directory); + image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8); + n_images = GET_UINT32 (cache->buffer, image_list_offset); + + for (i = 0; i < n_images; i++) + { + if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i) == + directory_index) + return GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i + 2); + } + + return 0; +} + +void +_gtk_icon_cache_add_icons (GtkIconCache *cache, + const gchar *directory, + GHashTable *hash_table) +{ + int directory_index; + guint32 hash_offset, n_buckets; + guint32 chain_offset; + guint32 image_list_offset, n_images; + int i, j; + + directory_index = get_directory_index (cache, directory); + + if (directory_index == -1) + return; + + hash_offset = GET_UINT32 (cache->buffer, 4); + n_buckets = GET_UINT32 (cache->buffer, hash_offset); + + for (i = 0; i < n_buckets; i++) + { + chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * i); + while (chain_offset != 0xffffffff) + { + guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4); + gchar *name = cache->buffer + name_offset; + + image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8); + n_images = GET_UINT32 (cache->buffer, image_list_offset); + + for (j = 0; j < n_images; j++) + { + if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * j) == + directory_index) + g_hash_table_insert (hash_table, name, NULL); + } + + chain_offset = GET_UINT32 (cache->buffer, chain_offset); + } + } + +} diff --git a/gtk/gtkiconcache.h b/gtk/gtkiconcache.h new file mode 100644 index 0000000000..ee9d48aeb3 --- /dev/null +++ b/gtk/gtkiconcache.h @@ -0,0 +1,41 @@ +/* gtkiconcache.h + * Copyright (C) 2004 Anders Carlsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef __GTK_ICON_CACHE_H__ +#define __GTK_ICON_CACHE_H__ + +#include + +typedef struct _GtkIconCache GtkIconCache; + +GtkIconCache *_gtk_icon_cache_new_for_path (const gchar *path); +gboolean _gtk_icon_cache_has_directory (GtkIconCache *cache, + const gchar *directory); +void _gtk_icon_cache_add_icons (GtkIconCache *cache, + const gchar *directory, + GHashTable *hash_table); + +gint _gtk_icon_cache_get_icon_flags (GtkIconCache *cache, + const gchar *icon_name, + const gchar *directory); + +GtkIconCache *_gtk_icon_cache_ref (GtkIconCache *cache); +void _gtk_icon_cache_unref (GtkIconCache *cache); + + +#endif /* __GTK_ICON_CACHE_H__ */ diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c index 6ac490e32d..b2053d620c 100644 --- a/gtk/gtkicontheme.c +++ b/gtk/gtkicontheme.c @@ -37,6 +37,7 @@ #include "gtkicontheme.h" #include "gtkiconthemeparser.h" +#include "gtkiconcache.h" #include "gtkintl.h" #include "gtksettings.h" #include "gtkprivate.h" @@ -61,7 +62,8 @@ typedef enum ICON_SUFFIX_NONE = 0, ICON_SUFFIX_XPM = 1 << 0, ICON_SUFFIX_SVG = 1 << 1, - ICON_SUFFIX_PNG = 1 << 2 + ICON_SUFFIX_PNG = 1 << 2, + HAS_ICON_FILE = 1 << 3 } IconSuffix; @@ -81,7 +83,8 @@ struct _GtkIconThemePrivate */ GList *themes; GHashTable *unthemed_icons; - + GList *unthemed_icons_caches; + /* Note: The keys of this hashtable are owned by the * themedir and unthemed hashtables. */ @@ -132,6 +135,11 @@ typedef struct char *comment; char *example; + /* Icon caches, per theme directory, key is NULL if + * no cache exists for that directory + */ + GHashTable *icon_caches; + /* In search order */ GList *dirs; } IconTheme; @@ -158,6 +166,9 @@ typedef struct int threshold; char *dir; + char *subdir; + + GtkIconCache *cache; GHashTable *icons; GHashTable *icon_data; @@ -204,6 +215,14 @@ static void do_theme_change (GtkIconTheme *icon_theme); static void blow_themes (GtkIconTheme *icon_themes); static void icon_data_free (GtkIconData *icon_data); +static void load_icon_data (IconThemeDir *dir, + const char *path, + const char *name); + +static IconSuffix theme_dir_get_icon_suffix (IconThemeDir *dir, + const gchar *icon_name, + gboolean *has_icon_file); + static GtkIconInfo *icon_info_new (void); static GtkIconInfo *icon_info_new_builtin (BuiltinIcon *icon); @@ -499,8 +518,12 @@ pixbuf_supports_svg () { GSList *formats = gdk_pixbuf_get_formats (); GSList *tmp_list; - gboolean found_svg = FALSE; + static gboolean found_svg = FALSE; + static gboolean value_known = FALSE; + if (value_known) + return found_svg; + for (tmp_list = formats; tmp_list && !found_svg; tmp_list = tmp_list->next) { gchar **mime_types = gdk_pixbuf_format_get_mime_types (tmp_list->data); @@ -516,7 +539,8 @@ pixbuf_supports_svg () } g_slist_free (formats); - + value_known = TRUE; + return found_svg; } @@ -553,7 +577,8 @@ gtk_icon_theme_init (GtkIconTheme *icon_theme) priv->themes_valid = FALSE; priv->themes = NULL; priv->unthemed_icons = NULL; - + priv->unthemed_icons_caches = NULL; + priv->pixbuf_supports_svg = pixbuf_supports_svg (); } @@ -569,6 +594,8 @@ do_theme_change (GtkIconTheme *icon_theme) { GtkIconThemePrivate *priv = icon_theme->priv; + GTK_NOTE (ICONTHEME, + g_print ("change to icon theme \"%s\"\n", priv->current_theme)); blow_themes (icon_theme); g_signal_emit (icon_theme, signal_changed, 0); @@ -579,6 +606,16 @@ do_theme_change (GtkIconTheme *icon_theme) } } +static void +free_cache (gpointer data, + gpointer user_data) +{ + GtkIconCache *cache = (GtkIconCache *)data; + + if (cache) + _gtk_icon_cache_unref (cache); +} + static void blow_themes (GtkIconTheme *icon_theme) { @@ -592,9 +629,13 @@ blow_themes (GtkIconTheme *icon_theme) g_list_foreach (priv->dir_mtimes, (GFunc)free_dir_mtime, NULL); g_list_free (priv->dir_mtimes); g_hash_table_destroy (priv->unthemed_icons); + if (priv->unthemed_icons_caches) + g_list_foreach (priv->unthemed_icons_caches, free_cache, NULL); + g_list_free (priv->unthemed_icons_caches); } priv->themes = NULL; priv->unthemed_icons = NULL; + priv->unthemed_icons_caches = NULL; priv->dir_mtimes = NULL; priv->all_icons = NULL; priv->themes_valid = FALSE; @@ -830,7 +871,7 @@ insert_theme (GtkIconTheme *icon_theme, const char *theme_name) struct stat stat_buf; priv = icon_theme->priv; - + for (l = priv->themes; l != NULL; l = l->next) { theme = l->data; @@ -910,6 +951,7 @@ insert_theme (GtkIconTheme *icon_theme, const char *theme_name) dirs = g_strsplit (directories, ",", 0); + theme->icon_caches = NULL; theme->dirs = NULL; for (i = 0; dirs[i] != NULL; i++) theme_subdir_load (icon_theme, theme, theme_file, dirs[i]); @@ -973,13 +1015,25 @@ load_themes (GtkIconTheme *icon_theme) /* Always look in the "default" icon theme */ insert_theme (icon_theme, DEFAULT_THEME_NAME); priv->themes = g_list_reverse (priv->themes); - + + priv->unthemed_icons = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)free_unthemed_icon); for (base = 0; base < icon_theme->priv->search_path_len; base++) { + GtkIconCache *cache; dir = icon_theme->priv->search_path[base]; + + cache = _gtk_icon_cache_new_for_path (dir); + + if (cache != NULL) + { + priv->unthemed_icons_caches = g_list_prepend (priv->unthemed_icons_caches, cache); + + continue; + } + gdir = g_dir_open (dir, 0, NULL); if (gdir == NULL) @@ -1036,7 +1090,7 @@ load_themes (GtkIconTheme *icon_theme) unthemed_icon->svg_filename = abs_file; else unthemed_icon->no_svg_filename = abs_file; - + g_hash_table_insert (priv->unthemed_icons, base_name, unthemed_icon); @@ -1357,8 +1411,8 @@ gtk_icon_theme_get_icon_sizes (GtkIconTheme *icon_theme, for (d = theme->dirs; d; d = d->next) { IconThemeDir *dir = d->data; - - suffix = GPOINTER_TO_UINT (g_hash_table_lookup (dir->icons, icon_name)); + + suffix = theme_dir_get_icon_suffix (dir, icon_name, NULL); if (suffix != ICON_SUFFIX_NONE) { if (suffix == ICON_SUFFIX_SVG) @@ -1566,16 +1620,25 @@ theme_destroy (IconTheme *theme) g_list_foreach (theme->dirs, (GFunc)theme_dir_destroy, NULL); g_list_free (theme->dirs); + + if (theme->icon_caches) + g_hash_table_destroy (theme->icon_caches); + g_free (theme); } static void theme_dir_destroy (IconThemeDir *dir) { - g_hash_table_destroy (dir->icons); + if (dir->cache) + _gtk_icon_cache_unref (dir->cache); + else + g_hash_table_destroy (dir->icons); + if (dir->icon_data) g_hash_table_destroy (dir->icon_data); g_free (dir->dir); + g_free (dir->subdir); g_free (dir); } @@ -1663,6 +1726,31 @@ best_suffix (IconSuffix suffix, return ICON_SUFFIX_NONE; } + +static IconSuffix +theme_dir_get_icon_suffix (IconThemeDir *dir, + const gchar *icon_name, + gboolean *has_icon_file) +{ + IconSuffix suffix; + + if (dir->cache) + { + suffix = (IconSuffix)_gtk_icon_cache_get_icon_flags (dir->cache, + icon_name, + dir->subdir); + + if (has_icon_file) + { + *has_icon_file = suffix & HAS_ICON_FILE; + } + } + else + suffix = GPOINTER_TO_UINT (g_hash_table_lookup (dir->icons, icon_name)); + + return suffix; +} + static GtkIconInfo * theme_lookup_icon (IconTheme *theme, const char *icon_name, @@ -1700,7 +1788,7 @@ theme_lookup_icon (IconTheme *theme, { dir = l->data; - suffix = GPOINTER_TO_UINT (g_hash_table_lookup (dir->icons, icon_name)); + suffix = theme_dir_get_icon_suffix (dir, icon_name, NULL); if (suffix != ICON_SUFFIX_NONE && (allow_svg || suffix != ICON_SUFFIX_SVG)) @@ -1744,8 +1832,9 @@ theme_lookup_icon (IconTheme *theme, if (min_dir) { GtkIconInfo *icon_info = icon_info_new (); + gboolean has_icon_file; - suffix = GPOINTER_TO_UINT (g_hash_table_lookup (min_dir->icons, icon_name)); + suffix = theme_dir_get_icon_suffix (min_dir, icon_name, &has_icon_file); suffix = best_suffix (suffix, allow_svg); g_assert (suffix != ICON_SUFFIX_NONE); @@ -1753,6 +1842,24 @@ theme_lookup_icon (IconTheme *theme, icon_info->filename = g_build_filename (min_dir->dir, file, NULL); g_free (file); + if (min_dir->cache && has_icon_file) + { + gchar *icon_file_name, *icon_file_path; + + icon_file_name = g_strconcat (icon_name, ".icon", NULL); + icon_file_path = g_build_filename (min_dir->dir, icon_file_name, NULL); + + if (g_file_test (icon_file_path, G_FILE_TEST_IS_REGULAR)) + { + if (min_dir->icon_data == NULL) + min_dir->icon_data = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify)icon_data_free); + load_icon_data (min_dir, icon_file_path, icon_file_name); + } + g_free (icon_file_name); + g_free (icon_file_path); + } + if (min_dir->icon_data != NULL) icon_info->data = g_hash_table_lookup (min_dir->icon_data, icon_name); @@ -1779,10 +1886,21 @@ theme_list_icons (IconTheme *theme, GHashTable *icons, if (context == dir->context || context == 0) - g_hash_table_foreach (dir->icons, - add_key_to_hash, - icons); - + { + if (dir->cache) + { + _gtk_icon_cache_add_icons (dir->cache, + dir->subdir, + icons); + + } + else + { + g_hash_table_foreach (dir->icons, + add_key_to_hash, + icons); + } + } l = l->next; } } @@ -1889,7 +2007,9 @@ scan_directory (GtkIconThemePrivate *icon_theme, char *base_name, *dot; char *path; IconSuffix suffix, hash_suffix; - + + GTK_NOTE (ICONTHEME, + g_print ("scanning directory %s\n", full_dir)); dir->icons = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); @@ -1998,11 +2118,35 @@ theme_subdir_load (GtkIconTheme *icon_theme, for (base = 0; base < icon_theme->priv->search_path_len; base++) { + GtkIconCache *cache; + gchar *theme_path; + full_dir = g_build_filename (icon_theme->priv->search_path[base], theme->name, subdir, NULL); - if (g_file_test (full_dir, G_FILE_TEST_IS_DIR)) + + /* First, see if we have a cache for the directory */ + if (!theme->icon_caches) + theme->icon_caches = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify)free_cache); + + theme_path = g_build_filename (icon_theme->priv->search_path[base], + theme->name, + NULL); + + if (!g_hash_table_lookup_extended (theme->icon_caches, theme_path, + NULL, (gpointer)&cache)) + { + /* This will return NULL if the cache doesn't exist or is outdated */ + cache = _gtk_icon_cache_new_for_path (theme_path); + + g_hash_table_insert (theme->icon_caches, g_strdup (theme_path), cache); + } + + g_free (theme_path); + + if (cache != NULL || g_file_test (full_dir, G_FILE_TEST_IS_DIR)) { dir = g_new (IconThemeDir, 1); dir->type = type; @@ -2013,8 +2157,14 @@ theme_subdir_load (GtkIconTheme *icon_theme, dir->threshold = threshold; dir->dir = full_dir; dir->icon_data = NULL; - - scan_directory (icon_theme->priv, dir, full_dir); + dir->subdir = g_strdup (subdir); + if (cache != NULL) + dir->cache = _gtk_icon_cache_ref (cache); + else + { + dir->cache = NULL; + scan_directory (icon_theme->priv, dir, full_dir); + } theme->dirs = g_list_prepend (theme->dirs, dir); } diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c index a078645912..40acba8fd6 100644 --- a/gtk/gtkmain.c +++ b/gtk/gtkmain.c @@ -158,7 +158,8 @@ static const GDebugKey gtk_debug_keys[] = { {"keybindings", GTK_DEBUG_KEYBINDINGS}, {"multihead", GTK_DEBUG_MULTIHEAD}, {"modules", GTK_DEBUG_MODULES}, - {"geometry", GTK_DEBUG_GEOMETRY} + {"geometry", GTK_DEBUG_GEOMETRY}, + {"icontheme", GTK_DEBUG_ICONTHEME} }; static const guint gtk_ndebug_keys = sizeof (gtk_debug_keys) / sizeof (GDebugKey); diff --git a/gtk/updateiconcache.c b/gtk/updateiconcache.c new file mode 100644 index 0000000000..e6957cff68 --- /dev/null +++ b/gtk/updateiconcache.c @@ -0,0 +1,628 @@ +/* updateiconcache.c + * Copyright (C) 2004 Anders Carlsson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define CACHE_NAME "icon-theme.cache" + +#define HAS_SUFFIX_XPM (1 << 0) +#define HAS_SUFFIX_SVG (1 << 1) +#define HAS_SUFFIX_PNG (1 << 2) +#define HAS_ICON_FILE (1 << 3) + +#define MAJOR_VERSION 1 +#define MINOR_VERSION 0 +#define HASH_OFFSET 12 + +#define ALIGN_VALUE(this, boundary) \ + (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1))) + +gboolean +is_cache_up_to_date (const gchar *path) +{ + struct stat path_stat, cache_stat; + gchar *cache_path; + int retval; + + retval = stat (path, &path_stat); + + if (retval < 0) + { + /* We can't stat the path, + * assume we have a updated cache */ + return TRUE; + } + + cache_path = g_build_filename (path, CACHE_NAME, NULL); + retval = stat (cache_path, &cache_stat); + g_free (cache_path); + + if (retval < 0 && errno == ENOENT) + { + /* Cache file not found */ + return FALSE; + } + + /* Check mtime */ + return cache_stat.st_mtime <= path_stat.st_mtime; +} + +typedef struct +{ + int flags; + int dir_index; +} Image; + +static gboolean +foreach_remove_func (gpointer key, gpointer value, gpointer user_data) +{ + GHashTable *files = user_data; + GList *list; + gboolean free_key = FALSE;; + + list = g_hash_table_lookup (files, key); + if (list) + free_key = TRUE; + + list = g_list_prepend (list, value); + g_hash_table_insert (files, key, list); + + if (free_key) + g_free (key); + + return TRUE; +} + +GList * +scan_directory (const gchar *base_path, + const gchar *subdir, + GHashTable *files, + GList *directories, + gint depth) +{ + GHashTable *dir_hash; + GDir *dir; + const gchar *name; + gchar *dir_path; + gboolean dir_added = FALSE; + guint dir_index = 0xffff; + + dir_path = g_build_filename (base_path, subdir, NULL); + + /* FIXME: Use the gerror */ + dir = g_dir_open (dir_path, 0, NULL); + + if (!dir) + return directories; + + dir_hash = g_hash_table_new (g_str_hash, g_str_equal); + + while ((name = g_dir_read_name (dir))) + { + gchar *path; + gboolean retval; + int flags = 0; + Image *image; + gchar *basename, *dot; + + path = g_build_filename (dir_path, name, NULL); + retval = g_file_test (path, G_FILE_TEST_IS_DIR); + if (retval) + { + gchar *subsubdir; + + if (subdir) + subsubdir = g_build_filename (subdir, name, NULL); + else + subsubdir = g_strdup (name); + directories = scan_directory (base_path, subsubdir, files, + directories, depth + 1); + g_free (subsubdir); + + continue; + } + + retval = g_file_test (path, G_FILE_TEST_IS_REGULAR); + g_free (path); + + if (retval) + { + if (g_str_has_suffix (name, ".png")) + flags |= HAS_SUFFIX_PNG; + else if (g_str_has_suffix (name, ".svg")) + flags |= HAS_SUFFIX_SVG; + else if (g_str_has_suffix (name, ".xpm")) + flags |= HAS_SUFFIX_XPM; + else if (g_str_has_suffix (name, ".icon")) + flags |= HAS_ICON_FILE; + + if (flags == 0) + continue; + + basename = g_strdup (name); + dot = strrchr (basename, '.'); + *dot = '\0'; + + image = g_hash_table_lookup (dir_hash, basename); + if (image) + image->flags |= flags; + else if ((flags & HAS_ICON_FILE) != HAS_ICON_FILE) + { + if (!dir_added) + { + dir_added = TRUE; + if (subdir) + { + dir_index = g_list_length (directories); + directories = g_list_append (directories, g_strdup (subdir)); + } + else + dir_index = 0xffff; + } + + image = g_new0 (Image, 1); + image->flags = flags; + image->dir_index = dir_index; + + g_hash_table_insert (dir_hash, g_strdup (basename), image); + } + + g_free (basename); + } + } + + g_dir_close (dir); + + /* Move dir into the big file hash */ + g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files); + + g_hash_table_destroy (dir_hash); + + return directories; +} + +typedef struct _HashNode HashNode; + +struct _HashNode +{ + HashNode *next; + gchar *name; + GList *image_list; +}; + +static guint +icon_name_hash (gconstpointer key) +{ + const char *p = key; + guint h = *p; + + if (h) + for (p += 1; *p != '\0'; p++) + h = (h << 5) - h + *p; + + return h; +} + +typedef struct { + gint size; + HashNode **nodes; +} HashContext; + +static gboolean +convert_to_hash (gpointer key, gpointer value, gpointer user_data) +{ + HashContext *context = user_data; + guint hash; + HashNode *node; + + hash = icon_name_hash (key) % context->size; + + node = g_new0 (HashNode, 1); + node->next = NULL; + node->name = key; + node->image_list = value; + + if (context->nodes[hash] != NULL) + node->next = context->nodes[hash]; + + context->nodes[hash] = node; + + return TRUE; +} + +gboolean +write_string (FILE *cache, const gchar *n) +{ + gchar *s; + int i, l; + + l = ALIGN_VALUE (strlen (n) + 1, 4); + + s = g_malloc0 (l); + strcpy (s, n); + + i = fwrite (s, l, 1, cache); + + return i == 1; + +} + +gboolean +write_card16 (FILE *cache, guint16 n) +{ + int i; + gchar s[2]; + + *((guint16 *)s) = GUINT16_TO_BE (n); + + i = fwrite (s, 2, 1, cache); + + return i == 1; +} + +gboolean +write_card32 (FILE *cache, guint32 n) +{ + int i; + gchar s[4]; + + *((guint32 *)s) = GUINT32_TO_BE (n); + + i = fwrite (s, 4, 1, cache); + + return i == 1; +} + +static gboolean +write_header (FILE *cache, guint32 dir_list_offset) +{ + return (write_card16 (cache, MAJOR_VERSION) && + write_card16 (cache, MINOR_VERSION) && + write_card32 (cache, HASH_OFFSET) && + write_card32 (cache, dir_list_offset)); +} + + +guint +get_single_node_size (HashNode *node) +{ + int len = 0; + + /* Node pointers */ + len += 12; + + /* Name */ + len += ALIGN_VALUE (strlen (node->name) + 1, 4); + + /* Image list */ + len += 4 + g_list_length (node->image_list) * 8; + + return len; +} + +guint +get_bucket_size (HashNode *node) +{ + int len = 0; + + while (node) + { + len += get_single_node_size (node); + + node = node->next; + } + + return len; +} + +gboolean +write_bucket (FILE *cache, HashNode *node, int *offset) +{ + while (node != NULL) + { + int next_offset = *offset + get_single_node_size (node); + int i, len; + GList *list; + + /* Chain offset */ + if (node->next != NULL) + { + if (!write_card32 (cache, next_offset)) + return FALSE; + } + else + { + if (!write_card32 (cache, 0xffffffff)) + return FALSE; + } + + /* Icon name offset */ + if (!write_card32 (cache, *offset + 12)) + return FALSE; + + /* Image list offset */ + if (!write_card32 (cache, *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4))) + return FALSE; + + /* Icon name */ + if (!write_string (cache, node->name)) + return FALSE; + + /* Image list */ + len = g_list_length (node->image_list); + if (!write_card32 (cache, len)) + return FALSE; + + list = node->image_list; + for (i = 0; i < len; i++) + { + Image *image = list->data; + + /* Directory index */ + if (!write_card16 (cache, image->dir_index)) + return FALSE; + + /* Flags */ + if (!write_card16 (cache, image->flags)) + return FALSE; + + /* Image data offset */ + if (!write_card32 (cache, 0)) + return FALSE; + + list = list->next; + } + + *offset = next_offset; + node = node->next; + } + + return TRUE; +} + +gboolean +write_hash_table (FILE *cache, HashContext *context, int *new_offset) +{ + int offset = HASH_OFFSET; + int node_offset; + int i; + + if (!(write_card32 (cache, context->size))) + return FALSE; + + /* Size int + size * 4 */ + node_offset = offset + 4 + context->size * 4; + + for (i = 0; i < context->size; i++) + { + if (context->nodes[i] != NULL) + { + if (!write_card32 (cache, node_offset)) + return FALSE; + + node_offset += get_bucket_size (context->nodes[i]); + } + else + { + if (!write_card32 (cache, 0xffffffff)) + { + return FALSE; + } + } + } + + *new_offset = node_offset; + + /* Now write the buckets */ + node_offset = offset + 4 + context->size * 4; + + for (i = 0; i < context->size; i++) + { + if (!context->nodes[i]) + continue; + + if (!write_bucket (cache, context->nodes[i], &node_offset)) + return FALSE; + } + + return TRUE; +} + +gboolean +write_dir_index (FILE *cache, int offset, GList *directories) +{ + int n_dirs; + GList *d; + char *dir; + + n_dirs = g_list_length (directories); + + if (!write_card32 (cache, n_dirs)) + return FALSE; + + offset += 4 + n_dirs * 4; + + for (d = directories; d; d = d->next) + { + dir = d->data; + if (!write_card32 (cache, offset)) + return FALSE; + + offset += ALIGN_VALUE (strlen (dir) + 1, 4); + } + + for (d = directories; d; d = d->next) + { + dir = d->data; + + if (!write_string (cache, dir)) + return FALSE; + } + + return TRUE; +} + +gboolean +write_file (FILE *cache, GHashTable *files, GList *directories) +{ + HashContext context; + int new_offset; + + /* Convert the hash table into something looking a bit more + * like what we want to write to disk. + */ + context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3); + context.nodes = g_new0 (HashNode *, context.size); + + g_hash_table_foreach_remove (files, convert_to_hash, &context); + + /* Now write the file */ + /* We write 0 as the directory list offset and go + * back and change it later */ + if (!write_header (cache, 0)) + { + g_printerr ("Failed to write header\n"); + return FALSE; + } + + if (!write_hash_table (cache, &context, &new_offset)) + { + g_printerr ("Failed to write hash table\n"); + return FALSE; + } + + if (!write_dir_index (cache, new_offset, directories)) + { + g_printerr ("Failed to write directory index\n"); + return FALSE; + } + + rewind (cache); + + if (!write_header (cache, new_offset)) + { + g_printerr ("Failed to rewrite header\n"); + return FALSE; + } + + return TRUE; +} + +void +build_cache (const gchar *path) +{ + gchar *cache_path, *tmp_cache_path; + GHashTable *files; + gboolean retval; + FILE *cache; + struct stat path_stat, cache_stat; + struct utimbuf utime_buf; + GList *directories = NULL; + + tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL); + cache = fopen (tmp_cache_path, "w"); + + if (!cache) + { + g_printerr ("Failed to write cache file: %s\n", g_strerror (errno)); + exit (1); + } + + files = g_hash_table_new (g_str_hash, g_str_equal); + + directories = scan_directory (path, NULL, files, NULL, 0); + + if (g_hash_table_size (files) == 0) + { + /* Empty table, just close and remove the file */ + + fclose (cache); + unlink (tmp_cache_path); + exit (0); + } + + /* FIXME: Handle failure */ + retval = write_file (cache, files, directories); + fclose (cache); + + g_list_foreach (directories, (GFunc)g_free, NULL); + g_list_free (directories); + + if (!retval) + { + unlink (tmp_cache_path); + exit (1); + } + + cache_path = g_build_filename (path, CACHE_NAME, NULL); + + if (rename (tmp_cache_path, cache_path) == -1) + { + unlink (tmp_cache_path); + exit (1); + } + + /* Update time */ + /* FIXME: What do do if an error occurs here? */ + stat (path, &path_stat); + stat (cache_path, &cache_stat); + + utime_buf.actime = path_stat.st_atime; + utime_buf.modtime = cache_stat.st_mtime; + utime (path, &utime_buf); + + g_printerr ("Cache file created successfully.\n"); +} + +static gboolean force_update = FALSE; + +static GOptionEntry args[] = { + { "force", 0, 0, G_OPTION_ARG_NONE, &force_update, "Overwrite an existing cache, even if uptodate", NULL }, + { NULL } +}; + +int +main (int argc, char **argv) +{ + gchar *path; + GOptionContext *context; + + context = g_option_context_new ("ICONPATH"); + g_option_context_add_main_entries (context, args, NULL); + + g_option_context_parse (context, &argc, &argv, NULL); + + path = argv[1]; + + if (!force_update && is_cache_up_to_date (path)) + return 0; + + build_cache (path); + + return 0; +}