gtkiconcache.c 12.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* 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
Javier Jardón's avatar
Javier Jardón committed
15
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 17
 */

18
#include "config.h"
Matthias Clasen's avatar
Matthias Clasen committed
19

20 21
#include "gtkdebug.h"
#include "gtkiconcache.h"
22
#include "gtkiconcachevalidator.h"
23

24
#include <glib/gstdio.h>
Anders Carlsson's avatar
Anders Carlsson committed
25
#include <gdk-pixbuf/gdk-pixdata.h>
26 27 28 29

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
30 31 32
#ifdef G_OS_WIN32
#include <io.h>
#endif
33
#include <fcntl.h>
Matthias Clasen's avatar
Matthias Clasen committed
34 35
#include <sys/types.h>
#include <sys/stat.h>
36 37
#include <string.h>

Matthias Clasen's avatar
Matthias Clasen committed
38

39 40 41 42
#ifndef _O_BINARY
#define _O_BINARY 0
#endif

43 44 45 46 47 48
#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))))

49

50 51 52
struct _GtkIconCache {
  gint ref_count;

Matthias Clasen's avatar
Matthias Clasen committed
53
  GMappedFile *map;
54
  gchar *buffer;
55 56

  guint32 last_chain_offset;
57 58 59 60 61
};

GtkIconCache *
_gtk_icon_cache_ref (GtkIconCache *cache)
{
62
  cache->ref_count++;
63 64 65 66 67 68 69 70 71 72
  return cache;
}

void
_gtk_icon_cache_unref (GtkIconCache *cache)
{
  cache->ref_count --;

  if (cache->ref_count == 0)
    {
73
      GTK_NOTE (ICONTHEME, g_message ("unmapping icon cache"));
Matthias Clasen's avatar
Matthias Clasen committed
74

75
      if (cache->map)
76
	g_mapped_file_unref (cache->map);
77 78 79 80 81 82 83
      g_free (cache);
    }
}

GtkIconCache *
_gtk_icon_cache_new_for_path (const gchar *path)
{
84
  GtkIconCache *cache = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
85
  GMappedFile *map;
86

87
  gchar *cache_filename;
88
  gint fd = -1;
89 90
  GStatBuf st;
  GStatBuf path_st;
91

92
   /* Check if we have a cache file */
93 94
  cache_filename = g_build_filename (path, "icon-theme.cache", NULL);

95
  GTK_NOTE (ICONTHEME, g_message ("look for icon cache in %s", path));
96

97 98 99 100 101
  if (g_stat (path, &path_st) < 0)
    goto done;

  /* Open the file and map it into memory */
  fd = g_open (cache_filename, O_RDONLY|_O_BINARY, 0);
102 103

  if (fd < 0)
Matthias Clasen's avatar
Matthias Clasen committed
104
    goto done;
105 106

#ifdef G_OS_WIN32
107 108 109 110

/* Bug 660730: _fstat32 is only defined in msvcrt80.dll+/VS 2005+ */
/*             or possibly in the msvcrt.dll linked to by the Windows DDK */
/*             (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 113
#undef fstat /* Just in case */
#define fstat _fstat32  
114
#endif
115 116
#endif

117
  if (fstat (fd, &st) < 0 || st.st_size < 4)
118 119 120 121 122
    goto done;

  /* Verify cache is uptodate */
  if (st.st_mtime < path_st.st_mtime)
    {
123
      GTK_NOTE (ICONTHEME, g_message ("icon cache outdated"));
124 125 126
      goto done; 
    }

Matthias Clasen's avatar
Matthias Clasen committed
127
  map = g_mapped_file_new (cache_filename, FALSE, NULL);
128

Matthias Clasen's avatar
Matthias Clasen committed
129
  if (!map)
130 131
    goto done;

132
#ifdef G_ENABLE_DEBUG
133
  if (GTK_DEBUG_CHECK (ICONTHEME))
134
    {
135 136 137 138 139 140 141
      CacheInfo info;

      info.cache = g_mapped_file_get_contents (map);
      info.cache_size = g_mapped_file_get_length (map);
      info.n_directories = 0;
      info.flags = CHECK_OFFSETS|CHECK_STRINGS;

142 143
      if (!_gtk_icon_cache_validate (&info))
        {
144
          g_mapped_file_unref (map);
145
          g_warning ("Icon cache '%s' is invalid", cache_filename);
Matthias Clasen's avatar
Matthias Clasen committed
146

147 148
          goto done;
        }
149
    }
150 151
#endif 

152
  GTK_NOTE (ICONTHEME, g_message ("found icon cache for %s", path));
153 154 155

  cache = g_new0 (GtkIconCache, 1);
  cache->ref_count = 1;
Matthias Clasen's avatar
Matthias Clasen committed
156
  cache->map = map;
157
  cache->buffer = g_mapped_file_get_contents (map);
Matthias Clasen's avatar
Matthias Clasen committed
158

159
 done:
160
  g_free (cache_filename);  
Matthias Clasen's avatar
Matthias Clasen committed
161
  if (fd >= 0)
162
    close (fd);
163 164 165 166

  return cache;
}

