2 * Copyright (C) 2004 Anders Carlsson <andersca@gnome.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
25 #include <sys/types.h>
32 #include <sys/utime.h>
38 #include <glib/gstdio.h>
39 #include <gdk-pixbuf/gdk-pixdata.h>
40 #include <glib/gi18n.h>
42 static gboolean force_update = FALSE;
43 static gboolean ignore_theme_index = FALSE;
44 static gboolean quiet = FALSE;
45 static gboolean index_only = FALSE;
46 static gchar *var_name = "-";
48 #define CACHE_NAME "icon-theme.cache"
50 #define HAS_SUFFIX_XPM (1 << 0)
51 #define HAS_SUFFIX_SVG (1 << 1)
52 #define HAS_SUFFIX_PNG (1 << 2)
53 #define HAS_ICON_FILE (1 << 3)
55 #define CAN_CACHE_IMAGE_DATA(flags) (!index_only && (((flags) & HAS_SUFFIX_PNG) || ((flags) & HAS_SUFFIX_XPM)))
57 #define MAJOR_VERSION 1
58 #define MINOR_VERSION 0
59 #define HASH_OFFSET 12
61 #define ALIGN_VALUE(this, boundary) \
62 (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
65 is_cache_up_to_date (const gchar *path)
67 struct stat path_stat, cache_stat;
71 retval = g_stat (path, &path_stat);
75 /* We can't stat the path,
76 * assume we have a updated cache */
80 cache_path = g_build_filename (path, CACHE_NAME, NULL);
81 retval = g_stat (cache_path, &cache_stat);
86 /* Cache file not found */
91 return cache_stat.st_mtime >= path_stat.st_mtime;
95 has_theme_index (const gchar *path)
100 index_path = g_build_filename (path, "index.theme", NULL);
102 result = g_file_test (index_path, G_FILE_TEST_IS_REGULAR);
112 gboolean has_pixdata;
115 guint pixel_data_size;
118 static GHashTable *image_data_hash = NULL;
125 ImageData *image_data;
126 guint pixel_data_size;
128 int has_embedded_rect;
135 char **display_names;
139 foreach_remove_func (gpointer key, gpointer value, gpointer user_data)
141 Image *image = (Image *)value;
142 GHashTable *files = user_data;
144 gboolean free_key = FALSE;
146 if (image->flags == HAS_ICON_FILE)
149 g_free (image->attach_points);
150 g_strfreev (image->display_names);
156 list = g_hash_table_lookup (files, key);
160 list = g_list_prepend (list, value);
161 g_hash_table_insert (files, key, list);
170 load_icon_data (Image *image, const char *path)
179 GError *error = NULL;
183 icon_file = g_key_file_new ();
184 g_key_file_set_list_separator (icon_file, ',');
185 g_key_file_load_from_file (icon_file, path, G_KEY_FILE_KEEP_TRANSLATIONS, &error);
188 g_error_free (error);
192 ivalues = g_key_file_get_integer_list (icon_file,
193 "Icon Data", "EmbeddedTextRectangle",
199 image->has_embedded_rect = TRUE;
200 image->x0 = ivalues[0];
201 image->y0 = ivalues[1];
202 image->x1 = ivalues[2];
203 image->y1 = ivalues[3];
209 str = g_key_file_get_string (icon_file, "Icon Data", "AttachPoints", NULL);
212 split = g_strsplit (str, "|", -1);
214 image->n_attach_points = g_strv_length (split);
215 image->attach_points = g_new (int, 2 * image->n_attach_points);
218 while (split[i] != NULL && i < image->n_attach_points)
220 split_point = strchr (split[i], ',');
225 image->attach_points[2 * i] = atoi (split[i]);
226 image->attach_points[2 * i + 1] = atoi (split_point);
235 keys = g_key_file_get_keys (icon_file, "Icon Data", &n_keys, &error);
236 image->display_names = g_new0 (gchar *, 2 * n_keys + 1);
237 image->n_display_names = 0;
239 for (i = 0; i < n_keys; i++)
243 if (g_str_has_prefix (keys[i], "DisplayName"))
245 gchar *open, *close = NULL;
247 open = strchr (keys[i], '[');
250 close = strchr (open, ']');
254 lang = g_strndup (open + 1, close - open - 1);
255 name = g_key_file_get_locale_string (icon_file,
256 "Icon Data", "DisplayName",
261 lang = g_strdup ("C");
262 name = g_key_file_get_string (icon_file,
263 "Icon Data", "DisplayName",
267 image->display_names[2 * image->n_display_names] = lang;
268 image->display_names[2 * image->n_display_names + 1] = name;
269 image->n_display_names++;
275 g_key_file_free (icon_file);
279 * This function was copied from gtkfilesystemunix.c, it should
280 * probably go to GLib
283 canonicalize_filename (gchar *filename)
286 gboolean last_was_slash = FALSE;
293 if (*p == G_DIR_SEPARATOR)
296 *q++ = G_DIR_SEPARATOR;
298 last_was_slash = TRUE;
302 if (last_was_slash && *p == '.')
304 if (*(p + 1) == G_DIR_SEPARATOR ||
307 if (*(p + 1) == '\0')
312 else if (*(p + 1) == '.' &&
313 (*(p + 2) == G_DIR_SEPARATOR ||
316 if (q > filename + 1)
319 while (q > filename + 1 &&
320 *(q - 1) != G_DIR_SEPARATOR)
324 if (*(p + 2) == '\0')
332 last_was_slash = FALSE;
338 last_was_slash = FALSE;
345 if (q > filename + 1 && *(q - 1) == G_DIR_SEPARATOR)
352 follow_links (const gchar *path)
358 path2 = g_strdup (path);
359 while (g_file_test (path2, G_FILE_TEST_IS_SYMLINK))
361 target = g_file_read_link (path2, NULL);
365 if (g_path_is_absolute (target))
369 d = g_path_get_dirname (path2);
370 s = g_build_filename (d, target, NULL);
381 if (strcmp (path, path2) == 0)
391 maybe_cache_image_data (Image *image,
394 if (CAN_CACHE_IMAGE_DATA(image->flags) && !image->image_data)
400 idata = g_hash_table_lookup (image_data_hash, path);
402 path2 = follow_links (path);
408 canonicalize_filename (path2);
410 idata2 = g_hash_table_lookup (image_data_hash, path2);
412 if (idata && idata2 && idata != idata2)
413 g_error (_("different idatas found for symlinked '%s' and '%s'\n"),
416 if (idata && !idata2)
417 g_hash_table_insert (image_data_hash, g_strdup (path2), idata);
419 if (!idata && idata2)
421 g_hash_table_insert (image_data_hash, g_strdup (path), idata2);
428 idata = g_new0 (ImageData, 1);
429 g_hash_table_insert (image_data_hash, g_strdup (path), idata);
431 g_hash_table_insert (image_data_hash, g_strdup (path2), idata);
434 if (!idata->has_pixdata)
436 pixbuf = gdk_pixbuf_new_from_file (path, NULL);
440 gdk_pixdata_from_pixbuf (&idata->pixdata, pixbuf, FALSE);
441 idata->pixel_data_size = idata->pixdata.length + 8;
442 idata->has_pixdata = TRUE;
446 image->image_data = idata;
454 scan_directory (const gchar *base_path,
460 GHashTable *dir_hash;
464 gboolean dir_added = FALSE;
465 guint dir_index = 0xffff;
467 dir_path = g_build_filename (base_path, subdir, NULL);
469 /* FIXME: Use the gerror */
470 dir = g_dir_open (dir_path, 0, NULL);
475 dir_hash = g_hash_table_new (g_str_hash, g_str_equal);
477 while ((name = g_dir_read_name (dir)))
483 gchar *basename, *dot;
485 path = g_build_filename (dir_path, name, NULL);
486 retval = g_file_test (path, G_FILE_TEST_IS_DIR);
492 subsubdir = g_build_filename (subdir, name, NULL);
494 subsubdir = g_strdup (name);
495 directories = scan_directory (base_path, subsubdir, files,
496 directories, depth + 1);
502 retval = g_file_test (path, G_FILE_TEST_IS_REGULAR);
505 if (g_str_has_suffix (name, ".png"))
506 flags |= HAS_SUFFIX_PNG;
507 else if (g_str_has_suffix (name, ".svg"))
508 flags |= HAS_SUFFIX_SVG;
509 else if (g_str_has_suffix (name, ".xpm"))
510 flags |= HAS_SUFFIX_XPM;
511 else if (g_str_has_suffix (name, ".icon"))
512 flags |= HAS_ICON_FILE;
517 basename = g_strdup (name);
518 dot = strrchr (basename, '.');
521 image = g_hash_table_lookup (dir_hash, basename);
524 image->flags |= flags;
525 maybe_cache_image_data (image, path);
534 dir_index = g_list_length (directories);
535 directories = g_list_append (directories, g_strdup (subdir));
541 image = g_new0 (Image, 1);
542 image->flags = flags;
543 image->dir_index = dir_index;
544 maybe_cache_image_data (image, path);
546 g_hash_table_insert (dir_hash, g_strdup (basename), image);
549 if (g_str_has_suffix (name, ".icon"))
550 load_icon_data (image, path);
560 /* Move dir into the big file hash */
561 g_hash_table_foreach_remove (dir_hash, foreach_remove_func, files);
563 g_hash_table_destroy (dir_hash);
568 typedef struct _HashNode HashNode;
578 icon_name_hash (gconstpointer key)
580 const signed char *p = key;
584 for (p += 1; *p != '\0'; p++)
585 h = (h << 5) - h + *p;
596 convert_to_hash (gpointer key, gpointer value, gpointer user_data)
598 HashContext *context = user_data;
602 hash = icon_name_hash (key) % context->size;
604 node = g_new0 (HashNode, 1);
607 node->image_list = value;
609 if (context->nodes[hash] != NULL)
610 node->next = context->nodes[hash];
612 context->nodes[hash] = node;
618 write_string (FILE *cache, const gchar *n)
623 l = ALIGN_VALUE (strlen (n) + 1, 4);
628 i = fwrite (s, l, 1, cache);
635 write_card16 (FILE *cache, guint16 n)
639 n = GUINT16_TO_BE (n);
641 i = fwrite ((char *)&n, 2, 1, cache);
647 write_card32 (FILE *cache, guint32 n)
651 n = GUINT32_TO_BE (n);
653 i = fwrite ((char *)&n, 4, 1, cache);
660 write_pixdata (FILE *cache, GdkPixdata *pixdata)
666 /* Type 0 is GdkPixdata */
667 if (!write_card32 (cache, 0))
670 s = gdk_pixdata_serialize (pixdata, &len);
672 if (!write_card32 (cache, len))
678 i = fwrite (s, len, 1, cache);
686 write_header (FILE *cache, guint32 dir_list_offset)
688 return (write_card16 (cache, MAJOR_VERSION) &&
689 write_card16 (cache, MINOR_VERSION) &&
690 write_card32 (cache, HASH_OFFSET) &&
691 write_card32 (cache, dir_list_offset));
695 get_image_meta_data_size (Image *image)
700 if (image->has_embedded_rect ||
701 image->attach_points > 0 ||
702 image->n_display_names > 0)
705 if (image->has_embedded_rect)
708 if (image->n_attach_points > 0)
709 len += 4 + image->n_attach_points * 4;
711 if (image->n_display_names > 0)
713 len += 4 + 8 * image->n_display_names;
715 for (i = 0; image->display_names[i]; i++)
716 len += ALIGN_VALUE (strlen (image->display_names[i]) + 1, 4);
723 get_image_pixel_data_size (Image *image)
725 if (image->pixel_data_size == 0)
727 if (image->image_data &&
728 image->image_data->has_pixdata)
730 image->pixel_data_size = image->image_data->pixel_data_size;
731 image->image_data->pixel_data_size = 0;
735 return image->pixel_data_size;
739 get_image_data_size (Image *image)
745 len += get_image_pixel_data_size (image);
746 len += get_image_meta_data_size (image);
748 if (len > 0 || (image->image_data && image->image_data->has_pixdata))
755 get_single_node_size (HashNode *node, gboolean include_image_data)
764 len += ALIGN_VALUE (strlen (node->name) + 1, 4);
767 len += 4 + g_list_length (node->image_list) * 8;
770 if (include_image_data)
771 for (list = node->image_list; list; list = list->next)
773 Image *image = list->data;
775 len += get_image_data_size (image);
782 get_bucket_size (HashNode *node)
787 len += get_single_node_size (node, TRUE);
796 write_bucket (FILE *cache, HashNode *node, int *offset)
800 int next_offset = *offset + get_single_node_size (node, TRUE);
801 int image_data_offset = *offset + get_single_node_size (node, FALSE);
808 if (node->next != NULL)
810 if (!write_card32 (cache, next_offset))
815 if (!write_card32 (cache, 0xffffffff))
819 /* Icon name offset */
820 if (!write_card32 (cache, *offset + 12))
823 /* Image list offset */
824 tmp = *offset + 12 + ALIGN_VALUE (strlen (node->name) + 1, 4);
825 if (!write_card32 (cache, tmp))
829 if (!write_string (cache, node->name))
833 len = g_list_length (node->image_list);
834 if (!write_card32 (cache, len))
837 /* Image data goes right after the image list */
840 list = node->image_list;
841 data_offset = image_data_offset;
842 for (i = 0; i < len; i++)
844 Image *image = list->data;
845 int image_data_size = get_image_data_size (image);
847 /* Directory index */
848 if (!write_card16 (cache, image->dir_index))
852 if (!write_card16 (cache, image->flags))
855 /* Image data offset */
856 if (image_data_size > 0)
858 if (!write_card32 (cache, data_offset))
860 data_offset += image_data_size;
864 if (!write_card32 (cache, 0))
871 /* Now write the image data */
872 list = node->image_list;
873 for (i = 0; i < len; i++, list = list->next)
875 Image *image = list->data;
876 int pixel_data_size = get_image_pixel_data_size (image);
877 int meta_data_size = get_image_meta_data_size (image);
879 if (get_image_data_size (image) == 0)
883 if (pixel_data_size > 0)
885 if (!write_card32 (cache, image_data_offset + 8))
888 image->image_data->offset = image_data_offset + 8;
894 if (image->image_data)
895 offset = image->image_data->offset;
899 if (!write_card32 (cache, offset))
903 if (meta_data_size > 0)
905 if (!write_card32 (cache, image_data_offset + pixel_data_size + 8))
910 if (!write_card32 (cache, 0))
914 if (pixel_data_size > 0)
916 if (!write_pixdata (cache, &image->image_data->pixdata))
920 if (meta_data_size > 0)
922 int ofs = image_data_offset + pixel_data_size + 20;
924 if (image->has_embedded_rect)
926 if (!write_card32 (cache, ofs))
933 if (!write_card32 (cache, 0))
937 if (image->n_attach_points > 0)
939 if (!write_card32 (cache, ofs))
942 ofs += 4 + 4 * image->n_attach_points;
946 if (!write_card32 (cache, 0))
950 if (image->n_display_names > 0)
952 if (!write_card32 (cache, ofs))
957 if (!write_card32 (cache, 0))
961 if (image->has_embedded_rect)
963 if (!write_card16 (cache, image->x0) ||
964 !write_card16 (cache, image->y0) ||
965 !write_card16 (cache, image->x1) ||
966 !write_card16 (cache, image->y1))
970 if (image->n_attach_points > 0)
972 if (!write_card32 (cache, image->n_attach_points))
975 for (j = 0; j < 2 * image->n_attach_points; j++)
977 if (!write_card16 (cache, image->attach_points[j]))
982 if (image->n_display_names > 0)
984 if (!write_card32 (cache, image->n_display_names))
987 ofs += 4 + 8 * image->n_display_names;
989 for (j = 0; j < 2 * image->n_display_names; j++)
991 if (!write_card32 (cache, ofs))
994 ofs += ALIGN_VALUE (strlen (image->display_names[j]) + 1, 4);
997 for (j = 0; j < 2 * image->n_display_names; j++)
999 if (!write_string (cache, image->display_names[j]))
1005 image_data_offset += pixel_data_size + meta_data_size + 8;
1008 *offset = next_offset;
1016 write_hash_table (FILE *cache, HashContext *context, int *new_offset)
1018 int offset = HASH_OFFSET;
1022 if (!(write_card32 (cache, context->size)))
1025 /* Size int + size * 4 */
1026 node_offset = offset + 4 + context->size * 4;
1028 for (i = 0; i < context->size; i++)
1030 if (context->nodes[i] != NULL)
1032 if (!write_card32 (cache, node_offset))
1035 node_offset += get_bucket_size (context->nodes[i]);
1039 if (!write_card32 (cache, 0xffffffff))
1046 *new_offset = node_offset;
1048 /* Now write the buckets */
1049 node_offset = offset + 4 + context->size * 4;
1051 for (i = 0; i < context->size; i++)
1053 if (!context->nodes[i])
1056 if (!write_bucket (cache, context->nodes[i], &node_offset))
1064 write_dir_index (FILE *cache, int offset, GList *directories)
1070 n_dirs = g_list_length (directories);
1072 if (!write_card32 (cache, n_dirs))
1075 offset += 4 + n_dirs * 4;
1077 for (d = directories; d; d = d->next)
1080 if (!write_card32 (cache, offset))
1083 offset += ALIGN_VALUE (strlen (dir) + 1, 4);
1086 for (d = directories; d; d = d->next)
1090 if (!write_string (cache, dir))
1098 write_file (FILE *cache, GHashTable *files, GList *directories)
1100 HashContext context;
1103 /* Convert the hash table into something looking a bit more
1104 * like what we want to write to disk.
1106 context.size = g_spaced_primes_closest (g_hash_table_size (files) / 3);
1107 context.nodes = g_new0 (HashNode *, context.size);
1109 g_hash_table_foreach_remove (files, convert_to_hash, &context);
1111 /* Now write the file */
1112 /* We write 0 as the directory list offset and go
1113 * back and change it later */
1114 if (!write_header (cache, 0))
1116 g_printerr (_("Failed to write header\n"));
1120 if (!write_hash_table (cache, &context, &new_offset))
1122 g_printerr (_("Failed to write hash table\n"));
1126 if (!write_dir_index (cache, new_offset, directories))
1128 g_printerr (_("Failed to write folder index\n"));
1134 if (!write_header (cache, new_offset))
1136 g_printerr (_("Failed to rewrite header\n"));
1144 build_cache (const gchar *path)
1146 gchar *cache_path, *tmp_cache_path;
1148 gchar *bak_cache_path = NULL;
1153 struct stat path_stat, cache_stat;
1154 struct utimbuf utime_buf;
1155 GList *directories = NULL;
1157 tmp_cache_path = g_build_filename (path, "."CACHE_NAME, NULL);
1158 cache = g_fopen (tmp_cache_path, "wb");
1162 g_printerr (_("Failed to write cache file: %s\n"), g_strerror (errno));
1166 files = g_hash_table_new (g_str_hash, g_str_equal);
1167 image_data_hash = g_hash_table_new (g_str_hash, g_str_equal);
1169 directories = scan_directory (path, NULL, files, NULL, 0);
1171 if (g_hash_table_size (files) == 0)
1173 /* Empty table, just close and remove the file */
1176 g_unlink (tmp_cache_path);
1180 /* FIXME: Handle failure */
1181 retval = write_file (cache, files, directories);
1184 g_list_foreach (directories, (GFunc)g_free, NULL);
1185 g_list_free (directories);
1189 g_unlink (tmp_cache_path);
1193 cache_path = g_build_filename (path, CACHE_NAME, NULL);
1196 if (g_file_test (cache_path, G_FILE_TEST_EXISTS))
1198 bak_cache_path = g_strconcat (cache_path, ".bak", NULL);
1199 g_unlink (bak_cache_path);
1200 if (g_rename (cache_path, bak_cache_path) == -1)
1202 g_printerr (_("Could not rename %s to %s: %s, removing %s then.\n"),
1203 cache_path, bak_cache_path,
1206 g_unlink (cache_path);
1207 bak_cache_path = NULL;
1212 if (g_rename (tmp_cache_path, cache_path) == -1)
1214 g_printerr (_("Could not rename %s to %s: %s\n"),
1215 tmp_cache_path, cache_path,
1216 g_strerror (errno));
1217 g_unlink (tmp_cache_path);
1219 if (bak_cache_path != NULL)
1220 if (g_rename (bak_cache_path, cache_path) == -1)
1221 g_printerr (_("Could not rename %s back to %s: %s.\n"),
1222 bak_cache_path, cache_path,
1223 g_strerror (errno));
1228 if (bak_cache_path != NULL)
1229 g_unlink (bak_cache_path);
1233 /* FIXME: What do do if an error occurs here? */
1234 if (g_stat (path, &path_stat) < 0 ||
1235 g_stat (cache_path, &cache_stat))
1238 utime_buf.actime = path_stat.st_atime;
1239 utime_buf.modtime = cache_stat.st_mtime;
1240 utime (path, &utime_buf);
1243 g_printerr (_("Cache file created successfully.\n"));
1247 write_csource (const gchar *path)
1254 cache_path = g_build_filename (path, CACHE_NAME, NULL);
1255 if (!g_file_get_contents (cache_path, &data, &len, NULL))
1258 g_printf ("#ifdef __SUNPRO_C\n");
1259 g_printf ("#pragma align 4 (%s)\n", var_name);
1260 g_printf ("#endif\n");
1262 g_printf ("#ifdef __GNUC__\n");
1263 g_printf ("static const guint8 %s[] __attribute__ ((__aligned__ (4))) = \n", var_name);
1264 g_printf ("#else\n");
1265 g_printf ("static const guint8 %s[] = \n", var_name);
1266 g_printf ("#endif\n");
1269 for (i = 0; i < len - 1; i++)
1273 g_printf ("0x%02x, ", (guint8)data[i]);
1278 g_printf ("0x%02x\n};\n", (guint8)data[i]);
1281 static GOptionEntry args[] = {
1282 { "force", 'f', 0, G_OPTION_ARG_NONE, &force_update, N_("Overwrite an existing cache, even if up to date"), NULL },
1283 { "ignore-theme-index", 't', 0, G_OPTION_ARG_NONE, &ignore_theme_index, N_("Don't check for the existence of index.theme"), NULL },
1284 { "index-only", 'i', 0, G_OPTION_ARG_NONE, &index_only, N_("Don't include image data in the cache"), NULL },
1285 { "source", 'c', 0, G_OPTION_ARG_STRING, &var_name, N_("Output a C header file"), "NAME" },
1286 { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, N_("Turn off verbose output"), NULL },
1291 main (int argc, char **argv)
1294 GOptionContext *context;
1299 setlocale (LC_ALL, "");
1301 bindtextdomain (GETTEXT_PACKAGE, GTK_LOCALEDIR);
1302 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
1304 context = g_option_context_new ("ICONPATH");
1305 g_option_context_add_main_entries (context, args, GETTEXT_PACKAGE);
1307 g_option_context_parse (context, &argc, &argv, NULL);
1311 path = g_locale_to_utf8 (path, -1, NULL, NULL, NULL);
1314 if (!ignore_theme_index && !has_theme_index (path))
1316 g_printerr (_("No theme index file in '%s'.\n"
1317 "If you really want to create an icon cache here, use --ignore-theme-index.\n"), path);
1321 if (!force_update && is_cache_up_to_date (path))
1327 if (strcmp (var_name, "-") != 0)
1328 write_csource (path);