1/* gtkiconcache.c
2 * Copyright (C) 2004 Anders Carlsson <andersca@gnome.org>
3 *
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.
8 *
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.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include "gtkdebug.h"
21#include "gtkiconcache.h"
22#include "gtkiconcachevalidator.h"
23
24#include <glib/gstdio.h>
25#include <gdk-pixbuf/gdk-pixdata.h>
26
27#ifdef HAVE_UNISTD_H
28#include <unistd.h>
29#endif
30#ifdef G_OS_WIN32
31#include <io.h>
32#endif
33#include <fcntl.h>
34#include <sys/types.h>
35#include <sys/stat.h>
36#include <string.h>
37
38
39#ifndef _O_BINARY
40#define _O_BINARY 0
41#endif
42
43#define MAJOR_VERSION 1
44#define MINOR_VERSION 0
45
46#define GET_UINT16(cache, offset) (GUINT16_FROM_BE (*(guint16 *)((cache) + (offset))))
47#define GET_UINT32(cache, offset) (GUINT32_FROM_BE (*(guint32 *)((cache) + (offset))))
48
49
50struct _GtkIconCache {
51 gint ref_count;
52
53 GMappedFile *map;
54 gchar *buffer;
55
56 guint32 last_chain_offset;
57};
58
59GtkIconCache *
60_gtk_icon_cache_ref (GtkIconCache *cache)
61{
62 cache->ref_count++;
63 return cache;
64}
65
66void
67_gtk_icon_cache_unref (GtkIconCache *cache)
68{
69 cache->ref_count --;
70
71 if (cache->ref_count == 0)
72 {
73 GTK_NOTE (ICONTHEME, g_message ("unmapping icon cache"));
74
75 if (cache->map)
76 g_mapped_file_unref (cache->map);
77 g_free (cache);
78 }
79}
80
81GtkIconCache *
82_gtk_icon_cache_new_for_path (const gchar *path)
83{
84 GtkIconCache *cache = NULL;
85 GMappedFile *map;
86
87 gchar *cache_filename;
88 gint fd = -1;
89 GStatBuf st;
90 GStatBuf path_st;
91
92 /* Check if we have a cache file */
93 cache_filename = g_build_filename (path, "icon-theme.cache", NULL);
94
95 GTK_NOTE (ICONTHEME, g_message ("look for icon cache in %s", path));
96
97 if (g_stat (path, &path_st) < 0)
98 goto done;
99
100 /* Open the file and map it into memory */
101 fd = g_open (cache_filename, O_RDONLY|_O_BINARY, 0);
102
103 if (fd < 0)
104 goto done;
105
106#ifdef G_OS_WIN32
107
108/* Bug 660730: _fstat32 is only defined in msvcrt80.dll+/VS 2005+ */
109/* or possibly in the msvcrt.dll linked to by the Windows DDK */
110/* (will need to check on the Windows DDK part later) */
111#if ((_MSC_VER >= 1400 || __MSVCRT_VERSION__ >= 0x0800) || defined (__MINGW64_VERSION_MAJOR)) && !defined(_WIN64)
112#undef fstat /* Just in case */
113#define fstat _fstat32
114#endif
115#endif
116
117 if (fstat (fd, &st) < 0 || st.st_size < 4)
118 goto done;
119
120 /* Verify cache is uptodate */
121 if (st.st_mtime < path_st.st_mtime)
122 {
123 GTK_NOTE (ICONTHEME, g_message ("icon cache outdated"));
124 goto done;
125 }
126
127 map = g_mapped_file_new (cache_filename, FALSE, NULL);
128
129 if (!map)
130 goto done;
131
132#ifdef G_ENABLE_DEBUG
133 if (GTK_DEBUG_CHECK (ICONTHEME))
134 {
135 CacheInfo info;
136
137 info.cache = g_mapped_file_get_contents (map);
138 info.cache_size = g_mapped_file_get_length (map);
139 info.n_directories = 0;
140 info.flags = CHECK_OFFSETS|CHECK_STRINGS;
141
142 if (!_gtk_icon_cache_validate (&info))
143 {
144 g_mapped_file_unref (map);
145 g_warning ("Icon cache '%s' is invalid", cache_filename);
146
147 goto done;
148 }
149 }
150#endif
151
152 GTK_NOTE (ICONTHEME, g_message ("found icon cache for %s", path));
153
154 cache = g_new0 (GtkIconCache, 1);
155 cache->ref_count = 1;
156 cache->map = map;
157 cache->buffer = g_mapped_file_get_contents (map);
158
159 done:
160 g_free (cache_filename);
161 if (fd >= 0)
162 close (fd);
163
164 return cache;
165}
166
167GtkIconCache *
168_gtk_icon_cache_new (const gchar *data)
169{
170 GtkIconCache *cache;
171
172 cache = g_new0 (GtkIconCache, 1);
173 cache->ref_count = 1;
174 cache->map = NULL;
175 cache->buffer = (gchar *)data;
176
177 return cache;
178}
179
180static gint
181get_directory_index (GtkIconCache *cache,
182 const gchar *directory)
183{
184 guint32 dir_list_offset;
185 gint n_dirs;
186 gint i;
187
188 dir_list_offset = GET_UINT32 (cache->buffer, 8);
189
190 n_dirs = GET_UINT32 (cache->buffer, dir_list_offset);
191
192 for (i = 0; i < n_dirs; i++)
193 {
194 guint32 name_offset = GET_UINT32 (cache->buffer, dir_list_offset + 4 + 4 * i);
195 gchar *name = cache->buffer + name_offset;
196 if (strcmp (name, directory) == 0)
197 return i;
198 }
199
200 return -1;
201}
202
203gint
204_gtk_icon_cache_get_directory_index (GtkIconCache *cache,
205 const gchar *directory)
206{
207 return get_directory_index (cache, directory);
208}
209
210static guint
211icon_name_hash (gconstpointer key)
212{
213 const signed char *p = key;
214 guint32 h = *p;
215
216 if (h)
217 for (p += 1; *p != '\0'; p++)
218 h = (h << 5) - h + *p;
219
220 return h;
221}
222
223static gint
224find_image_offset (GtkIconCache *cache,
225 const gchar *icon_name,
226 gint directory_index)
227{
228 guint32 hash_offset;
229 guint32 n_buckets;
230 guint32 chain_offset;
231 int hash;
232 guint32 image_list_offset, n_images;
233 int i;
234
235 if (!icon_name)
236 return 0;
237
238 chain_offset = cache->last_chain_offset;
239 if (chain_offset)
240 {
241 guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
242 gchar *name = cache->buffer + name_offset;
243
244 if (strcmp (name, icon_name) == 0)
245 goto find_dir;
246 }
247
248 hash_offset = GET_UINT32 (cache->buffer, 4);
249 n_buckets = GET_UINT32 (cache->buffer, hash_offset);
250 hash = icon_name_hash (icon_name) % n_buckets;
251
252 chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
253 while (chain_offset != 0xffffffff)
254 {
255 guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
256 gchar *name = cache->buffer + name_offset;
257
258 if (strcmp (name, icon_name) == 0)
259 {
260 cache->last_chain_offset = chain_offset;
261 goto find_dir;
262 }
263
264 chain_offset = GET_UINT32 (cache->buffer, chain_offset);
265 }
266
267 cache->last_chain_offset = 0;
268 return 0;
269
270find_dir:
271 /* We've found an icon list, now check if we have the right icon in it */
272 image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
273 n_images = GET_UINT32 (cache->buffer, image_list_offset);
274
275 for (i = 0; i < n_images; i++)
276 {
277 if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i) ==
278 directory_index)
279 return image_list_offset + 4 + 8 * i;
280 }
281
282 return 0;
283}
284
285gint
286_gtk_icon_cache_get_icon_flags (GtkIconCache *cache,
287 const gchar *icon_name,
288 gint directory_index)
289{
290 guint32 image_offset;
291
292 image_offset = find_image_offset (cache, icon_name, directory_index);
293
294 if (!image_offset)
295 return 0;
296
297 return GET_UINT16 (cache->buffer, image_offset + 2);
298}
299
300gboolean
301_gtk_icon_cache_has_icons (GtkIconCache *cache,
302 const gchar *directory)
303{
304 int directory_index;
305 guint32 hash_offset, n_buckets;
306 guint32 chain_offset;
307 guint32 image_list_offset, n_images;
308 int i, j;
309
310 directory_index = get_directory_index (cache, directory);
311
312 if (directory_index == -1)
313 return FALSE;
314
315 hash_offset = GET_UINT32 (cache->buffer, 4);
316 n_buckets = GET_UINT32 (cache->buffer, hash_offset);
317
318 for (i = 0; i < n_buckets; i++)
319 {
320 chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * i);
321 while (chain_offset != 0xffffffff)
322 {
323 image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
324 n_images = GET_UINT32 (cache->buffer, image_list_offset);
325
326 for (j = 0; j < n_images; j++)
327 {
328 if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * j) ==
329 directory_index)
330 return TRUE;
331 }
332
333 chain_offset = GET_UINT32 (cache->buffer, chain_offset);
334 }
335 }
336
337 return FALSE;
338}
339
340void
341_gtk_icon_cache_add_icons (GtkIconCache *cache,
342 const gchar *directory,
343 GHashTable *hash_table)
344{
345 int directory_index;
346 guint32 hash_offset, n_buckets;
347 guint32 chain_offset;
348 guint32 image_list_offset, n_images;
349 int i, j;
350
351 directory_index = get_directory_index (cache, directory);
352
353 if (directory_index == -1)
354 return;
355
356 hash_offset = GET_UINT32 (cache->buffer, 4);
357 n_buckets = GET_UINT32 (cache->buffer, hash_offset);
358
359 for (i = 0; i < n_buckets; i++)
360 {
361 chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * i);
362 while (chain_offset != 0xffffffff)
363 {
364 guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
365 gchar *name = cache->buffer + name_offset;
366
367 image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
368 n_images = GET_UINT32 (cache->buffer, image_list_offset);
369
370 for (j = 0; j < n_images; j++)
371 {
372 if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * j) ==
373 directory_index)
374 g_hash_table_insert (hash_table, name, NULL);
375 }
376
377 chain_offset = GET_UINT32 (cache->buffer, chain_offset);
378 }
379 }
380}
381
382gboolean
383_gtk_icon_cache_has_icon (GtkIconCache *cache,
384 const gchar *icon_name)
385{
386 guint32 hash_offset;
387 guint32 n_buckets;
388 guint32 chain_offset;
389 gint hash;
390
391 hash_offset = GET_UINT32 (cache->buffer, 4);
392 n_buckets = GET_UINT32 (cache->buffer, hash_offset);
393
394 hash = icon_name_hash (icon_name) % n_buckets;
395
396 chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
397 while (chain_offset != 0xffffffff)
398 {
399 guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
400 gchar *name = cache->buffer + name_offset;
401
402 if (strcmp (name, icon_name) == 0)
403 return TRUE;
404
405 chain_offset = GET_UINT32 (cache->buffer, chain_offset);
406 }
407
408 return FALSE;
409}
410
411gboolean
412_gtk_icon_cache_has_icon_in_directory (GtkIconCache *cache,
413 const gchar *icon_name,
414 const gchar *directory)
415{
416 guint32 hash_offset;
417 guint32 n_buckets;
418 guint32 chain_offset;
419 gint hash;
420 gboolean found_icon = FALSE;
421 gint directory_index;
422
423 directory_index = get_directory_index (cache, directory);
424
425 if (directory_index == -1)
426 return FALSE;
427
428 hash_offset = GET_UINT32 (cache->buffer, 4);
429 n_buckets = GET_UINT32 (cache->buffer, hash_offset);
430
431 hash = icon_name_hash (icon_name) % n_buckets;
432
433 chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
434 while (chain_offset != 0xffffffff)
435 {
436 guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
437 gchar *name = cache->buffer + name_offset;
438
439 if (strcmp (name, icon_name) == 0)
440 {
441 found_icon = TRUE;
442 break;
443 }
444
445 chain_offset = GET_UINT32 (cache->buffer, chain_offset);
446 }
447
448 if (found_icon)
449 {
450 guint32 image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
451 guint32 n_images = GET_UINT32 (cache->buffer, image_list_offset);
452 guint32 image_offset = image_list_offset + 4;
453 gint i;
454 for (i = 0; i < n_images; i++)
455 {
456 guint16 index = GET_UINT16 (cache->buffer, image_offset);
457
458 if (index == directory_index)
459 return TRUE;
460 image_offset += 8;
461 }
462 }
463
464 return FALSE;
465}
466
467static void
468pixbuf_destroy_cb (guchar *pixels,
469 gpointer data)
470{
471 GtkIconCache *cache = data;
472
473 _gtk_icon_cache_unref (cache);
474}
475
476GdkPixbuf *
477_gtk_icon_cache_get_icon (GtkIconCache *cache,
478 const gchar *icon_name,
479 gint directory_index)
480{
481 guint32 offset, image_data_offset, pixel_data_offset;
482 guint32 length, type;
483 GdkPixbuf *pixbuf;
484 GdkPixdata pixdata;
485 GError *error = NULL;
486
487 offset = find_image_offset (cache, icon_name, directory_index);
488
489 if (!offset)
490 return NULL;
491
492 image_data_offset = GET_UINT32 (cache->buffer, offset + 4);
493
494 if (!image_data_offset)
495 return NULL;
496
497 pixel_data_offset = GET_UINT32 (cache->buffer, image_data_offset);
498
499 type = GET_UINT32 (cache->buffer, pixel_data_offset);
500
501 if (type != 0)
502 {
503 GTK_NOTE (ICONTHEME, g_message ("invalid pixel data type %u", type));
504 return NULL;
505 }
506
507 length = GET_UINT32 (cache->buffer, pixel_data_offset + 4);
508
509G_GNUC_BEGIN_IGNORE_DEPRECATIONS
510 if (!gdk_pixdata_deserialize (&pixdata, length,
511 (guchar *)(cache->buffer + pixel_data_offset + 8),
512 &error))
513 {
514 GTK_NOTE (ICONTHEME, g_message ("could not deserialize data: %s", error->message));
515 g_error_free (error);
516
517 return NULL;
518 }
519G_GNUC_END_IGNORE_DEPRECATIONS
520
521 pixbuf = gdk_pixbuf_new_from_data (pixdata.pixel_data, GDK_COLORSPACE_RGB,
522 (pixdata.pixdata_type & GDK_PIXDATA_COLOR_TYPE_MASK) == GDK_PIXDATA_COLOR_TYPE_RGBA,
523 8, pixdata.width, pixdata.height, pixdata.rowstride,
524 (GdkPixbufDestroyNotify)pixbuf_destroy_cb,
525 cache);
526 if (!pixbuf)
527 {
528 GTK_NOTE (ICONTHEME, g_message ("could not convert pixdata to pixbuf: %s", error->message));
529 g_error_free (error);
530
531 return NULL;
532 }
533
534 _gtk_icon_cache_ref (cache);
535
536 return pixbuf;
537}
538
539