167 168 169 170 171 172 173 174 175 176 177 178 179 180
GtkIconCache *
_gtk_icon_cache_new (const gchar *data)
{
  GtkIconCache *cache;

  cache = g_new0 (GtkIconCache, 1);
  cache->ref_count = 1;
  cache->map = NULL;
  cache->buffer = (gchar *)data;
  
  return cache;
}

static gint
181 182 183 184
get_directory_index (GtkIconCache *cache,
		     const gchar *directory)
{
  guint32 dir_list_offset;
185 186
  gint n_dirs;
  gint i;
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
  
  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;
}

203 204 205
gint
_gtk_icon_cache_get_directory_index (GtkIconCache *cache,
			             const gchar *directory)
206
{
207
  return get_directory_index (cache, directory);
208 209 210 211 212
}

static guint
icon_name_hash (gconstpointer key)
{
213 214
  const signed char *p = key;
  guint32 h = *p;
215 216 217 218 219 220 221 222

  if (h)
    for (p += 1; *p != '\0'; p++)
      h = (h << 5) - h + *p;

  return h;
}

223
static gint
Anders Carlsson's avatar
Anders Carlsson committed
224 225
find_image_offset (GtkIconCache *cache,
		   const gchar  *icon_name,
226
		   gint          directory_index)
227 228 229 230
{
  guint32 hash_offset;
  guint32 n_buckets;
  guint32 chain_offset;
231
  int hash;
232 233
  guint32 image_list_offset, n_images;
  int i;
234

235 236 237
  if (!icon_name)
    return 0;

238 239 240 241 242 243 244 245 246 247
  chain_offset = cache->last_chain_offset;
  if (chain_offset)
    {
      guint32 name_offset = GET_UINT32 (cache->buffer, chain_offset + 4);
      gchar *name = cache->buffer + name_offset;

      if (strcmp (name, icon_name) == 0)
        goto find_dir;
    }

248 249 250 251 252 253 254 255 256 257 258
  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)
259 260 261
        {
          cache->last_chain_offset = chain_offset;
          goto find_dir;
262
	}
263
  
264 265 266
      chain_offset = GET_UINT32 (cache->buffer, chain_offset);
    }

267 268
  cache->last_chain_offset = 0;
  return 0;
269

270
find_dir:
271 272 273 274 275 276 277
  /* We've found an icon list, now check if we have the right icon in it */
  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) ==
Anders Carlsson's avatar
Anders Carlsson committed
278 279
	  directory_index) 
	return image_list_offset + 4 + 8 * i;
280
    }
Anders Carlsson's avatar
Anders Carlsson committed
281

282 283 284
  return 0;
}

Anders Carlsson's avatar
Anders Carlsson committed
285 286 287
gint
_gtk_icon_cache_get_icon_flags (GtkIconCache *cache,
				const gchar  *icon_name,
288
				gint          directory_index)
Anders Carlsson's avatar
Anders Carlsson committed
289 290 291
{
  guint32 image_offset;

292
  image_offset = find_image_offset (cache, icon_name, directory_index);
Anders Carlsson's avatar
Anders Carlsson committed
293 294 295 296 297 298 299

  if (!image_offset)
    return 0;

  return GET_UINT16 (cache->buffer, image_offset + 2);
}

300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
gboolean
_gtk_icon_cache_has_icons (GtkIconCache *cache,
			   const gchar  *directory)
{
  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 FALSE;

  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)
	{
	  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)
		return TRUE;
	    }

	  chain_offset = GET_UINT32 (cache->buffer, chain_offset);
	}
    }

  return FALSE;
}

340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
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);
Anders Carlsson's avatar
Anders Carlsson committed
358

359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
  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);
	}
379 380 381 382 383 384 385 386 387 388 389
    }  
}

gboolean
_gtk_icon_cache_has_icon (GtkIconCache *cache,
			  const gchar  *icon_name)
{
  guint32 hash_offset;
  guint32 n_buckets;
  guint32 chain_offset;
  gint hash;
390
  
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
  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)
	return TRUE;
	  
      chain_offset = GET_UINT32 (cache->buffer, chain_offset);
    }

  return FALSE;
