Index: gtk/Makefile.am
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/Makefile.am,v
retrieving revision 1.255
diff -u -p -r1.255 Makefile.am
--- gtk/Makefile.am	27 Sep 2004 18:37:33 -0000	1.255
+++ gtk/Makefile.am	17 Oct 2004 06:19:54 -0000
@@ -393,6 +393,8 @@ gtk_c_sources =                 \
 	gtkhsv.c		\
 	gtkhsv.h		\
 	gtkiconfactory.c	\
+	gtkiconcache.c		\
+	gtkiconcache.h		\
 	gtkicontheme.c		\
 	gtkiconthemeparser.c	\
 	gtkiconthemeparser.h	\
@@ -701,12 +703,18 @@ 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
 
Index: gtk/gtkdebug.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkdebug.h,v
retrieving revision 1.22
diff -u -p -r1.22 gtkdebug.h
--- gtk/gtkdebug.h	8 Oct 2004 15:26:02 -0000	1.22
+++ gtk/gtkdebug.h	17 Oct 2004 06:19:54 -0000
@@ -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
Index: gtk/gtkiconfactory.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkiconfactory.c,v
retrieving revision 1.72
diff -u -p -r1.72 gtkiconfactory.c
--- gtk/gtkiconfactory.c	16 Aug 2004 18:38:55 -0000	1.72
+++ gtk/gtkiconfactory.c	17 Oct 2004 06:19:54 -0000
@@ -30,6 +30,7 @@
 #include <string.h>
 #include "gtkalias.h"
 #include <pango/pango-utils.h>	/* For pango_scan_* */
+#include "gtkdebug.h"
 #include "gtkiconfactory.h"
 #include "stock-icons/gtkstockpixbufs.h"
 #include "gtkdebug.h"
Index: gtk/gtkicontheme.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkicontheme.c,v
retrieving revision 1.29
diff -u -p -r1.29 gtkicontheme.c
--- gtk/gtkicontheme.c	5 Oct 2004 17:06:00 -0000	1.29
+++ gtk/gtkicontheme.c	17 Oct 2004 06:19:55 -0000
@@ -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   (G
 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_
   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_them
 {
   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);
   
@@ -592,9 +619,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, (GFunc)_gtk_icon_cache_unref, 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 +861,7 @@ insert_theme (GtkIconTheme *icon_theme, 
   struct stat stat_buf;
   
   priv = icon_theme->priv;
-  
+
   for (l = priv->themes; l != NULL; l = l->next)
     {
       theme = l->data;
@@ -910,6 +941,7 @@ insert_theme (GtkIconTheme *icon_theme, 
   
   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 +1005,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 +1080,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 +1401,8 @@ gtk_icon_theme_get_icon_sizes (GtkIconTh
       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 +1610,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 +1716,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 +1778,7 @@ theme_lookup_icon (IconTheme          *t
     {
       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 +1822,9 @@ theme_lookup_icon (IconTheme          *t
   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 +1832,24 @@ theme_lookup_icon (IconTheme          *t
       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 +1876,21 @@ theme_list_icons (IconTheme *theme, GHas
 
       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 +1997,9 @@ scan_directory (GtkIconThemePrivate *ico
   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 +2108,35 @@ theme_subdir_load (GtkIconTheme *icon_th
 
   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)_gtk_icon_cache_unref);
+						   
+      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 +2147,14 @@ theme_subdir_load (GtkIconTheme *icon_th
 	  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);
 	}
Index: gtk/gtkmain.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkmain.c,v
retrieving revision 1.251
diff -u -p -r1.251 gtkmain.c
--- gtk/gtkmain.c	8 Oct 2004 15:26:02 -0000	1.251
+++ gtk/gtkmain.c	17 Oct 2004 06:19:55 -0000
@@ -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);
--- /dev/null	2004-10-16 23:43:43.121202296 -0400
+++ gtk/gtkiconcache.h	2004-10-17 01:50:03.221002912 -0400
@@ -0,0 +1,41 @@
+/* gtkiconcache.h
+ * Copyright (C) 2004  Anders Carlsson <andersca@gnome.org>
+ *
+ * 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 <gdk-pixbuf/gdk-pixbuf.h>
+
+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__ */
--- /dev/null	2004-10-16 23:43:43.121202296 -0400
+++ gtk/gtkiconcache.c	2004-10-17 02:01:46.023160744 -0400
@@ -0,0 +1,284 @@
+/* gtkiconcache.c
+ * Copyright (C) 2004  Anders Carlsson <andersca@gnome.org>
+ *
+ * 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 <config.h>
+#include "gtkdebug.h"
+#include "gtkiconcache.h"
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <string.h>
+
+#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;
+  
+  /* 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;
+}
+
+gboolean
+_gtk_icon_cache_has_icon (GtkIconCache *cache,
+			  const gchar  *icon_name)
+{
+}
+
+			     
+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 + 4 * j) ==
+		  directory_index)
+		g_hash_table_insert (hash_table, name, NULL);
+	    }
+
+	  chain_offset = GET_UINT32 (cache->buffer, chain_offset);
+	}
+    }
+  
+}
--- /dev/null	2004-10-16 23:43:43.121202296 -0400
+++ gtk/updateiconcache.c	2004-10-16 01:16:08.000000000 -0400
@@ -0,0 +1,619 @@
+/* updateiconcache.c
+ * Copyright (C) 2004  Anders Carlsson <andersca@gnome.org>
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <utime.h>
+
+#include <glib.h>
+
+#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)))
+
+void
+usage (void)
+{
+  g_printerr ("Usage: gtk-update-icon-cache <iconpath>\n");
+  exit (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;
+
+      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;
+	  
+	  image = g_hash_table_lookup (dir_hash, name);
+	  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 (name), image);
+	    }
+	}
+    }
+
+  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");
+}
+
+
+int
+main (int argc, char **argv)
+{
+  gchar *path;
+  
+  if (argc != 2 || strcmp (argv[1], "--help") == 0)
+    usage ();
+
+  path = argv[1];
+  
+  if (is_cache_up_to_date (path))
+    return 0;
+
+  build_cache (path);
+  
+  return 0;
+}
