]> Pileus Git - ~andy/gtk/blob - gtk/gtkiconcache.c
Remove a leftover debugging envvar.
[~andy/gtk] / gtk / gtkiconcache.c
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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include <config.h>
21 #include "gtkdebug.h"
22 #include "gtkiconcache.h"
23 #include <glib/gstdio.h>
24 #include <gdk-pixbuf/gdk-pixdata.h>
25
26 #ifdef HAVE_MMAP
27 #include <sys/mman.h>
28 #endif
29 #ifdef G_OS_WIN32
30 #include <windows.h>
31 #include <io.h>
32 #endif
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #ifdef HAVE_UNISTD_H
36 #include <unistd.h>
37 #endif
38 #include <fcntl.h>
39 #include <string.h>
40
41 #ifndef _O_BINARY
42 #define _O_BINARY 0
43 #endif
44
45 #ifndef MAP_FAILED
46 #define MAP_FAILED ((void *) -1)
47 #endif
48
49 #define MAJOR_VERSION 1
50 #define MINOR_VERSION 0
51
52 #define GET_UINT16(cache, offset) (GUINT16_FROM_BE (*(guint16 *)((cache) + (offset))))
53 #define GET_UINT32(cache, offset) (GUINT32_FROM_BE (*(guint32 *)((cache) + (offset))))
54
55 struct _GtkIconCache {
56   gint ref_count;
57
58   gsize size;
59   gchar *buffer;
60 #ifdef G_OS_WIN32
61   HANDLE handle;
62 #endif
63 };
64
65 GtkIconCache *
66 _gtk_icon_cache_ref (GtkIconCache *cache)
67 {
68   cache->ref_count ++;
69   return cache;
70 }
71
72 void
73 _gtk_icon_cache_unref (GtkIconCache *cache)
74 {
75   cache->ref_count --;
76
77   if (cache->ref_count == 0)
78     {
79       GTK_NOTE (ICONTHEME, 
80                 g_print ("unmapping icon cache\n"));
81 #ifdef HAVE_MMAP
82       munmap (cache->buffer, cache->size);
83 #endif
84 #ifdef G_OS_WIN32
85       UnmapViewOfFile (cache->buffer);
86       CloseHandle (cache->handle);
87 #endif
88       g_free (cache);
89     }
90 }
91
92 GtkIconCache *
93 _gtk_icon_cache_new_for_path (const gchar *path)
94 {
95   GtkIconCache *cache = NULL;
96
97 #if defined(HAVE_MMAP) || defined(G_OS_WIN32)
98   gchar *cache_filename;
99   gint fd = -1;
100   struct stat st;
101   struct stat path_st;
102   gchar *buffer = NULL;
103 #ifdef G_OS_WIN32
104   HANDLE handle = NULL;
105 #endif
106
107    /* Check if we have a cache file */
108   cache_filename = g_build_filename (path, "icon-theme.cache", NULL);
109
110   GTK_NOTE (ICONTHEME, 
111             g_print ("look for cache in %s\n", path));
112
113   if (!g_file_test (cache_filename, G_FILE_TEST_IS_REGULAR))
114     goto done;
115
116   if (g_stat (path, &path_st) < 0)
117     goto done;
118
119   /* Open the file and map it into memory */
120   fd = g_open (cache_filename, O_RDONLY|_O_BINARY, 0);
121
122   if (fd < 0)
123     {
124       g_free (cache_filename);
125       return NULL;
126     }
127   
128   if (fstat (fd, &st) < 0 || st.st_size < 4)
129     goto done;
130
131   /* Verify cache is uptodate */
132   if (st.st_mtime < path_st.st_mtime)
133     {
134       GTK_NOTE (ICONTHEME, 
135                 g_print ("cache outdated\n"));
136       goto done; 
137     }
138
139 #ifndef G_OS_WIN32
140   buffer = (gchar *) mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
141
142   if (buffer == MAP_FAILED)
143     goto done;
144 #else
145   handle = CreateFileMapping (_get_osfhandle (fd), NULL, PAGE_READONLY,
146                               0, 0, NULL);
147   if (handle == NULL)
148     goto done;
149
150   buffer = MapViewOfFile (handle, FILE_MAP_READ, 0, 0, 0);
151
152   if (buffer == NULL)
153     {
154       CloseHandle (handle);
155       goto done;
156     }
157 #endif
158
159   /* Verify version */
160   if (GET_UINT16 (buffer, 0) != MAJOR_VERSION ||
161       GET_UINT16 (buffer, 2) != MINOR_VERSION)
162     {
163 #ifndef G_OS_WIN32
164       munmap (buffer, st.st_size);
165 #else
166       UnmapViewOfFile (buffer);
167       CloseHandle (handle);
168 #endif
169       GTK_NOTE (ICONTHEME, 
170                 g_print ("wrong cache version\n"));
171       goto done;
172     }
173   
174   GTK_NOTE (ICONTHEME, 
175             g_print ("found cache for %s\n", path));
176
177   cache = g_new0 (GtkIconCache, 1);
178   cache->ref_count = 1;
179   cache->buffer = buffer;
180 #ifdef G_OS_WIN32
181   cache->handle = handle;
182 #endif
183   cache->size = st.st_size;
184  done:
185   g_free (cache_filename);  
186   if (fd != -1)
187     close (fd);
188
189 #endif  /* HAVE_MMAP || G_OS_WIN32 */
190
191   return cache;
192 }
193
194 static int
195 get_directory_index (GtkIconCache *cache,
196                      const gchar *directory)
197 {
198   guint32 dir_list_offset;
199   int n_dirs;
200   int i;
201   
202   dir_list_offset = GET_UINT32 (cache->buffer, 8);
203
204   n_dirs = GET_UINT32 (cache->buffer, dir_list_offset);
205
206   for (i = 0; i < n_dirs; i++)
207     {
208       guint32 name_offset = GET_UINT32 (cache->buffer, dir_list_offset + 4 + 4 * i);
209       gchar *name = cache->buffer + name_offset;
210       if (strcmp (name, directory) == 0)
211         return i;
212     }
213   
214   return -1;
215 }
216
217 gboolean
218 _gtk_icon_cache_has_directory (GtkIconCache *cache,
219                                const gchar *directory)
220 {
221   return get_directory_index (cache, directory) != -1;
222 }
223
224 static guint
225 icon_name_hash (gconstpointer key)
226 {
227   const signed char *p = key;
228   guint32 h = *p;
229
230   if (h)
231     for (p += 1; *p != '\0'; p++)
232       h = (h << 5) - h + *p;
233
234   return h;
235 }
236
237 static gint
238 find_image_offset (GtkIconCache *cache,
239                    const gchar  *icon_name,
240                    const gchar  *directory)
241 {
242   guint32 hash_offset;
243   guint32 n_buckets;
244   guint32 chain_offset;
245   int hash, directory_index;
246   guint32 image_list_offset, n_images;
247   gboolean found = FALSE;
248   int i;
249   
250   hash_offset = GET_UINT32 (cache->buffer, 4);
251   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
252
253   hash = icon_name_hash (icon_name) % n_buckets;
254
255   chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
256   while (chain_offset != 0xffffffff)
257     {
258       guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
259       gchar *name = cache->buffer + name_offset;
260
261       if (strcmp (name, icon_name) == 0)
262         {
263           found = TRUE;
264           break;
265         }
266           
267       chain_offset = GET_UINT32 (cache->buffer, chain_offset);
268     }
269
270   if (!found) {
271     return 0;
272   }
273
274   /* We've found an icon list, now check if we have the right icon in it */
275   directory_index = get_directory_index (cache, directory);
276   image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
277   n_images = GET_UINT32 (cache->buffer, image_list_offset);
278   
279   for (i = 0; i < n_images; i++)
280     {
281       if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * i) ==
282           directory_index) 
283         return image_list_offset + 4 + 8 * i;
284     }
285
286   return 0;
287 }
288
289 gint
290 _gtk_icon_cache_get_icon_flags (GtkIconCache *cache,
291                                 const gchar  *icon_name,
292                                 const gchar  *directory)
293 {
294   guint32 image_offset;
295
296   image_offset = find_image_offset (cache, icon_name, directory);
297
298   if (!image_offset)
299     return 0;
300
301   return GET_UINT16 (cache->buffer, image_offset + 2);
302 }
303
304 void
305 _gtk_icon_cache_add_icons (GtkIconCache *cache,
306                            const gchar  *directory,
307                            GHashTable   *hash_table)
308 {
309   int directory_index;
310   guint32 hash_offset, n_buckets;
311   guint32 chain_offset;
312   guint32 image_list_offset, n_images;
313   int i, j;
314   
315   directory_index = get_directory_index (cache, directory);
316
317   if (directory_index == -1)
318     return;
319   
320   hash_offset = GET_UINT32 (cache->buffer, 4);
321   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
322
323   for (i = 0; i < n_buckets; i++)
324     {
325       chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * i);
326       while (chain_offset != 0xffffffff)
327         {
328           guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
329           gchar *name = cache->buffer + name_offset;
330           
331           image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
332           n_images = GET_UINT32 (cache->buffer, image_list_offset);
333   
334           for (j = 0; j < n_images; j++)
335             {
336               if (GET_UINT16 (cache->buffer, image_list_offset + 4 + 8 * j) ==
337                   directory_index)
338                 g_hash_table_insert (hash_table, name, NULL);
339             }
340
341           chain_offset = GET_UINT32 (cache->buffer, chain_offset);
342         }
343     }  
344 }
345
346 gboolean
347 _gtk_icon_cache_has_icon (GtkIconCache *cache,
348                           const gchar  *icon_name)
349 {
350   guint32 hash_offset;
351   guint32 n_buckets;
352   guint32 chain_offset;
353   gint hash;
354   
355   hash_offset = GET_UINT32 (cache->buffer, 4);
356   n_buckets = GET_UINT32 (cache->buffer, hash_offset);
357
358   hash = icon_name_hash (icon_name) % n_buckets;
359
360   chain_offset = GET_UINT32 (cache->buffer, hash_offset + 4 + 4 * hash);
361   while (chain_offset != 0xffffffff)
362     {
363       guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
364       gchar *name = cache->buffer + name_offset;
365
366       if (strcmp (name, icon_name) == 0)
367         return TRUE;
368           
369       chain_offset = GET_UINT32 (cache->buffer, chain_offset);
370     }
371
372   return FALSE;
373 }
374                           
375 GdkPixbuf *
376 _gtk_icon_cache_get_icon (GtkIconCache *cache,
377                           const gchar  *icon_name,
378                           const gchar  *directory)
379 {
380   guint32 offset, image_data_offset, pixel_data_offset;
381   guint32 length, type;
382   GdkPixbuf *pixbuf;
383   GdkPixdata pixdata;
384   GError *error = NULL;
385
386   offset = find_image_offset (cache, icon_name, directory);
387   
388   image_data_offset = GET_UINT32 (cache->buffer, offset + 4);
389   
390   if (!image_data_offset)
391     return NULL;
392
393   pixel_data_offset = GET_UINT32 (cache->buffer, image_data_offset);
394
395   type = GET_UINT32 (cache->buffer, pixel_data_offset);
396
397   if (type != 0)
398     {
399       GTK_NOTE (ICONTHEME,
400                 g_print ("invalid pixel data type %d\n", type));
401       return NULL;
402     }
403
404   length = GET_UINT32 (cache->buffer, pixel_data_offset + 4);
405   
406   if (!gdk_pixdata_deserialize (&pixdata, length, 
407                                 cache->buffer + pixel_data_offset + 8,
408                                 &error))
409     {
410       GTK_NOTE (ICONTHEME,
411                 g_print ("could not deserialize data: %s\n", error->message));
412       g_error_free (error);
413
414       return NULL;
415     }
416
417   pixbuf = gdk_pixbuf_from_pixdata (&pixdata, FALSE, &error);
418
419   if (!pixbuf)
420     {
421       GTK_NOTE (ICONTHEME,
422                 g_print ("could not convert pixdata to pixbuf: %s\n", error->message));
423       g_error_free (error);
424
425       return NULL;
426     }
427
428   return pixbuf;
429 }
430
431 GtkIconData  *
432 _gtk_icon_cache_get_icon_data  (GtkIconCache *cache,
433                                 const gchar  *icon_name,
434                                 const gchar  *directory)
435 {
436   guint32 offset, image_data_offset, meta_data_offset;
437   GtkIconData *data;
438   int i;
439
440   offset = find_image_offset (cache, icon_name, directory);
441   if (!offset)
442     return NULL;
443
444   image_data_offset = GET_UINT32 (cache->buffer, offset + 4);
445   if (!image_data_offset)
446     return NULL;
447
448   meta_data_offset = GET_UINT32 (cache->buffer, image_data_offset + 4);
449   
450   if (!meta_data_offset)
451     return NULL;
452
453   data = g_new0 (GtkIconData, 1);
454
455   offset = GET_UINT32 (cache->buffer, meta_data_offset);
456   if (offset)
457     {
458       data->has_embedded_rect = TRUE;
459       data->x0 = GET_UINT16 (cache->buffer, offset);
460       data->y0 = GET_UINT16 (cache->buffer, offset + 2);
461       data->x1 = GET_UINT16 (cache->buffer, offset + 4);
462       data->y1 = GET_UINT16 (cache->buffer, offset + 6);
463     }
464
465   offset = GET_UINT32 (cache->buffer, meta_data_offset + 4);
466   if (offset)
467     {
468       data->n_attach_points = GET_UINT32 (cache->buffer, offset);
469       data->attach_points = g_new (GdkPoint, data->n_attach_points);
470       for (i = 0; i < data->n_attach_points; i++)
471         {
472           data->attach_points[i].x = GET_UINT16 (cache->buffer, offset + 4 + 4 * i); 
473           data->attach_points[i].y = GET_UINT16 (cache->buffer, offset + 4 + 4 * i + 2); 
474         }
475     }
476
477   offset = GET_UINT32 (cache->buffer, meta_data_offset + 8);
478   if (offset)
479     {
480       gint n_names;
481       gchar *lang, *name;
482       gchar **langs;
483       GHashTable *table = g_hash_table_new (g_str_hash, g_str_equal);
484
485       n_names = GET_UINT32 (cache->buffer, offset);
486       
487       for (i = 0; i < n_names; i++)
488         {
489           lang = cache->buffer + GET_UINT32 (cache->buffer, offset + 4 + 8 * i);
490           name = cache->buffer + GET_UINT32 (cache->buffer, offset + 4 + 8 * i + 4);
491           
492           g_hash_table_insert (table, lang, name);
493         }
494       
495       langs = (gchar **)g_get_language_names ();
496       for (i = 0; langs[i]; i++)
497         {
498           name = g_hash_table_lookup (table, langs[i]);
499           if (name)
500             {
501               data->display_name = g_strdup (name);
502               break;
503             }
504         }
505
506       g_hash_table_destroy (table);
507     }
508
509   return data;
510 }
511