summaryrefslogtreecommitdiffstats
path: root/src/st/st-texture-cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/st/st-texture-cache.c')
-rw-r--r--src/st/st-texture-cache.c1688
1 files changed, 1688 insertions, 0 deletions
diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c
new file mode 100644
index 0000000..7062221
--- /dev/null
+++ b/src/st/st-texture-cache.c
@@ -0,0 +1,1688 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * st-texture-cache.h: Object for loading and caching images as textures
+ *
+ * Copyright 2009, 2010 Red Hat, Inc.
+ * Copyright 2010, Maxim Ermilov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "st-image-content.h"
+#include "st-texture-cache.h"
+#include "st-private.h"
+#include "st-settings.h"
+#include <gtk/gtk.h>
+#include <math.h>
+#include <string.h>
+#include <glib.h>
+
+#define CACHE_PREFIX_ICON "icon:"
+#define CACHE_PREFIX_FILE "file:"
+#define CACHE_PREFIX_FILE_FOR_CAIRO "file-for-cairo:"
+
+struct _StTextureCachePrivate
+{
+ GtkIconTheme *icon_theme;
+ GSettings *settings;
+
+ /* Things that were loaded with a cache policy != NONE */
+ GHashTable *keyed_cache; /* char * -> ClutterImage* */
+ GHashTable *keyed_surface_cache; /* char * -> cairo_surface_t* */
+
+ GHashTable *used_scales; /* Set: double */
+
+ /* Presently this is used to de-duplicate requests for GIcons and async URIs. */
+ GHashTable *outstanding_requests; /* char * -> AsyncTextureLoadData * */
+
+ /* File monitors to evict cache data on changes */
+ GHashTable *file_monitors; /* char * -> GFileMonitor * */
+
+ GCancellable *cancellable;
+};
+
+static void st_texture_cache_dispose (GObject *object);
+static void st_texture_cache_finalize (GObject *object);
+
+enum
+{
+ ICON_THEME_CHANGED,
+ TEXTURE_FILE_CHANGED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+G_DEFINE_TYPE(StTextureCache, st_texture_cache, G_TYPE_OBJECT);
+
+/* We want to preserve the aspect ratio by default, also the default
+ * pipeline for an empty texture is full opacity white, which we
+ * definitely don't want. Skip that by setting 0 opacity.
+ */
+static ClutterActor *
+create_invisible_actor (void)
+{
+ return g_object_new (CLUTTER_TYPE_ACTOR,
+ "opacity", 0,
+ "request-mode", CLUTTER_REQUEST_CONTENT_SIZE,
+ NULL);
+}
+
+/* Reverse the opacity we added while loading */
+static void
+set_content_from_image (ClutterActor *actor,
+ ClutterContent *image)
+{
+ g_assert (image && CLUTTER_IS_IMAGE (image));
+
+ clutter_actor_set_content (actor, image);
+ clutter_actor_set_opacity (actor, 255);
+}
+
+static void
+st_texture_cache_class_init (StTextureCacheClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *)klass;
+
+ gobject_class->dispose = st_texture_cache_dispose;
+ gobject_class->finalize = st_texture_cache_finalize;
+
+ /**
+ * StTextureCache::icon-theme-changed:
+ * @self: a #StTextureCache
+ *
+ * Emitted when the icon theme is changed.
+ */
+ signals[ICON_THEME_CHANGED] =
+ g_signal_new ("icon-theme-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* no default handler slot */
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * StTextureCache::texture-file-changed:
+ * @self: a #StTextureCache
+ * @file: a #GFile
+ *
+ * Emitted when the source file of a texture is changed.
+ */
+ signals[TEXTURE_FILE_CHANGED] =
+ g_signal_new ("texture-file-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, /* no default handler slot */
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_FILE);
+}
+
+/* Evicts all cached textures for named icons */
+static void
+st_texture_cache_evict_icons (StTextureCache *cache)
+{
+ GHashTableIter iter;
+ gpointer key;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, cache->priv->keyed_cache);
+ while (g_hash_table_iter_next (&iter, &key, &value))
+ {
+ const char *cache_key = key;
+
+ /* This is too conservative - it takes out all cached textures
+ * for GIcons even when they aren't named icons, but it's not
+ * worth the complexity of parsing the key and calling
+ * g_icon_new_for_string(); icon theme changes aren't normal */
+ if (g_str_has_prefix (cache_key, CACHE_PREFIX_ICON))
+ g_hash_table_iter_remove (&iter);
+ }
+}
+
+static void
+on_icon_theme_changed (StSettings *settings,
+ GParamSpec *pspec,
+ StTextureCache *cache)
+{
+ g_autofree gchar *theme = NULL;
+
+ g_cancellable_cancel (cache->priv->cancellable);
+ g_cancellable_reset (cache->priv->cancellable);
+
+ st_texture_cache_evict_icons (cache);
+
+ g_object_get (settings, "gtk-icon-theme", &theme, NULL);
+ gtk_icon_theme_set_custom_theme (cache->priv->icon_theme, theme);
+
+ g_signal_emit (cache, signals[ICON_THEME_CHANGED], 0);
+}
+
+static void
+on_gtk_icon_theme_changed (GtkIconTheme *icon_theme,
+ StTextureCache *self)
+{
+ st_texture_cache_evict_icons (self);
+ g_signal_emit (self, signals[ICON_THEME_CHANGED], 0);
+}
+
+static void
+st_texture_cache_init (StTextureCache *self)
+{
+ StSettings *settings;
+
+ self->priv = g_new0 (StTextureCachePrivate, 1);
+
+ self->priv->icon_theme = gtk_icon_theme_new ();
+ gtk_icon_theme_add_resource_path (self->priv->icon_theme,
+ "/org/gnome/shell/icons");
+ g_signal_connect (self->priv->icon_theme, "changed",
+ G_CALLBACK (on_gtk_icon_theme_changed), self);
+
+ settings = st_settings_get ();
+ g_signal_connect (settings, "notify::gtk-icon-theme",
+ G_CALLBACK (on_icon_theme_changed), self);
+
+ self->priv->keyed_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_object_unref);
+ self->priv->keyed_surface_cache = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) cairo_surface_destroy);
+ self->priv->used_scales = g_hash_table_new_full (g_double_hash, g_double_equal,
+ g_free, NULL);
+ self->priv->outstanding_requests = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+ self->priv->file_monitors = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
+ g_object_unref, g_object_unref);
+
+ self->priv->cancellable = g_cancellable_new ();
+
+ on_icon_theme_changed (settings, NULL, self);
+}
+
+static void
+st_texture_cache_dispose (GObject *object)
+{
+ StTextureCache *self = (StTextureCache*)object;
+
+ g_cancellable_cancel (self->priv->cancellable);
+
+ g_clear_object (&self->priv->settings);
+ g_clear_object (&self->priv->icon_theme);
+ g_clear_object (&self->priv->cancellable);
+
+ g_clear_pointer (&self->priv->keyed_cache, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->keyed_surface_cache, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->used_scales, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->outstanding_requests, g_hash_table_destroy);
+ g_clear_pointer (&self->priv->file_monitors, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (st_texture_cache_parent_class)->dispose (object);
+}
+
+static void
+st_texture_cache_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (st_texture_cache_parent_class)->finalize (object);
+}
+
+static void
+compute_pixbuf_scale (gint width,
+ gint height,
+ gint available_width,
+ gint available_height,
+ gint *new_width,
+ gint *new_height)
+{
+ int scaled_width, scaled_height;
+
+ if (width == 0 || height == 0)
+ {
+ *new_width = *new_height = 0;
+ return;
+ }
+
+ if (available_width >= 0 && available_height >= 0)
+ {
+ /* This should keep the aspect ratio of the image intact, because if
+ * available_width < (available_height * width) / height
+ * then
+ * (available_width * height) / width < available_height
+ * So we are guaranteed to either scale the image to have an available_width
+ * for width and height scaled accordingly OR have the available_height
+ * for height and width scaled accordingly, whichever scaling results
+ * in the image that can fit both available dimensions.
+ */
+ scaled_width = MIN (available_width, (available_height * width) / height);
+ scaled_height = MIN (available_height, (available_width * height) / width);
+ }
+ else if (available_width >= 0)
+ {
+ scaled_width = available_width;
+ scaled_height = (available_width * height) / width;
+ }
+ else if (available_height >= 0)
+ {
+ scaled_width = (available_height * width) / height;
+ scaled_height = available_height;
+ }
+ else
+ {
+ scaled_width = scaled_height = 0;
+ }
+
+ /* Scale the image only if that will not increase its original dimensions. */
+ if (scaled_width > 0 && scaled_height > 0 && scaled_width < width && scaled_height < height)
+ {
+ *new_width = scaled_width;
+ *new_height = scaled_height;
+ }
+ else
+ {
+ *new_width = width;
+ *new_height = height;
+ }
+}
+
+static void
+rgba_from_clutter (GdkRGBA *rgba,
+ ClutterColor *color)
+{
+ rgba->red = color->red / 255.;
+ rgba->green = color->green / 255.;
+ rgba->blue = color->blue / 255.;
+ rgba->alpha = color->alpha / 255.;
+}
+
+/* A private structure for keeping width, height and scale. */
+typedef struct {
+ int width;
+ int height;
+ int scale;
+} Dimensions;
+
+/* This struct corresponds to a request for an texture.
+ * It's creasted when something needs a new texture,
+ * and destroyed when the texture data is loaded. */
+typedef struct {
+ StTextureCache *cache;
+ StTextureCachePolicy policy;
+ char *key;
+
+ guint width;
+ guint height;
+ guint paint_scale;
+ gfloat resource_scale;
+ GSList *actors;
+
+ GtkIconInfo *icon_info;
+ StIconColors *colors;
+ GFile *file;
+} AsyncTextureLoadData;
+
+static void
+texture_load_data_free (gpointer p)
+{
+ AsyncTextureLoadData *data = p;
+
+ if (data->icon_info)
+ {
+ g_object_unref (data->icon_info);
+ if (data->colors)
+ st_icon_colors_unref (data->colors);
+ }
+ else if (data->file)
+ g_object_unref (data->file);
+
+ if (data->key)
+ g_free (data->key);
+
+ if (data->actors)
+ g_slist_free_full (data->actors, (GDestroyNotify) g_object_unref);
+
+ g_free (data);
+}
+
+/**
+ * on_image_size_prepared:
+ * @pixbuf_loader: #GdkPixbufLoader loading the image
+ * @width: the original width of the image
+ * @height: the original height of the image
+ * @data: pointer to the #Dimensions structure containing available width and height for the image,
+ * available width or height can be -1 if the dimension is not limited
+ *
+ * Private function.
+ *
+ * Sets the size of the image being loaded to fit the available width and height dimensions,
+ * but never scales up the image beyond its actual size.
+ * Intended to be used as a callback for #GdkPixbufLoader "size-prepared" signal.
+ */
+static void
+on_image_size_prepared (GdkPixbufLoader *pixbuf_loader,
+ gint width,
+ gint height,
+ gpointer data)
+{
+ Dimensions *available_dimensions = data;
+ int available_width = available_dimensions->width;
+ int available_height = available_dimensions->height;
+ int scale_factor = available_dimensions->scale;
+ int scaled_width;
+ int scaled_height;
+
+ compute_pixbuf_scale (width, height, available_width, available_height,
+ &scaled_width, &scaled_height);
+
+ gdk_pixbuf_loader_set_size (pixbuf_loader,
+ scaled_width * scale_factor,
+ scaled_height * scale_factor);
+}
+
+static GdkPixbuf *
+impl_load_pixbuf_data (const guchar *data,
+ gsize size,
+ int available_width,
+ int available_height,
+ int scale,
+ GError **error)
+{
+ GdkPixbufLoader *pixbuf_loader = NULL;
+ GdkPixbuf *rotated_pixbuf = NULL;
+ GdkPixbuf *pixbuf;
+ gboolean success;
+ Dimensions available_dimensions;
+ int width_before_rotation, width_after_rotation;
+
+ pixbuf_loader = gdk_pixbuf_loader_new ();
+
+ available_dimensions.width = available_width;
+ available_dimensions.height = available_height;
+ available_dimensions.scale = scale;
+ g_signal_connect (pixbuf_loader, "size-prepared",
+ G_CALLBACK (on_image_size_prepared), &available_dimensions);
+
+ success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
+ if (!success)
+ goto out;
+ success = gdk_pixbuf_loader_close (pixbuf_loader, error);
+ if (!success)
+ goto out;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
+
+ width_before_rotation = gdk_pixbuf_get_width (pixbuf);
+
+ rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ width_after_rotation = gdk_pixbuf_get_width (rotated_pixbuf);
+
+ /* There is currently no way to tell if the pixbuf will need to be rotated before it is loaded,
+ * so we only check that once it is loaded, and reload it again if it needs to be rotated in order
+ * to use the available width and height correctly.
+ * See http://bugzilla.gnome.org/show_bug.cgi?id=579003
+ */
+ if (width_before_rotation != width_after_rotation)
+ {
+ g_object_unref (pixbuf_loader);
+ g_object_unref (rotated_pixbuf);
+ rotated_pixbuf = NULL;
+
+ pixbuf_loader = gdk_pixbuf_loader_new ();
+
+ /* We know that the image will later be rotated, so we reverse the available dimensions. */
+ available_dimensions.width = available_height;
+ available_dimensions.height = available_width;
+ available_dimensions.scale = scale;
+ g_signal_connect (pixbuf_loader, "size-prepared",
+ G_CALLBACK (on_image_size_prepared), &available_dimensions);
+
+ success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
+ if (!success)
+ goto out;
+
+ success = gdk_pixbuf_loader_close (pixbuf_loader, error);
+ if (!success)
+ goto out;
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (pixbuf_loader);
+
+ rotated_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
+ }
+
+out:
+ if (pixbuf_loader)
+ g_object_unref (pixbuf_loader);
+ return rotated_pixbuf;
+}
+
+static GdkPixbuf *
+impl_load_pixbuf_file (GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ float resource_scale,
+ GError **error)
+{
+ GdkPixbuf *pixbuf = NULL;
+ char *contents = NULL;
+ gsize size;
+
+ if (g_file_load_contents (file, NULL, &contents, &size, NULL, error))
+ {
+ int scale = ceilf (paint_scale * resource_scale);
+ pixbuf = impl_load_pixbuf_data ((const guchar *) contents, size,
+ available_width, available_height,
+ scale,
+ error);
+ }
+
+ g_free (contents);
+
+ return pixbuf;
+}
+
+static void
+load_pixbuf_thread (GTask *result,
+ gpointer source,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GdkPixbuf *pixbuf;
+ AsyncTextureLoadData *data = task_data;
+ GError *error = NULL;
+
+ g_assert (data != NULL);
+ g_assert (data->file != NULL);
+
+ pixbuf = impl_load_pixbuf_file (data->file, data->width, data->height,
+ data->paint_scale, data->resource_scale,
+ &error);
+
+ if (error != NULL)
+ g_task_return_error (result, error);
+ else if (pixbuf)
+ g_task_return_pointer (result, g_object_ref (pixbuf), g_object_unref);
+
+ g_clear_object (&pixbuf);
+}
+
+static GdkPixbuf *
+load_pixbuf_async_finish (StTextureCache *cache, GAsyncResult *result, GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static ClutterContent *
+pixbuf_to_st_content_image (GdkPixbuf *pixbuf,
+ int width,
+ int height,
+ int paint_scale,
+ float resource_scale)
+{
+ ClutterContent *image;
+ g_autoptr(GError) error = NULL;
+
+ float native_width, native_height;
+
+ native_width = ceilf (gdk_pixbuf_get_width (pixbuf) / resource_scale);
+ native_height = ceilf (gdk_pixbuf_get_height (pixbuf) / resource_scale);
+
+ if (width < 0 && height < 0)
+ {
+ width = native_width;
+ height = native_height;
+ }
+ else if (width < 0)
+ {
+ height *= paint_scale;
+ width = native_width * (height / native_height);
+ }
+ else if (height < 0)
+ {
+ width *= paint_scale;
+ height = native_height * (width / native_width);
+ }
+ else
+ {
+ width *= paint_scale;
+ height *= paint_scale;
+ }
+
+ image = st_image_content_new_with_preferred_size (width, height);
+ clutter_image_set_data (CLUTTER_IMAGE (image),
+ gdk_pixbuf_get_pixels (pixbuf),
+ gdk_pixbuf_get_has_alpha (pixbuf) ?
+ COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ gdk_pixbuf_get_rowstride (pixbuf),
+ &error);
+
+ if (error)
+ {
+ g_warning ("Failed to allocate texture: %s", error->message);
+ g_clear_object (&image);
+ }
+
+ return image;
+}
+
+static cairo_surface_t *
+pixbuf_to_cairo_surface (GdkPixbuf *pixbuf)
+{
+ cairo_surface_t *dummy_surface;
+ cairo_pattern_t *pattern;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+
+ dummy_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
+
+ cr = cairo_create (dummy_surface);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ pattern = cairo_get_source (cr);
+ cairo_pattern_get_surface (pattern, &surface);
+ cairo_surface_reference (surface);
+ cairo_destroy (cr);
+ cairo_surface_destroy (dummy_surface);
+
+ return surface;
+}
+
+static void
+finish_texture_load (AsyncTextureLoadData *data,
+ GdkPixbuf *pixbuf)
+{
+ g_autoptr(ClutterContent) image = NULL;
+ GSList *iter;
+ StTextureCache *cache;
+
+ cache = data->cache;
+
+ g_hash_table_remove (cache->priv->outstanding_requests, data->key);
+
+ if (pixbuf == NULL)
+ goto out;
+
+ if (data->policy != ST_TEXTURE_CACHE_POLICY_NONE)
+ {
+ gpointer orig_key = NULL, value = NULL;
+
+ if (!g_hash_table_lookup_extended (cache->priv->keyed_cache, data->key,
+ &orig_key, &value))
+ {
+ image = pixbuf_to_st_content_image (pixbuf,
+ data->width, data->height,
+ data->paint_scale,
+ data->resource_scale);
+ if (!image)
+ goto out;
+
+ g_hash_table_insert (cache->priv->keyed_cache, g_strdup (data->key),
+ g_object_ref (image));
+ }
+ else
+ {
+ image = g_object_ref (value);
+ }
+ }
+ else
+ {
+ image = pixbuf_to_st_content_image (pixbuf,
+ data->width, data->height,
+ data->paint_scale,
+ data->resource_scale);
+ if (!image)
+ goto out;
+ }
+
+ for (iter = data->actors; iter; iter = iter->next)
+ {
+ ClutterActor *actor = iter->data;
+ set_content_from_image (actor, image);
+ }
+
+out:
+ texture_load_data_free (data);
+}
+
+static void
+on_symbolic_icon_loaded (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GdkPixbuf *pixbuf;
+ pixbuf = gtk_icon_info_load_symbolic_finish (GTK_ICON_INFO (source), result, NULL, NULL);
+ finish_texture_load (user_data, pixbuf);
+ g_clear_object (&pixbuf);
+}
+
+static void
+on_icon_loaded (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GdkPixbuf *pixbuf;
+ pixbuf = gtk_icon_info_load_icon_finish (GTK_ICON_INFO (source), result, NULL);
+ finish_texture_load (user_data, pixbuf);
+ g_clear_object (&pixbuf);
+}
+
+static void
+on_pixbuf_loaded (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GdkPixbuf *pixbuf;
+ pixbuf = load_pixbuf_async_finish (ST_TEXTURE_CACHE (source), result, NULL);
+ finish_texture_load (user_data, pixbuf);
+ g_clear_object (&pixbuf);
+}
+
+static void
+load_texture_async (StTextureCache *cache,
+ AsyncTextureLoadData *data)
+{
+ if (data->file)
+ {
+ GTask *task = g_task_new (cache, NULL, on_pixbuf_loaded, data);
+ g_task_set_task_data (task, data, NULL);
+ g_task_run_in_thread (task, load_pixbuf_thread);
+ g_object_unref (task);
+ }
+ else if (data->icon_info)
+ {
+ StIconColors *colors = data->colors;
+ if (colors)
+ {
+ GdkRGBA foreground_color;
+ GdkRGBA success_color;
+ GdkRGBA warning_color;
+ GdkRGBA error_color;
+
+ rgba_from_clutter (&foreground_color, &colors->foreground);
+ rgba_from_clutter (&success_color, &colors->success);
+ rgba_from_clutter (&warning_color, &colors->warning);
+ rgba_from_clutter (&error_color, &colors->error);
+
+ gtk_icon_info_load_symbolic_async (data->icon_info,
+ &foreground_color, &success_color,
+ &warning_color, &error_color,
+ cache->priv->cancellable,
+ on_symbolic_icon_loaded, data);
+ }
+ else
+ {
+ gtk_icon_info_load_icon_async (data->icon_info,
+ cache->priv->cancellable,
+ on_icon_loaded, data);
+ }
+ }
+ else
+ g_assert_not_reached ();
+}
+
+typedef struct {
+ StTextureCache *cache;
+ ClutterContent *image;
+ GObject *source;
+ gulong notify_signal_id;
+ gboolean weakref_active;
+} StTextureCachePropertyBind;
+
+static void
+st_texture_cache_load_surface (ClutterContent **image,
+ cairo_surface_t *surface)
+{
+ g_return_if_fail (image != NULL);
+
+ if (surface != NULL &&
+ cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE &&
+ (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32 ||
+ cairo_image_surface_get_format (surface) == CAIRO_FORMAT_RGB24))
+ {
+ g_autoptr(GError) error = NULL;
+ int width, height, size;
+
+ width = cairo_image_surface_get_width (surface);
+ height = cairo_image_surface_get_width (surface);
+ size = MAX(width, height);
+
+ if (*image == NULL)
+ *image = st_image_content_new_with_preferred_size (size, size);
+
+ clutter_image_set_data (CLUTTER_IMAGE (*image),
+ cairo_image_surface_get_data (surface),
+ cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32 ?
+ COGL_PIXEL_FORMAT_BGRA_8888 : COGL_PIXEL_FORMAT_BGR_888,
+ width,
+ height,
+ cairo_image_surface_get_stride (surface),
+ &error);
+
+ if (error)
+ g_warning ("Failed to allocate texture: %s", error->message);
+ }
+ else if (*image == NULL)
+ {
+ *image = st_image_content_new_with_preferred_size (0, 0);
+ }
+}
+
+static void
+st_texture_cache_reset_texture (StTextureCachePropertyBind *bind,
+ const char *propname)
+{
+ cairo_surface_t *surface;
+
+ g_object_get (bind->source, propname, &surface, NULL);
+
+ st_texture_cache_load_surface (&bind->image, surface);
+}
+
+static void
+st_texture_cache_on_pixbuf_notify (GObject *object,
+ GParamSpec *paramspec,
+ gpointer data)
+{
+ StTextureCachePropertyBind *bind = data;
+ st_texture_cache_reset_texture (bind, paramspec->name);
+}
+
+static void
+st_texture_cache_bind_weak_notify (gpointer data,
+ GObject *source_location)
+{
+ StTextureCachePropertyBind *bind = data;
+ bind->weakref_active = FALSE;
+ g_signal_handler_disconnect (bind->source, bind->notify_signal_id);
+}
+
+static void
+st_texture_cache_free_bind (gpointer data)
+{
+ StTextureCachePropertyBind *bind = data;
+ if (bind->weakref_active)
+ g_object_weak_unref (G_OBJECT (bind->image), st_texture_cache_bind_weak_notify, bind);
+ g_free (bind);
+}
+
+/**
+ * st_texture_cache_bind_cairo_surface_property:
+ * @cache: A #StTextureCache
+ * @object: A #GObject with a property @property_name of type #cairo_surface_t
+ * @property_name: Name of a property
+ *
+ * Create a #GIcon which tracks the #cairo_surface_t value of a GObject property
+ * named by @property_name. Unlike other methods in StTextureCache, the underlying
+ * #CoglTexture is not shared by default with other invocations to this method.
+ *
+ * If the source object is destroyed, the texture will continue to show the last
+ * value of the property.
+ *
+ * Returns: (transfer full): A new #GIcon
+ */
+GIcon *
+st_texture_cache_bind_cairo_surface_property (StTextureCache *cache,
+ GObject *object,
+ const char *property_name)
+{
+ gchar *notify_key;
+ StTextureCachePropertyBind *bind;
+
+ bind = g_new0 (StTextureCachePropertyBind, 1);
+ bind->cache = cache;
+ bind->source = object;
+
+ st_texture_cache_reset_texture (bind, property_name);
+
+ g_object_weak_ref (G_OBJECT (bind->image), st_texture_cache_bind_weak_notify, bind);
+ bind->weakref_active = TRUE;
+
+ notify_key = g_strdup_printf ("notify::%s", property_name);
+ bind->notify_signal_id = g_signal_connect_data (object, notify_key, G_CALLBACK(st_texture_cache_on_pixbuf_notify),
+ bind, (GClosureNotify)st_texture_cache_free_bind, 0);
+ g_free (notify_key);
+
+ return G_ICON (bind->image);
+}
+
+/**
+ * st_texture_cache_load_cairo_surface_to_gicon:
+ * @cache: A #StTextureCache
+ * @surface: A #cairo_surface_t
+ *
+ * Create a #GIcon from @surface.
+ *
+ * Returns: (transfer full): A new #GIcon
+ */
+GIcon *
+st_texture_cache_load_cairo_surface_to_gicon (StTextureCache *cache,
+ cairo_surface_t *surface)
+{
+ ClutterContent *image = NULL;
+ st_texture_cache_load_surface (&image, surface);
+
+ return G_ICON (image);
+}
+
+/**
+ * st_texture_cache_load: (skip)
+ * @cache: A #StTextureCache
+ * @key: Arbitrary string used to refer to item
+ * @policy: Caching policy
+ * @load: Function to create the texture, if not already cached
+ * @data: User data passed to @load
+ * @error: A #GError
+ *
+ * Load an arbitrary texture, caching it. The string chosen for @key
+ * should be of the form "type-prefix:type-uuid". For example,
+ * "url:file:///usr/share/icons/hicolor/48x48/apps/firefox.png", or
+ * "stock-icon:gtk-ok".
+ *
+ * Returns: (transfer full): A newly-referenced handle to the texture
+ */
+CoglTexture *
+st_texture_cache_load (StTextureCache *cache,
+ const char *key,
+ StTextureCachePolicy policy,
+ StTextureCacheLoader load,
+ void *data,
+ GError **error)
+{
+ CoglTexture *texture;
+
+ texture = g_hash_table_lookup (cache->priv->keyed_cache, key);
+ if (!texture)
+ {
+ texture = load (cache, key, data, error);
+ if (texture && policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), texture);
+ }
+
+ if (texture && policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ cogl_object_ref (texture);
+
+ return texture;
+}
+
+/**
+ * ensure_request:
+ * @cache: A #StTextureCache
+ * @key: A cache key
+ * @policy: Cache policy
+ * @request: (out): If no request is outstanding, one will be created and returned here
+ * @texture: A texture to be added to the request
+ *
+ * Check for any outstanding load for the data represented by @key. If there
+ * is already a request pending, append it to that request to avoid loading
+ * the data multiple times.
+ *
+ * Returns: %TRUE if there is already a request pending
+ */
+static gboolean
+ensure_request (StTextureCache *cache,
+ const char *key,
+ StTextureCachePolicy policy,
+ AsyncTextureLoadData **request,
+ ClutterActor *actor)
+{
+ ClutterContent *image;
+ AsyncTextureLoadData *pending;
+ gboolean had_pending;
+
+ image = g_hash_table_lookup (cache->priv->keyed_cache, key);
+
+ if (image != NULL)
+ {
+ /* We had this cached already, just set the texture and we're done. */
+ set_content_from_image (actor, image);
+ return TRUE;
+ }
+
+ pending = g_hash_table_lookup (cache->priv->outstanding_requests, key);
+ had_pending = pending != NULL;
+
+ if (pending == NULL)
+ {
+ /* Not cached and no pending request, create it */
+ *request = g_new0 (AsyncTextureLoadData, 1);
+ if (policy != ST_TEXTURE_CACHE_POLICY_NONE)
+ g_hash_table_insert (cache->priv->outstanding_requests, g_strdup (key), *request);
+ }
+ else
+ *request = pending;
+
+ /* Regardless of whether there was a pending request, prepend our texture here. */
+ (*request)->actors = g_slist_prepend ((*request)->actors, g_object_ref (actor));
+
+ return had_pending;
+}
+
+/**
+ * st_texture_cache_load_gicon:
+ * @cache: A #StTextureCache
+ * @theme_node: (nullable): The #StThemeNode to use for colors, or %NULL
+ * if the icon must not be recolored
+ * @icon: the #GIcon to load
+ * @size: Size of themed
+ * @paint_scale: Scale factor of display
+ * @resource_scale: Resource scale factor
+ *
+ * This method returns a new #ClutterActor for a given #GIcon. If the
+ * icon isn't loaded already, the texture will be filled
+ * asynchronously.
+ *
+ * Returns: (transfer none) (nullable): A new #ClutterActor for the icon, or %NULL if not found
+ */
+ClutterActor *
+st_texture_cache_load_gicon (StTextureCache *cache,
+ StThemeNode *theme_node,
+ GIcon *icon,
+ gint size,
+ gint paint_scale,
+ gfloat resource_scale)
+{
+ AsyncTextureLoadData *request;
+ ClutterActor *actor;
+ gint scale;
+ char *gicon_string;
+ g_autofree char *key = NULL;
+ float actor_size;
+ GtkIconTheme *theme;
+ StTextureCachePolicy policy;
+ StIconColors *colors = NULL;
+ StIconStyle icon_style = ST_ICON_STYLE_REQUESTED;
+ GtkIconLookupFlags lookup_flags;
+
+ actor_size = size * paint_scale;
+
+ if (ST_IS_IMAGE_CONTENT (icon))
+ {
+ int width, height;
+
+ g_object_get (G_OBJECT (icon),
+ "preferred-width", &width,
+ "preferred-height", &height,
+ NULL);
+ if (width == 0 && height == 0)
+ return NULL;
+
+ return g_object_new (CLUTTER_TYPE_ACTOR,
+ "content-gravity", CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT,
+ "width", actor_size,
+ "height", actor_size,
+ "content", CLUTTER_CONTENT (icon),
+ NULL);
+ }
+
+ if (theme_node)
+ {
+ colors = st_theme_node_get_icon_colors (theme_node);
+ icon_style = st_theme_node_get_icon_style (theme_node);
+ }
+
+ /* Do theme lookups in the main thread to avoid thread-unsafety */
+ theme = cache->priv->icon_theme;
+
+ lookup_flags = GTK_ICON_LOOKUP_USE_BUILTIN;
+
+ if (icon_style == ST_ICON_STYLE_REGULAR)
+ lookup_flags |= GTK_ICON_LOOKUP_FORCE_REGULAR;
+ else if (icon_style == ST_ICON_STYLE_SYMBOLIC)
+ lookup_flags |= GTK_ICON_LOOKUP_FORCE_SYMBOLIC;
+
+ if (clutter_get_default_text_direction () == CLUTTER_TEXT_DIRECTION_RTL)
+ lookup_flags |= GTK_ICON_LOOKUP_DIR_RTL;
+ else
+ lookup_flags |= GTK_ICON_LOOKUP_DIR_LTR;
+
+ scale = ceilf (paint_scale * resource_scale);
+
+ gicon_string = g_icon_to_string (icon);
+ /* A return value of NULL indicates that the icon can not be serialized,
+ * so don't have a unique identifier for it as a cache key, and thus can't
+ * be cached. If it is cacheable, we hardcode a policy of FOREVER here for
+ * now; we should actually blow this away on icon theme changes probably */
+ policy = gicon_string != NULL ? ST_TEXTURE_CACHE_POLICY_FOREVER
+ : ST_TEXTURE_CACHE_POLICY_NONE;
+ if (colors)
+ {
+ /* This raises some doubts about the practice of using string keys */
+ key = g_strdup_printf (CACHE_PREFIX_ICON "%s,size=%d,scale=%d,style=%d,colors=%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x",
+ gicon_string, size, scale, icon_style,
+ colors->foreground.red, colors->foreground.blue, colors->foreground.green, colors->foreground.alpha,
+ colors->warning.red, colors->warning.blue, colors->warning.green, colors->warning.alpha,
+ colors->error.red, colors->error.blue, colors->error.green, colors->error.alpha,
+ colors->success.red, colors->success.blue, colors->success.green, colors->success.alpha);
+ }
+ else
+ {
+ key = g_strdup_printf (CACHE_PREFIX_ICON "%s,size=%d,scale=%d,style=%d",
+ gicon_string, size, scale, icon_style);
+ }
+ g_free (gicon_string);
+
+ actor = create_invisible_actor ();
+ clutter_actor_set_content_gravity (actor, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
+ clutter_actor_set_size (actor, actor_size, actor_size);
+ if (!ensure_request (cache, key, policy, &request, actor))
+ {
+ /* Else, make a new request */
+ GtkIconInfo *info;
+
+ info = gtk_icon_theme_lookup_by_gicon_for_scale (theme, icon,
+ size, scale,
+ lookup_flags);
+ if (info == NULL)
+ {
+ g_hash_table_remove (cache->priv->outstanding_requests, key);
+ texture_load_data_free (request);
+ g_object_unref (actor);
+ return NULL;
+ }
+
+ request->cache = cache;
+ /* Transfer ownership of key */
+ request->key = g_steal_pointer (&key);
+ request->policy = policy;
+ request->colors = colors ? st_icon_colors_ref (colors) : NULL;
+ request->icon_info = info;
+ request->width = request->height = size;
+ request->paint_scale = paint_scale;
+ request->resource_scale = resource_scale;
+
+ load_texture_async (cache, request);
+ }
+
+ return actor;
+}
+
+static ClutterActor *
+load_from_pixbuf (GdkPixbuf *pixbuf,
+ int paint_scale,
+ float resource_scale)
+{
+ g_autoptr(ClutterContent) image = NULL;
+ ClutterActor *actor;
+
+ image = pixbuf_to_st_content_image (pixbuf, -1, -1, paint_scale, resource_scale);
+
+ actor = g_object_new (CLUTTER_TYPE_ACTOR,
+ "request-mode", CLUTTER_REQUEST_CONTENT_SIZE,
+ NULL);
+ clutter_actor_set_content (actor, image);
+
+ return actor;
+}
+
+static void
+hash_table_remove_with_scales (GHashTable *hash,
+ GList *scales,
+ const char *base_key)
+{
+ GList *l;
+
+ for (l = scales; l; l = l->next)
+ {
+ double scale = *((double *)l->data);
+ g_autofree char *key = NULL;
+ key = g_strdup_printf ("%s%f", base_key, scale);
+ g_hash_table_remove (hash, key);
+ }
+}
+
+static void
+hash_table_insert_scale (GHashTable *hash,
+ double scale)
+{
+ double *saved_scale;
+
+ if (g_hash_table_contains (hash, &scale))
+ return;
+
+ saved_scale = g_new (double, 1);
+ *saved_scale = scale;
+
+ g_hash_table_add (hash, saved_scale);
+}
+
+static void
+file_changed_cb (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other,
+ GFileMonitorEvent event_type,
+ gpointer user_data)
+{
+ StTextureCache *cache = user_data;
+ char *key;
+ guint file_hash;
+ g_autoptr (GList) scales = NULL;
+
+ if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
+ return;
+
+ file_hash = g_file_hash (file);
+ scales = g_hash_table_get_keys (cache->priv->used_scales);
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE "%u", file_hash);
+ g_hash_table_remove (cache->priv->keyed_cache, key);
+ hash_table_remove_with_scales (cache->priv->keyed_cache, scales, key);
+ g_free (key);
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE_FOR_CAIRO "%u", file_hash);
+ g_hash_table_remove (cache->priv->keyed_surface_cache, key);
+ hash_table_remove_with_scales (cache->priv->keyed_surface_cache, scales, key);
+ g_free (key);
+
+ g_signal_emit (cache, signals[TEXTURE_FILE_CHANGED], 0, file);
+}
+
+static void
+ensure_monitor_for_file (StTextureCache *cache,
+ GFile *file)
+{
+ StTextureCachePrivate *priv = cache->priv;
+
+ /* No point in trying to monitor files that are part of a
+ * GResource, since it does not support file monitoring.
+ */
+ if (g_file_has_uri_scheme (file, "resource"))
+ return;
+
+ if (g_hash_table_lookup (priv->file_monitors, file) == NULL)
+ {
+ GFileMonitor *monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE,
+ NULL, NULL);
+ g_signal_connect (monitor, "changed",
+ G_CALLBACK (file_changed_cb), cache);
+ g_hash_table_insert (priv->file_monitors, g_object_ref (file), monitor);
+ }
+}
+
+typedef struct {
+ GFile *gfile;
+ gint grid_width, grid_height;
+ gint paint_scale;
+ gfloat resource_scale;
+ ClutterActor *actor;
+ GCancellable *cancellable;
+ GFunc load_callback;
+ gpointer load_callback_data;
+} AsyncImageData;
+
+static void
+on_data_destroy (gpointer data)
+{
+ AsyncImageData *d = (AsyncImageData *)data;
+ g_object_unref (d->gfile);
+ g_object_unref (d->actor);
+ g_object_unref (d->cancellable);
+ g_free (d);
+}
+
+static void
+on_sliced_image_actor_destroyed (ClutterActor *actor,
+ gpointer data)
+{
+ GTask *task = data;
+ GCancellable *cancellable = g_task_get_cancellable (task);
+
+ g_cancellable_cancel (cancellable);
+}
+
+static void
+on_sliced_image_loaded (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GObject *cache = source_object;
+ AsyncImageData *data = (AsyncImageData *)user_data;
+ GTask *task = G_TASK (res);
+ GList *list, *pixbufs;
+
+ if (g_task_had_error (task) || g_cancellable_is_cancelled (data->cancellable))
+ return;
+
+ pixbufs = g_task_propagate_pointer (task, NULL);
+
+ for (list = pixbufs; list; list = list->next)
+ {
+ ClutterActor *actor = load_from_pixbuf (GDK_PIXBUF (list->data),
+ data->paint_scale,
+ data->resource_scale);
+ clutter_actor_hide (actor);
+ clutter_actor_add_child (data->actor, actor);
+ }
+
+ g_list_free_full (pixbufs, g_object_unref);
+
+ g_signal_handlers_disconnect_by_func (data->actor,
+ on_sliced_image_actor_destroyed,
+ task);
+
+ if (data->load_callback != NULL)
+ data->load_callback (cache, data->load_callback_data);
+}
+
+static void
+free_glist_unref_gobjects (gpointer p)
+{
+ g_list_free_full (p, g_object_unref);
+}
+
+static void
+on_loader_size_prepared (GdkPixbufLoader *loader,
+ gint width,
+ gint height,
+ gpointer user_data)
+{
+ AsyncImageData *data = user_data;
+ int scale = ceilf (data->paint_scale * data->resource_scale);
+
+ gdk_pixbuf_loader_set_size (loader, width * scale, height * scale);
+}
+
+static void
+load_sliced_image (GTask *result,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ AsyncImageData *data;
+ GList *res = NULL;
+ GdkPixbuf *pix;
+ gint width, height, y, x;
+ gint scale_factor;
+ GdkPixbufLoader *loader;
+ GError *error = NULL;
+ gchar *buffer = NULL;
+ gsize length;
+
+ g_assert (cancellable);
+
+ data = task_data;
+ g_assert (data);
+
+ loader = gdk_pixbuf_loader_new ();
+ g_signal_connect (loader, "size-prepared", G_CALLBACK (on_loader_size_prepared), data);
+
+ if (!g_file_load_contents (data->gfile, cancellable, &buffer, &length, NULL, &error))
+ {
+ g_warning ("Failed to open sliced image: %s", error->message);
+ goto out;
+ }
+
+ if (!gdk_pixbuf_loader_write (loader, (const guchar *) buffer, length, &error))
+ {
+ g_warning ("Failed to load image: %s", error->message);
+ goto out;
+ }
+
+ if (!gdk_pixbuf_loader_close (loader, NULL))
+ goto out;
+
+ pix = gdk_pixbuf_loader_get_pixbuf (loader);
+ width = gdk_pixbuf_get_width (pix);
+ height = gdk_pixbuf_get_height (pix);
+ scale_factor = ceilf (data->paint_scale * data->resource_scale);
+ for (y = 0; y < height; y += data->grid_height * scale_factor)
+ {
+ for (x = 0; x < width; x += data->grid_width * scale_factor)
+ {
+ GdkPixbuf *pixbuf = gdk_pixbuf_new_subpixbuf (pix, x, y,
+ data->grid_width * scale_factor,
+ data->grid_height * scale_factor);
+ g_assert (pixbuf != NULL);
+ res = g_list_append (res, pixbuf);
+ }
+ }
+
+ out:
+ /* We don't need the original pixbuf anymore, which is owned by the loader,
+ * though the subpixbufs will hold a reference. */
+ g_object_unref (loader);
+ g_free (buffer);
+ g_clear_pointer (&error, g_error_free);
+ g_task_return_pointer (result, res, free_glist_unref_gobjects);
+}
+
+/**
+ * st_texture_cache_load_sliced_image:
+ * @cache: A #StTextureCache
+ * @file: A #GFile
+ * @grid_width: Width in pixels
+ * @grid_height: Height in pixels
+ * @paint_scale: Scale factor of the display
+ * @load_callback: (scope async) (nullable): Function called when the image is loaded, or %NULL
+ * @user_data: Data to pass to the load callback
+ *
+ * This function reads a single image file which contains multiple images internally.
+ * The image file will be divided using @grid_width and @grid_height;
+ * note that the dimensions of the image loaded from @path
+ * should be a multiple of the specified grid dimensions.
+ *
+ * Returns: (transfer none): A new #ClutterActor
+ */
+ClutterActor *
+st_texture_cache_load_sliced_image (StTextureCache *cache,
+ GFile *file,
+ gint grid_width,
+ gint grid_height,
+ gint paint_scale,
+ gfloat resource_scale,
+ GFunc load_callback,
+ gpointer user_data)
+{
+ AsyncImageData *data;
+ GTask *result;
+ ClutterActor *actor = clutter_actor_new ();
+ GCancellable *cancellable = g_cancellable_new ();
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_assert (paint_scale > 0);
+ g_assert (resource_scale > 0);
+
+ data = g_new0 (AsyncImageData, 1);
+ data->grid_width = grid_width;
+ data->grid_height = grid_height;
+ data->paint_scale = paint_scale;
+ data->resource_scale = resource_scale;
+ data->gfile = g_object_ref (file);
+ data->actor = actor;
+ data->cancellable = cancellable;
+ data->load_callback = load_callback;
+ data->load_callback_data = user_data;
+ g_object_ref (G_OBJECT (actor));
+
+ result = g_task_new (cache, cancellable, on_sliced_image_loaded, data);
+
+ g_signal_connect (actor, "destroy",
+ G_CALLBACK (on_sliced_image_actor_destroyed), result);
+
+ g_task_set_task_data (result, data, on_data_destroy);
+ g_task_run_in_thread (result, load_sliced_image);
+
+ g_object_unref (result);
+
+ return actor;
+}
+
+/**
+ * st_texture_cache_load_file_async:
+ * @cache: A #StTextureCache
+ * @file: a #GFile of the image file from which to create a pixbuf
+ * @available_width: available width for the image, can be -1 if not limited
+ * @available_height: available height for the image, can be -1 if not limited
+ * @paint_scale: scale factor of the display
+ * @resource_scale: Resource scale factor
+ *
+ * Asynchronously load an image. Initially, the returned texture will have a natural
+ * size of zero. At some later point, either the image will be loaded successfully
+ * and at that point size will be negotiated, or upon an error, no image will be set.
+ *
+ * Returns: (transfer none): A new #ClutterActor with no image loaded initially.
+ */
+ClutterActor *
+st_texture_cache_load_file_async (StTextureCache *cache,
+ GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ gfloat resource_scale)
+{
+ ClutterActor *actor;
+ AsyncTextureLoadData *request;
+ StTextureCachePolicy policy;
+ gchar *key;
+ int scale;
+
+ scale = ceilf (paint_scale * resource_scale);
+ key = g_strdup_printf (CACHE_PREFIX_FILE "%u%d", g_file_hash (file), scale);
+
+ policy = ST_TEXTURE_CACHE_POLICY_NONE; /* XXX */
+
+ actor = create_invisible_actor ();
+
+ if (ensure_request (cache, key, policy, &request, actor))
+ {
+ /* If there's an outstanding request, we've just added ourselves to it */
+ g_free (key);
+ }
+ else
+ {
+ /* Else, make a new request */
+
+ request->cache = cache;
+ /* Transfer ownership of key */
+ request->key = key;
+ request->file = g_object_ref (file);
+ request->policy = policy;
+ request->width = available_width;
+ request->height = available_height;
+ request->paint_scale = paint_scale;
+ request->resource_scale = resource_scale;
+
+ load_texture_async (cache, request);
+ }
+
+ ensure_monitor_for_file (cache, file);
+
+ return actor;
+}
+
+static CoglTexture *
+st_texture_cache_load_file_sync_to_cogl_texture (StTextureCache *cache,
+ StTextureCachePolicy policy,
+ GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ gfloat resource_scale,
+ GError **error)
+{
+ ClutterContent *image;
+ CoglTexture *texdata;
+ GdkPixbuf *pixbuf;
+ char *key;
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE "%u%f", g_file_hash (file), resource_scale);
+
+ texdata = NULL;
+ image = g_hash_table_lookup (cache->priv->keyed_cache, key);
+
+ if (image == NULL)
+ {
+ pixbuf = impl_load_pixbuf_file (file, available_width, available_height,
+ paint_scale, resource_scale, error);
+ if (!pixbuf)
+ goto out;
+
+ image = pixbuf_to_st_content_image (pixbuf,
+ available_height, available_width,
+ paint_scale, resource_scale);
+ g_object_unref (pixbuf);
+
+ if (!image)
+ goto out;
+
+ if (policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ {
+ g_hash_table_insert (cache->priv->keyed_cache, g_strdup (key), image);
+ hash_table_insert_scale (cache->priv->used_scales, (double)resource_scale);
+ }
+ }
+
+ /* Because the texture is loaded synchronously, we won't call
+ * clutter_image_set_data(), so it's safe to use the texture
+ * of ClutterImage here. */
+ texdata = clutter_image_get_texture (CLUTTER_IMAGE (image));
+ cogl_object_ref (texdata);
+
+ ensure_monitor_for_file (cache, file);
+
+out:
+ g_free (key);
+ return texdata;
+}
+
+static cairo_surface_t *
+st_texture_cache_load_file_sync_to_cairo_surface (StTextureCache *cache,
+ StTextureCachePolicy policy,
+ GFile *file,
+ int available_width,
+ int available_height,
+ int paint_scale,
+ gfloat resource_scale,
+ GError **error)
+{
+ cairo_surface_t *surface;
+ GdkPixbuf *pixbuf;
+ char *key;
+
+ key = g_strdup_printf (CACHE_PREFIX_FILE_FOR_CAIRO "%u%f", g_file_hash (file), resource_scale);
+
+ surface = g_hash_table_lookup (cache->priv->keyed_surface_cache, key);
+
+ if (surface == NULL)
+ {
+ pixbuf = impl_load_pixbuf_file (file, available_width, available_height,
+ paint_scale, resource_scale, error);
+ if (!pixbuf)
+ goto out;
+
+ surface = pixbuf_to_cairo_surface (pixbuf);
+ g_object_unref (pixbuf);
+
+ if (policy == ST_TEXTURE_CACHE_POLICY_FOREVER)
+ {
+ cairo_surface_reference (surface);
+ g_hash_table_insert (cache->priv->keyed_surface_cache,
+ g_strdup (key), surface);
+ hash_table_insert_scale (cache->priv->used_scales, (double)resource_scale);
+ }
+ }
+ else
+ cairo_surface_reference (surface);
+
+ ensure_monitor_for_file (cache, file);
+
+out:
+ g_free (key);
+ return surface;
+}
+
+/**
+ * st_texture_cache_load_file_to_cogl_texture: (skip)
+ * @cache: A #StTextureCache
+ * @file: A #GFile in supported image format
+ * @paint_scale: Scale factor of the display
+ * @resource_scale: Resource scale factor
+ *
+ * This function synchronously loads the given file path
+ * into a COGL texture. On error, a warning is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): a new #CoglTexture
+ */
+CoglTexture *
+st_texture_cache_load_file_to_cogl_texture (StTextureCache *cache,
+ GFile *file,
+ gint paint_scale,
+ gfloat resource_scale)
+{
+ CoglTexture *texture;
+ GError *error = NULL;
+
+ texture = st_texture_cache_load_file_sync_to_cogl_texture (cache, ST_TEXTURE_CACHE_POLICY_FOREVER,
+ file, -1, -1, paint_scale, resource_scale,
+ &error);
+
+ if (texture == NULL)
+ {
+ char *uri = g_file_get_uri (file);
+ g_warning ("Failed to load %s: %s", uri, error->message);
+ g_clear_error (&error);
+ g_free (uri);
+ }
+
+ return texture;
+}
+
+/**
+ * st_texture_cache_load_file_to_cairo_surface:
+ * @cache: A #StTextureCache
+ * @file: A #GFile in supported image format
+ * @paint_scale: Scale factor of the display
+ * @resource_scale: Resource scale factor
+ *
+ * This function synchronously loads the given file path
+ * into a cairo surface. On error, a warning is emitted
+ * and %NULL is returned.
+ *
+ * Returns: (transfer full): a new #cairo_surface_t
+ */
+cairo_surface_t *
+st_texture_cache_load_file_to_cairo_surface (StTextureCache *cache,
+ GFile *file,
+ gint paint_scale,
+ gfloat resource_scale)
+{
+ cairo_surface_t *surface;
+ GError *error = NULL;
+
+ surface = st_texture_cache_load_file_sync_to_cairo_surface (cache, ST_TEXTURE_CACHE_POLICY_FOREVER,
+ file, -1, -1, paint_scale, resource_scale,
+ &error);
+
+ if (surface == NULL)
+ {
+ char *uri = g_file_get_uri (file);
+ g_warning ("Failed to load %s: %s", uri, error->message);
+ g_clear_error (&error);
+ g_free (uri);
+ }
+
+ return surface;
+}
+
+static StTextureCache *instance = NULL;
+
+/**
+ * st_texture_cache_get_default:
+ *
+ * Returns: (transfer none): The global texture cache
+ */
+StTextureCache*
+st_texture_cache_get_default (void)
+{
+ if (instance == NULL)
+ instance = g_object_new (ST_TYPE_TEXTURE_CACHE, NULL);
+ return instance;
+}
+
+/**
+ * st_texture_cache_rescan_icon_theme:
+ *
+ * Rescan the current icon theme, if necessary.
+ *
+ * Returns: %TRUE if the icon theme has changed and needed to be reloaded.
+ */
+gboolean
+st_texture_cache_rescan_icon_theme (StTextureCache *cache)
+{
+ StTextureCachePrivate *priv = cache->priv;
+
+ return gtk_icon_theme_rescan_if_needed (priv->icon_theme);
+}