gtk2/gtk/gtkiconcache.c
Matthias Clasen 97ec1cdf4f Add a note regarding icon theme changes.
2005-08-22  Matthias Clasen  <mclasen@redhat.com>

	* gtk/gtkicontheme.c (gtk_icon_theme_load_icon): Add a note
	regarding icon theme changes.

	* gtk/gtkiconcache.c (_gtk_icon_cache_get_icon): When returning
	pixbufs which are backed by the mmapped memory of an icon cache,
	increase the refcount of the icon cache, so that the memory is not
	munmapped away underneath the pixbuf upon icon theme changes.
	(#314170, Kjartan Maraas)
2005-08-22 16:38:46 +00:00

483 lines
11 KiB
C

/* 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 <glib/gstdio.h>
#include <gdk-pixbuf/gdk-pixdata.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef G_OS_WIN32
#include <io.h>
#endif
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#ifndef _O_BINARY
#define _O_BINARY 0
#endif
#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;
GMappedFile *map;
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"));
g_mapped_file_free (cache->map);
g_free (cache);
}
}
GtkIconCache *
_gtk_icon_cache_new_for_path (const gchar *path)
{
GtkIconCache *cache = NULL;
GMappedFile *map;
gchar *cache_filename;
gint fd = -1;
struct stat st;
struct stat path_st;
gchar *buffer = 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_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);
if (fd < 0)
goto done;
if (fstat (fd, &st) < 0 || st.st_size < 4)
goto done;
/* Verify cache is uptodate */
if (st.st_mtime < path_st.st_mtime)
{
GTK_NOTE (ICONTHEME,
g_print ("cache outdated\n"));
goto done;
}
map = g_mapped_file_new (cache_filename, FALSE, NULL);
if (!map)
goto done;
/* Verify version */
buffer = g_mapped_file_get_contents (map);
if (GET_UINT16 (buffer, 0) != MAJOR_VERSION ||
GET_UINT16 (buffer, 2) != MINOR_VERSION)
{
g_mapped_file_free (map);
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->map = map;
cache->buffer = buffer;
done:
g_free (cache_filename);
if (fd >= 0)
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 signed char *p = key;
guint32 h = *p;
if (h)
for (p += 1; *p != '\0'; p++)
h = (h << 5) - h + *p;
return h;
}
static gint
find_image_offset (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 image_list_offset + 4 + 8 * i;
}
return 0;
}
gint
_gtk_icon_cache_get_icon_flags (GtkIconCache *cache,
const gchar *icon_name,
const gchar *directory)
{
guint32 image_offset;
image_offset = find_image_offset (cache, icon_name, directory);
if (!image_offset)
return 0;
return GET_UINT16 (cache->buffer, image_offset + 2);
}
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 + 8 * j) ==
directory_index)
g_hash_table_insert (hash_table, name, NULL);
}
chain_offset = GET_UINT32 (cache->buffer, chain_offset);
}
}
}
gboolean
_gtk_icon_cache_has_icon (GtkIconCache *cache,
const gchar *icon_name)
{
guint32 hash_offset;
guint32 n_buckets;
guint32 chain_offset;
gint hash;
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;
}
static void
pixbuf_destroy_cb (guchar *pixels,
gpointer data)
{
GtkIconCache *cache = data;
_gtk_icon_cache_unref (cache);
}
GdkPixbuf *
_gtk_icon_cache_get_icon (GtkIconCache *cache,
const gchar *icon_name,
const gchar *directory)
{
guint32 offset, image_data_offset, pixel_data_offset;
guint32 length, type;
GdkPixbuf *pixbuf;
GdkPixdata pixdata;
GError *error = NULL;
offset = find_image_offset (cache, icon_name, directory);
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)
{
GTK_NOTE (ICONTHEME,
g_print ("invalid pixel data type %d\n", type));
return NULL;
}
length = GET_UINT32 (cache->buffer, pixel_data_offset + 4);
if (!gdk_pixdata_deserialize (&pixdata, length,
cache->buffer + pixel_data_offset + 8,
&error))
{
GTK_NOTE (ICONTHEME,
g_print ("could not deserialize data: %s\n", error->message));
g_error_free (error);
return NULL;
}
pixbuf = gdk_pixbuf_from_pixdata (&pixdata, FALSE, &error);
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);
if (!pixbuf)
{
GTK_NOTE (ICONTHEME,
g_print ("could not convert pixdata to pixbuf: %s\n", error->message));
g_error_free (error);
return NULL;
}
_gtk_icon_cache_ref (cache);
return pixbuf;
}
GtkIconData *
_gtk_icon_cache_get_icon_data (GtkIconCache *cache,
const gchar *icon_name,
const gchar *directory)
{
guint32 offset, image_data_offset, meta_data_offset;
GtkIconData *data;
int i;
offset = find_image_offset (cache, icon_name, directory);
if (!offset)
return NULL;
image_data_offset = GET_UINT32 (cache->buffer, offset + 4);
if (!image_data_offset)
return NULL;
meta_data_offset = GET_UINT32 (cache->buffer, image_data_offset + 4);
if (!meta_data_offset)
return NULL;
data = g_new0 (GtkIconData, 1);
offset = GET_UINT32 (cache->buffer, meta_data_offset);
if (offset)
{
data->has_embedded_rect = TRUE;
data->x0 = GET_UINT16 (cache->buffer, offset);
data->y0 = GET_UINT16 (cache->buffer, offset + 2);
data->x1 = GET_UINT16 (cache->buffer, offset + 4);
data->y1 = GET_UINT16 (cache->buffer, offset + 6);
}
offset = GET_UINT32 (cache->buffer, meta_data_offset + 4);
if (offset)
{
data->n_attach_points = GET_UINT32 (cache->buffer, offset);
data->attach_points = g_new (GdkPoint, data->n_attach_points);
for (i = 0; i < data->n_attach_points; i++)
{
data->attach_points[i].x = GET_UINT16 (cache->buffer, offset + 4 + 4 * i);
data->attach_points[i].y = GET_UINT16 (cache->buffer, offset + 4 + 4 * i + 2);
}
}
offset = GET_UINT32 (cache->buffer, meta_data_offset + 8);
if (offset)
{
gint n_names;
gchar *lang, *name;
gchar **langs;
GHashTable *table = g_hash_table_new (g_str_hash, g_str_equal);
n_names = GET_UINT32 (cache->buffer, offset);
for (i = 0; i < n_names; i++)
{
lang = cache->buffer + GET_UINT32 (cache->buffer, offset + 4 + 8 * i);
name = cache->buffer + GET_UINT32 (cache->buffer, offset + 4 + 8 * i + 4);
g_hash_table_insert (table, lang, name);
}
langs = (gchar **)g_get_language_names ();
for (i = 0; langs[i]; i++)
{
name = g_hash_table_lookup (table, langs[i]);
if (name)
{
data->display_name = g_strdup (name);
break;
}
}
g_hash_table_destroy (table);
}
return data;
}