409
}
410

411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
gboolean
_gtk_icon_cache_has_icon_in_directory (GtkIconCache *cache,
				       const gchar  *icon_name,
				       const gchar  *directory)
{
  guint32 hash_offset;
  guint32 n_buckets;
  guint32 chain_offset;
  gint hash;
  gboolean found_icon = FALSE;
  gint directory_index;

  directory_index = get_directory_index (cache, directory);

  if (directory_index == -1)
    return FALSE;
  
  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_icon = TRUE;
	  break;
	}
	  
      chain_offset = GET_UINT32 (cache->buffer, chain_offset);
    }

  if (found_icon)
    {
      guint32 image_list_offset = GET_UINT32 (cache->buffer, chain_offset + 8);
      guint32 n_images =  GET_UINT32 (cache->buffer, image_list_offset);
      guint32 image_offset = image_list_offset + 4;
      gint i;
      for (i = 0; i < n_images; i++)
	{
	  guint16 index = GET_UINT16 (cache->buffer, image_offset);
	  
	  if (index == directory_index)
	    return TRUE;
	  image_offset += 8;
	}
    }

  return FALSE;
}

467 468 469 470 471 472 473 474 475
static void
pixbuf_destroy_cb (guchar   *pixels, 
		   gpointer  data)
{
  GtkIconCache *cache = data;

  _gtk_icon_cache_unref (cache);
}

Anders Carlsson's avatar
Anders Carlsson committed
476 477 478
GdkPixbuf *
_gtk_icon_cache_get_icon (GtkIconCache *cache,
			  const gchar  *icon_name,
479
			  gint          directory_index)
Anders Carlsson's avatar
Anders Carlsson committed
480 481 482 483 484 485 486
{
  guint32 offset, image_data_offset, pixel_data_offset;
  guint32 length, type;
  GdkPixbuf *pixbuf;
  GdkPixdata pixdata;
  GError *error = NULL;

487
  offset = find_image_offset (cache, icon_name, directory_index);
Anders Carlsson's avatar
Anders Carlsson committed
488
  
489 490 491
  if (!offset)
    return NULL;

Anders Carlsson's avatar
Anders Carlsson committed
492 493 494 495 496 497 498 499 500 501 502
  image_data_offset = GET_UINT32 (cache->buffer, offset + 4);
  
  if (!image_data_offset)
    return NULL;

  pixel_data_offset = GET_UINT32 (cache->buffer, image_data_offset);

  type = GET_UINT32 (cache->buffer, pixel_data_offset);

  if (type != 0)
    {
503
      GTK_NOTE (ICONTHEME, g_message ("invalid pixel data type %u", type));
Anders Carlsson's avatar
Anders Carlsson committed
504 505 506 507 508
      return NULL;
    }

  length = GET_UINT32 (cache->buffer, pixel_data_offset + 4);
  
509
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
Anders Carlsson's avatar
Anders Carlsson committed
510
  if (!gdk_pixdata_deserialize (&pixdata, length, 
511
				(guchar *)(cache->buffer + pixel_data_offset + 8),
Anders Carlsson's avatar
Anders Carlsson committed
512 513
				&error))
    {
514
      GTK_NOTE (ICONTHEME, g_message ("could not deserialize data: %s", error->message));
Anders Carlsson's avatar
Anders Carlsson committed
515 516 517 518
      g_error_free (error);

      return NULL;
    }
519
G_GNUC_END_IGNORE_DEPRECATIONS
Anders Carlsson's avatar
Anders Carlsson committed
520

521 522 523 524 525
  pixbuf = gdk_pixbuf_new_from_data (pixdata.pixel_data, GDK_COLORSPACE_RGB,
				     (pixdata.pixdata_type & GDK_PIXDATA_COLOR_TYPE_MASK) == GDK_PIXDATA_COLOR_TYPE_RGBA,
				     8, pixdata.width, pixdata.height, pixdata.rowstride,
				     (GdkPixbufDestroyNotify)pixbuf_destroy_cb, 
				     cache);
Anders Carlsson's avatar
Anders Carlsson committed
526 527
  if (!pixbuf)
    {
528
      GTK_NOTE (ICONTHEME, g_message ("could not convert pixdata to pixbuf: %s", error->message));
Anders Carlsson's avatar
Anders Carlsson committed
529 530 531 532 533
      g_error_free (error);

      return NULL;
    }

534 535
  _gtk_icon_cache_ref (cache);

Anders Carlsson's avatar
Anders Carlsson committed
536 537 538
  return pixbuf;
}