[gnome-software: 10/18] lib: Port GsApp to use GIcons rather than GdkPixbufs for icons




commit f49a7403256c6c0b8e6e8707650bab8f46673464
Author: Philip Withnall <pwithnall endlessos org>
Date:   Fri Mar 5 01:12:04 2021 +0000

    lib: Port GsApp to use GIcons rather than GdkPixbufs for icons
    
    This moves the process of loading an icon (as a `GdkPixbuf`) from the
    time when the `GsApp` is created, to the time when the icon is displayed
    in the UI. This allows the pixbufs to be kept in memory only as long as
    they’re displayed and, in particular, to not consume memory while the
    main window of gnome-software is closed.
    
    GTK itself maintains an LRU cache of icon pixbufs, so if a particular
    icon is needed frequently, it shouldn’t end up being reloaded from disk
    unnecessarily.
    
    This commit uses various classes which implement `GIcon` to wrap
    different icon sources which were previously all represented by
    `AsIcon`. It wouldn’t have made sense to implement `GIcon` in `AsIcon`
    itself — as well as being a minor layering violation, this approach
    would not have supported other icon sources. In particular, flatpak
    bundles provide their icons as binary blobs, rather than `AsIcon`s;
    these need to be supported too. With this approach, they can be, using
    `GBytesIcon`.
    
    Another advantage of using `GIcon`s is that they allow multiple sizes of
    icon to be loaded, rather than relying on loading the
    most-commonly-requested size to a pixbuf, or loading the largest size
    and scaling it down on command.
    
    Remote icons (with a HTTP or HTTPS URI) are implemented using
    `GsRemoteIcon`, introduced in a previous commit.
    
    This allows for most of the implementation of `gs-plugin-icons` to be
    dropped, as converting a `GIcon` to a `GdkPixbuf` is now mostly handled
    by `gtk_image_set_from_gicon()`. The downloading and caching of remote
    icons is handled in `GsRemoteIcon`, and `gs-plugin-icons` now remains
    only to schedule calls to `gs_remote_icon_ensure_cached()` from a worker
    thread. Eventually this too can disappear, but it’s a convenient way of
    getting the threading right for now.
    
    Signed-off-by: Philip Withnall <pwithnall endlessos org>
    
    Helps: #1147

 doc/api/gnome-software-docs.xml                    |   8 +-
 lib/gnome-software.h                               |   1 +
 lib/gs-app.c                                       | 234 ++++++++-------
 lib/gs-app.h                                       |   8 +-
 lib/gs-icon.c                                      | 305 ++++++++++++++++++++
 lib/gs-icon.h                                      |  32 +++
 lib/gs-plugin-loader.c                             |  26 +-
 lib/meson.build                                    |   2 +
 plugins/core/gs-appstream.c                        |  20 +-
 plugins/core/gs-plugin-generic-updates.c           |   6 +-
 plugins/core/gs-plugin-icons.c                     | 317 ++-------------------
 plugins/dummy/gs-plugin-dummy.c                    |  31 +-
 plugins/eos-updater/gs-plugin-eos-updater.c        |   6 +-
 .../gs-plugin-fedora-pkgdb-collections.c           |   8 +-
 plugins/flatpak/gs-flatpak-utils.c                 |  10 +-
 plugins/flatpak/gs-flatpak.c                       |  40 +--
 plugins/fwupd/gs-plugin-fwupd.c                    |  21 +-
 plugins/modalias/gs-plugin-modalias.c              |   6 +-
 plugins/snap/gs-plugin-snap.c                      |  13 +-
 19 files changed, 568 insertions(+), 526 deletions(-)
---
diff --git a/doc/api/gnome-software-docs.xml b/doc/api/gnome-software-docs.xml
index 33a1228cf..80cd6a9b3 100644
--- a/doc/api/gnome-software-docs.xml
+++ b/doc/api/gnome-software-docs.xml
@@ -193,7 +193,7 @@ gs_plugin_add_installed (GsPlugin *plugin,
 {
   g_autofree gchar *fn = NULL;
   g_autoptr(GsApp) app = NULL;
-  g_autoptr(AsIcon) icon = NULL;
+  g_autoptr(GIcon) icon = NULL;
 
   /* check if the app exists */
   fn = g_build_filename (g_get_home_dir (), "chiron", NULL);
@@ -221,10 +221,8 @@ gs_plugin_add_installed (GsPlugin *plugin,
   gs_app_add_kudo (app, GS_APP_KUDO_INSTALLS_USER_DOCS);
   gs_app_set_license (app, GS_APP_QUALITY_NORMAL, "GPL-2.0+ and LGPL-2.1+");
 
-  /* create a stock icon which will be loaded by the 'icons' plugin */
-  icon = as_icon_new ();
-  as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-  as_icon_set_name (icon, "input-gaming");
+  /* use a stock icon */
+  icon = g_themed_icon_new ("input-gaming");
   gs_app_add_icon (app, icon);
 
   /* return new app */
diff --git a/lib/gnome-software.h b/lib/gnome-software.h
index b24d518aa..8b7497d15 100644
--- a/lib/gnome-software.h
+++ b/lib/gnome-software.h
@@ -20,6 +20,7 @@
 #include <gs-category-manager.h>
 #include <gs-desktop-data.h>
 #include <gs-enums.h>
+#include <gs-icon.h>
 #include <gs-metered.h>
 #include <gs-os-release.h>
 #include <gs-plugin.h>
diff --git a/lib/gs-app.c b/lib/gs-app.c
index c7350964e..824991e4a 100644
--- a/lib/gs-app.c
+++ b/lib/gs-app.c
@@ -44,6 +44,7 @@
 #include "gs-app-private.h"
 #include "gs-desktop-data.h"
 #include "gs-enums.h"
+#include "gs-icon.h"
 #include "gs-key-colors.h"
 #include "gs-os-release.h"
 #include "gs-plugin.h"
@@ -122,7 +123,6 @@ typedef struct
        GsApp                   *runtime;
        GFile                   *local_file;
        AsContentRating         *content_rating;
-       GPtrArray               *pixbufs;  /* (nullable) (owned) (element-type GdkPixbuf), sorted by pixel 
size, smallest first */
        AsScreenshot            *action_screenshot;  /* (nullable) (owned) */
        GCancellable            *cancellable;
        GsPluginAction           pending_action;
@@ -526,35 +526,12 @@ gs_app_to_string_append (GsApp *app, GString *str)
                          gs_app_get_kudos_percentage (app));
        if (priv->name != NULL)
                gs_app_kv_lpad (str, "name", priv->name);
-       for (i = 0; priv->pixbufs != NULL && i < priv->pixbufs->len; i++) {
-               GdkPixbuf *pixbuf = priv->pixbufs->pdata[i];
-               gs_app_kv_printf (str, "pixbuf", "%p, %dx%d", pixbuf,
-                                 gdk_pixbuf_get_width (pixbuf),
-                                 gdk_pixbuf_get_height (pixbuf));
-       }
        if (priv->action_screenshot != NULL)
                gs_app_kv_printf (str, "action-screenshot", "%p", priv->action_screenshot);
        for (i = 0; priv->icons != NULL && i < priv->icons->len; i++) {
-               AsIcon *icon = g_ptr_array_index (priv->icons, i);
-               g_autofree gchar *dimensions_str = NULL;
-
-               gs_app_kv_lpad (str, "icon-kind",
-                               as_icon_kind_to_string (as_icon_get_kind (icon)));
-               if (as_icon_get_name (icon) != NULL)
-                       gs_app_kv_lpad (str, "icon-name",
-                                       as_icon_get_name (icon));
-               if (as_icon_get_filename (icon) != NULL)
-                       gs_app_kv_lpad (str, "icon-filename",
-                                       as_icon_get_filename (icon));
-               if (as_icon_get_url (icon) != NULL)
-                       gs_app_kv_lpad (str, "icon-url",
-                                       as_icon_get_url (icon));
-
-               if (as_icon_get_width (icon) != 0 && as_icon_get_height (icon) != 0)
-                       dimensions_str = g_strdup_printf ("%ux%u",
-                                                         as_icon_get_width (icon),
-                                                         as_icon_get_height (icon));
-               gs_app_kv_lpad (str, "width", (dimensions_str != NULL) ? dimensions_str : "unknown");
+               GIcon *icon = g_ptr_array_index (priv->icons, i);
+               g_autofree gchar *icon_str = g_icon_to_string (icon);
+               gs_app_kv_lpad (str, "icon", icon_str);
        }
        if (priv->match_value != 0)
                gs_app_kv_printf (str, "match-value", "%05x", priv->match_value);
@@ -1816,7 +1793,7 @@ gs_app_has_pixbufs (GsApp *app)
 
        g_return_val_if_fail (GS_IS_APP (app), FALSE);
 
-       return (priv->pixbufs != NULL && priv->pixbufs->len > 0);
+       return (priv->icons != NULL && priv->icons->len > 0);
 }
 
 /**
@@ -1841,36 +1818,109 @@ gs_app_has_pixbufs (GsApp *app)
 GdkPixbuf *
 gs_app_load_pixbuf (GsApp *app,
                     guint  size)
+{
+       g_autoptr(GIcon) icon = NULL;
+       g_autoptr(GInputStream) input_stream = NULL;
+
+       g_return_val_if_fail (GS_IS_APP (app), NULL);
+       g_return_val_if_fail (size > 0, NULL);
+
+       icon = gs_app_get_icon_for_size (app, size, 1, NULL);
+       if (icon == NULL || !G_IS_LOADABLE_ICON (icon))
+               return NULL;
+
+       input_stream = g_loadable_icon_load (G_LOADABLE_ICON (icon), size, NULL, NULL, NULL);
+       if (input_stream == NULL)
+               return NULL;
+
+       return gdk_pixbuf_new_from_stream_at_scale (input_stream, size, size, TRUE, NULL, NULL);
+}
+
+/**
+ * gs_app_get_icon_for_size:
+ * @app: a #GsApp
+ * @size: size (width or height, square) of the icon to fetch, in device pixels
+ * @scale: scale of the icon to fetch, typically from gtk_widget_get_scale_factor()
+ * @fallback_icon_name: (nullable): name of an icon to load as a fallback if
+ *    no other suitable one is found, or %NULL for no fallback
+ *
+ * Finds the most appropriate icon in the @app’s set of icons to be loaded at
+ * the given @size×@scale to represent the application. This might be provided
+ * by the backend at the given @size, or downsized from a larger icon provided
+ * by the backend. The return value is guaranteed to be suitable for loading as
+ * a pixbuf at @size, if it’s not %NULL.
+ *
+ * If an image at least @size pixels in width isn’t available, and
+ * @fallback_icon_name has not been provided, %NULL will be returned. If
+ * @fallback_icon_name has been provided, a #GIcon representing that will be
+ * returned, and %NULL is guaranteed not to be returned.
+ *
+ * Icons which come from a remote server (over HTTP or HTTPS) will be returned
+ * as a pointer into a local cache, which may not have been populated. You must
+ * call gs_remote_icon_ensure_cached() on icons of type #GsRemoteIcon to
+ * download them; this function will not do that for you.
+ *
+ * This function may do disk I/O or image resizing, but it will not do network
+ * I/O to load a pixbuf. It should be acceptable to call this from a UI thread.
+ *
+ * Returns: (transfer full) (nullable): a #GIcon, or %NULL
+ *
+ * Since: 40
+ */
+GIcon *
+gs_app_get_icon_for_size (GsApp       *app,
+                          guint        size,
+                          guint        scale,
+                          const gchar *fallback_icon_name)
 {
        GsAppPrivate *priv = gs_app_get_instance_private (app);
 
        g_return_val_if_fail (GS_IS_APP (app), NULL);
        g_return_val_if_fail (size > 0, NULL);
+       g_return_val_if_fail (scale >= 1, NULL);
+
+       g_debug ("Looking for icon for %s, at size %u×%u, with fallback %s",
+                gs_app_get_id (app), size, scale, fallback_icon_name);
+
+       /* See if there’s an icon the right size, or the first one which is too
+        * big which could be scaled down. Note that the icons array may be
+        * lazily created. */
+       for (guint i = 0; priv->icons != NULL && i < priv->icons->len; i++) {
+               GIcon *icon = priv->icons->pdata[i];
+               guint icon_width = gs_icon_get_width (icon);
+               guint icon_scale = gs_icon_get_scale (icon);
 
-       /* FIXME: This algorithm currently relies on gs-plugin-icons to
-        * asynchronously load #AsIcons from #GsApp.icons and add them as
-        * pixbufs. Eventually, that plugin should be retired and its code
-        * folded in here. But for now, we can rely on everything being
-        * available as a pixbuf. */
-
-       /* See if there’s a pixbuf the right size. Note that the pixbufs array
-        * may be lazily created. */
-       for (guint i = 0; priv->pixbufs != NULL && i < priv->pixbufs->len; i++) {
-               GdkPixbuf *pixbuf = priv->pixbufs->pdata[i];
-               if ((guint) gdk_pixbuf_get_width (pixbuf) == size)
-                       return g_object_ref (pixbuf);
-               else if ((guint) gdk_pixbuf_get_width (pixbuf) > size)
-                       break;  /* array is sorted by size */
+               g_debug ("\tConsidering icon of type %s, width %u×%u",
+                        G_OBJECT_TYPE_NAME (icon), icon_width, icon_scale);
+
+               /* Ignore icons with unknown width and skip over ones which
+                * are too small. */
+               if (icon_width == 0 || icon_width * icon_scale < size * scale)
+                       continue;
+
+               if (icon_width * icon_scale >= size * scale)
+                       return g_object_ref (icon);
        }
 
-       /* Now see if there’s a pixbuf which is too big, which could be resized. */
-       for (guint i = 0; priv->pixbufs != NULL && i < priv->pixbufs->len; i++) {
-               GdkPixbuf *pixbuf = priv->pixbufs->pdata[i];
-               if ((guint) gdk_pixbuf_get_width (pixbuf) > size)
-                       return gdk_pixbuf_scale_simple (pixbuf, size, size, GDK_INTERP_BILINEAR);
+       g_debug ("Found no icons of the right size; checking themed icons");
+
+       /* If there’s a themed icon with no width set, use that, as typically
+        * themed icons are available in all the right sizes. */
+       for (guint i = 0; priv->icons != NULL && i < priv->icons->len; i++) {
+               GIcon *icon = priv->icons->pdata[i];
+               guint icon_width = gs_icon_get_width (icon);
+
+               if (icon_width == 0 && G_IS_THEMED_ICON (icon))
+                       return g_object_ref (icon);
        }
 
-       return NULL;
+       if (fallback_icon_name != NULL) {
+               g_debug ("Using fallback icon %s", fallback_icon_name);
+               return g_themed_icon_new (fallback_icon_name);
+       } else {
+               g_debug ("No icon found");
+               return NULL;
+       }
 }
 
 /**
@@ -1900,7 +1950,7 @@ gs_app_get_action_screenshot (GsApp *app)
  * This will never return an empty array; it will always return either %NULL or
  * a non-empty array.
  *
- * Returns: (transfer none) (element-type AsIcon) (nullable): an array of icons,
+ * Returns: (transfer none) (element-type GIcon) (nullable): an array of icons,
  *     or %NULL if there are no icons
  *
  * Since: 3.22
@@ -1921,11 +1971,12 @@ static gint
 icon_sort_width_cb (gconstpointer a,
                     gconstpointer b)
 {
-       AsIcon *icon_a = *((AsIcon **) a);
-       AsIcon *icon_b = *((AsIcon **) b);
-       guint width_a = as_icon_get_width (icon_a);
-       guint width_b = as_icon_get_width (icon_b);
+       GIcon *icon_a = *((GIcon **) a);
+       GIcon *icon_b = *((GIcon **) b);
+       guint width_a = gs_icon_get_width (icon_a);
+       guint width_b = gs_icon_get_width (icon_b);
 
+       /* Sort unknown widths (0 value) to the end. */
        if (width_a == 0 && width_b == 0)
                return 0;
        else if (width_a == 0)
@@ -1939,7 +1990,7 @@ icon_sort_width_cb (gconstpointer a,
 /**
  * gs_app_add_icon:
  * @app: a #GsApp
- * @icon: a #AsIcon
+ * @icon: a #GIcon
  *
  * Adds an icon to use for the application.
  * If the first icon added cannot be loaded then the next one is tried.
@@ -1947,12 +1998,13 @@ icon_sort_width_cb (gconstpointer a,
  * Since: 40
  **/
 void
-gs_app_add_icon (GsApp *app, AsIcon *icon)
+gs_app_add_icon (GsApp *app, GIcon *icon)
 {
        GsAppPrivate *priv = gs_app_get_instance_private (app);
        g_autoptr(GMutexLocker) locker = NULL;
        g_return_if_fail (GS_IS_APP (app));
-       g_return_if_fail (AS_IS_ICON (icon));
+       g_return_if_fail (G_IS_ICON (icon));
+
        locker = g_mutex_locker_new (&priv->mutex);
 
        if (priv->icons == NULL)
@@ -1999,7 +2051,8 @@ gboolean
 gs_app_get_use_drop_shadow (GsApp *app)
 {
        GsAppPrivate *priv = gs_app_get_instance_private (app);
-       AsIcon *ic;
+       GIcon *icon;
+       const gchar * const *names;
 
        g_return_val_if_fail (GS_IS_APP (app), FALSE);
 
@@ -2007,10 +2060,20 @@ gs_app_get_use_drop_shadow (GsApp *app)
        if (priv->icons == NULL || priv->icons->len == 0)
                return TRUE;
 
-       /* stock, and symbolic */
-       ic = g_ptr_array_index (priv->icons, 0);
-       return as_icon_get_kind (ic) != AS_ICON_KIND_STOCK ||
-               !g_str_has_suffix (as_icon_get_name (ic), "-symbolic");
+       icon = g_ptr_array_index (priv->icons, 0);
+
+       /* Apply drop shadows to non-themed icons. */
+       if (!G_IS_THEMED_ICON (icon))
+               return TRUE;
+
+       /* Don’t apply drop shadows to symbolic icons. */
+       names = g_themed_icon_get_names (G_THEMED_ICON (icon));
+       for (gsize i = 0; names[i] != NULL; i++) {
+               if (g_str_has_suffix (names[i], "-symbolic"))
+                       return FALSE;
+       }
+
+       return TRUE;
 }
 
 /**
@@ -2166,54 +2229,6 @@ gs_app_set_runtime (GsApp *app, GsApp *runtime)
        g_set_object (&priv->runtime, runtime);
 }
 
-/**
- * gs_app_add_pixbuf:
- * @app: a #GsApp
- * @pixbuf: (transfer none) (not nullable): a #GdkPixbuf
- *
- * Add a pixbuf to the set of pixbufs used to represent the application.
- * Multiple pixbufs are supported as the application’s icon might be available
- * in multiple sizes.
- *
- * Only add pixbufs for native icon sizes provided by the backend — don’t resize
- * a pixbuf to pass to this function. #GsApp can handle resizing pixbufs itself.
- *
- * Since: 40
- **/
-void
-gs_app_add_pixbuf (GsApp *app, GdkPixbuf *pixbuf)
-{
-       GsAppPrivate *priv = gs_app_get_instance_private (app);
-       g_autoptr(GMutexLocker) locker = NULL;
-       guint pixbuf_width;
-
-       g_return_if_fail (GS_IS_APP (app));
-       g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
-
-       locker = g_mutex_locker_new (&priv->mutex);
-       pixbuf_width = gdk_pixbuf_get_width (pixbuf);
-
-       if (priv->pixbufs == NULL)
-               priv->pixbufs = g_ptr_array_new_with_free_func (g_object_unref);
-
-       /* Replace any existing pixbuf of the same size, or insert in order. */
-       for (guint i = 0; i < priv->pixbufs->len; i++) {
-               GdkPixbuf *p = priv->pixbufs->pdata[i];
-               guint p_width = gdk_pixbuf_get_width (p);
-
-               if (p_width == pixbuf_width) {
-                       g_set_object (&priv->pixbufs->pdata[i], pixbuf);
-                       return;
-               } else if (p_width > pixbuf_width) {
-                       g_ptr_array_insert (priv->pixbufs, i, g_object_ref (pixbuf));
-                       return;
-               }
-       }
-
-       /* Append if the array is empty. */
-       g_ptr_array_add (priv->pixbufs, g_object_ref (pixbuf));
-}
-
 /**
  * gs_app_set_action_screenshot:
  * @app: a #GsApp
@@ -4827,7 +4842,6 @@ gs_app_finalize (GObject *object)
                g_object_unref (priv->local_file);
        if (priv->content_rating != NULL)
                g_object_unref (priv->content_rating);
-       g_clear_pointer (&priv->pixbufs, g_ptr_array_unref);
        if (priv->action_screenshot != NULL)
                g_object_unref (priv->action_screenshot);
 
diff --git a/lib/gs-app.h b/lib/gs-app.h
index 0ea253d5b..d550d6b9f 100644
--- a/lib/gs-app.h
+++ b/lib/gs-app.h
@@ -356,11 +356,13 @@ void               gs_app_set_management_plugin   (GsApp          *app,
 gboolean        gs_app_has_pixbufs             (GsApp          *app);
 GdkPixbuf      *gs_app_load_pixbuf             (GsApp          *app,
                                                 guint           size);
-void            gs_app_add_pixbuf              (GsApp          *app,
-                                                GdkPixbuf      *pixbuf);
+GIcon          *gs_app_get_icon_for_size       (GsApp          *app,
+                                                guint           size,
+                                                guint           scale,
+                                                const gchar    *fallback_icon_name);
 GPtrArray      *gs_app_get_icons               (GsApp          *app);
 void            gs_app_add_icon                (GsApp          *app,
-                                                AsIcon         *icon);
+                                                GIcon          *icon);
 void            gs_app_remove_all_icons        (GsApp          *app);
 gboolean        gs_app_get_use_drop_shadow     (GsApp          *app);
 GFile          *gs_app_get_local_file          (GsApp          *app);
diff --git a/lib/gs-icon.c b/lib/gs-icon.c
new file mode 100644
index 000000000..a88e62d34
--- /dev/null
+++ b/lib/gs-icon.c
@@ -0,0 +1,305 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Endless OS Foundation, Inc
+ *
+ * Author: Philip Withnall <pwithnall endlessos org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/**
+ * SECTION:gs-icon
+ * @short_description: Utilities for handling #GIcons
+ *
+ * This file provides several utilities for creating and handling #GIcon
+ * instances. #GIcon is used for representing icon sources throughout
+ * gnome-software, as it has low memory overheads, and allows the most
+ * appropriate icon data to be loaded when it’s needed to be used in a UI.
+ *
+ * gnome-software uses various classes which implement #GIcon, mostly the
+ * built-in ones provided by GIO, but also #GsRemoteIcon. All of them are tagged
+ * with `width` and `height` metadata (when that data was available at
+ * construction time). See gs_icon_get_width().
+ *
+ * Since: 40
+ */
+
+#include "config.h"
+
+#include <appstream.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gs-icon.h"
+#include "gs-remote-icon.h"
+
+/**
+ * gs_icon_get_width:
+ * @icon: a #GIcon
+ *
+ * Get the width of an icon, if it was attached as metadata when the #GIcon was
+ * created from an #AsIcon.
+ *
+ * Returns: width of the icon (in device pixels), or `0` if unknown
+ * Since: 40
+ */
+guint
+gs_icon_get_width (GIcon *icon)
+{
+       g_return_val_if_fail (G_IS_ICON (icon), 0);
+
+       return GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (icon), "width"));
+}
+
+/**
+ * gs_icon_set_width:
+ * @icon: a #GIcon
+ * @width: width of the icon, in device pixels
+ *
+ * Set the width of an icon. See gs_icon_get_width().
+ *
+ * Since: 40
+ */
+void
+gs_icon_set_width (GIcon *icon,
+                   guint  width)
+{
+       g_return_if_fail (G_IS_ICON (icon));
+
+       g_object_set_data (G_OBJECT (icon), "width", GUINT_TO_POINTER (width));
+}
+
+/**
+ * gs_icon_get_height:
+ * @icon: a #GIcon
+ *
+ * Get the height of an icon, if it was attached as metadata when the #GIcon was
+ * created from an #AsIcon.
+ *
+ * Returns: height of the icon (in device pixels), or `0` if unknown
+ * Since: 40
+ */
+guint
+gs_icon_get_height (GIcon *icon)
+{
+       g_return_val_if_fail (G_IS_ICON (icon), 0);
+
+       return GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (icon), "height"));
+}
+
+/**
+ * gs_icon_set_height:
+ * @icon: a #GIcon
+ * @height: height of the icon, in device pixels
+ *
+ * Set the height of an icon. See gs_icon_get_height().
+ *
+ * Since: 40
+ */
+void
+gs_icon_set_height (GIcon *icon,
+                    guint  height)
+{
+       g_return_if_fail (G_IS_ICON (icon));
+
+       g_object_set_data (G_OBJECT (icon), "height", GUINT_TO_POINTER (height));
+}
+
+/**
+ * gs_icon_get_scale:
+ * @icon: a #GIcon
+ *
+ * Get the scale of an icon, if it was attached as metadata when the #GIcon was
+ * created from an #AsIcon.
+ *
+ * See gtk_widget_get_scale_factor() for more information about scales.
+ *
+ * Returns: scale of the icon, or `1` if unknown; guaranteed to always be
+ *     greater than or equal to 1
+ * Since: 40
+ */
+guint
+gs_icon_get_scale (GIcon *icon)
+{
+       g_return_val_if_fail (G_IS_ICON (icon), 0);
+
+       return MAX (1, GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (icon), "scale")));
+}
+
+/**
+ * gs_icon_set_scale:
+ * @icon: a #GIcon
+ * @scale: scale of the icon, which must be greater than or equal to 1
+ *
+ * Set the scale of an icon. See gs_icon_get_scale().
+ *
+ * Since: 40
+ */
+void
+gs_icon_set_scale (GIcon *icon,
+                   guint  scale)
+{
+       g_return_if_fail (G_IS_ICON (icon));
+       g_return_if_fail (scale >= 1);
+
+       g_object_set_data (G_OBJECT (icon), "scale", GUINT_TO_POINTER (scale));
+}
+
+static GIcon *
+gs_icon_load_local (AsIcon *icon)
+{
+       const gchar *filename = as_icon_get_filename (icon);
+       g_autoptr(GFile) file = NULL;
+
+       if (filename == NULL)
+               return NULL;
+
+       file = g_file_new_for_path (filename);
+       return g_file_icon_new (file);
+}
+
+static GIcon *
+gs_icon_load_stock (AsIcon *icon)
+{
+       const gchar *name = as_icon_get_name (icon);
+
+       if (name == NULL)
+               return NULL;
+
+       return g_themed_icon_new (name);
+}
+
+static GIcon *
+gs_icon_load_remote (AsIcon *icon)
+{
+       const gchar *url = as_icon_get_url (icon);
+
+       if (url == NULL)
+               return NULL;
+
+       /* Load local files directly. */
+       if (g_str_has_prefix (url, "file:")) {
+               g_autoptr(GFile) file = g_file_new_for_path (url + strlen ("file:"));
+               return g_file_icon_new (file);
+       }
+
+       /* Only HTTP and HTTPS are supported. */
+       if (!g_str_has_prefix (url, "http:") &&
+           !g_str_has_prefix (url, "https:"))
+               return NULL;
+
+       return gs_remote_icon_new (url);
+}
+
+static GIcon *
+gs_icon_load_cached (AsIcon *icon)
+{
+       const gchar *filename = as_icon_get_filename (icon);
+       const gchar *name = as_icon_get_name (icon);
+       g_autofree gchar *full_filename = NULL;
+       g_autoptr(GFile) file = NULL;
+
+       if (filename == NULL || name == NULL)
+               return NULL;
+
+       if (!g_str_has_suffix (filename, name)) {
+               /* Spec: 
https://www.freedesktop.org/software/appstream/docs/sect-AppStream-IconCache.html#spec-iconcache-location */
+               if (as_icon_get_scale (icon) <= 1) {
+                       full_filename = g_strdup_printf ("%s/%ux%u/%s",
+                                                        filename,
+                                                        as_icon_get_width (icon),
+                                                        as_icon_get_height (icon),
+                                                        name);
+               } else {
+                       full_filename = g_strdup_printf ("%s/%ux%u@%u/%s",
+                                                        filename,
+                                                        as_icon_get_width (icon),
+                                                        as_icon_get_height (icon),
+                                                        as_icon_get_scale (icon),
+                                                        name);
+               }
+
+               filename = full_filename;
+       }
+
+       file = g_file_new_for_path (filename);
+       return g_file_icon_new (file);
+}
+
+/**
+ * gs_icon_new_for_appstream_icon:
+ * @appstream_icon: an #AsIcon
+ *
+ * Create a new #GIcon representing the given #AsIcon. The actual type of the
+ * returned icon will vary depending on the #AsIconKind of @appstream_icon.
+ *
+ * If the width or height of the icon are set on the #AsIcon, they are stored
+ * as the `width` and `height` data associated with the returned object, using
+ * g_object_set_data().
+ *
+ * This can fail (and return %NULL) if the @appstream_icon has invalid or
+ * missing properties.
+ *
+ * Returns: (transfer full) (nullable): the #GIcon, or %NULL
+ * Since: 40
+ */
+GIcon *
+gs_icon_new_for_appstream_icon (AsIcon *appstream_icon)
+{
+       g_autoptr(GIcon) icon = NULL;
+
+       g_return_val_if_fail (AS_IS_ICON (appstream_icon), NULL);
+
+       switch (as_icon_get_kind (appstream_icon)) {
+       case AS_ICON_KIND_LOCAL:
+               icon = gs_icon_load_local (appstream_icon);
+               break;
+       case AS_ICON_KIND_STOCK:
+               icon = gs_icon_load_stock (appstream_icon);
+               break;
+       case AS_ICON_KIND_REMOTE:
+               icon = gs_icon_load_remote (appstream_icon);
+               break;
+       case AS_ICON_KIND_CACHED:
+               icon = gs_icon_load_cached (appstream_icon);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       if (icon == NULL) {
+               g_debug ("Error creating GIcon for AsIcon of kind %s",
+                        as_icon_kind_to_string (as_icon_get_kind (appstream_icon)));
+               return NULL;
+       }
+
+       /* Store the width, height and scale as associated metadata (if
+        * available) so that #GsApp can sort icons by size and return the most
+        * appropriately sized one in gs_app_get_icon_by_size().
+        *
+        * FIXME: Ideally we’d store these as properties on the objects, but
+        * GIO currently doesn’t allow subclassing of its #GIcon classes. If we
+        * were to implement a #GLoadableIcon with these as properties, all the
+        * fast paths in GTK for loading icon data (particularly named icons)
+        * would be ignored.
+        *
+        * Storing the width and height as associated metadata means GObject
+        * creates a hash table for each GIcon object. This is a waste of memory
+        * (compared to using properties), but seems like the least-worst
+        * option.
+        *
+        * See https://gitlab.gnome.org/GNOME/glib/-/issues/2345
+        */
+       if (as_icon_get_width (appstream_icon) != 0 || as_icon_get_height (appstream_icon) != 0) {
+               gs_icon_set_width (icon, as_icon_get_width (appstream_icon));
+               gs_icon_set_height (icon, as_icon_get_height (appstream_icon));
+       }
+       if (as_icon_get_scale (appstream_icon) != 0)
+               gs_icon_set_scale (icon, as_icon_get_scale (appstream_icon));
+
+       return g_steal_pointer (&icon);
+}
diff --git a/lib/gs-icon.h b/lib/gs-icon.h
new file mode 100644
index 000000000..b1b7a892d
--- /dev/null
+++ b/lib/gs-icon.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Endless OS Foundation, Inc
+ *
+ * Author: Philip Withnall <pwithnall endlessos org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#pragma once
+
+#include <appstream.h>
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+guint           gs_icon_get_width                      (GIcon                   *icon);
+void            gs_icon_set_width                      (GIcon                   *icon,
+                                                        guint                    width);
+guint           gs_icon_get_height                     (GIcon                   *icon);
+void            gs_icon_set_height                     (GIcon                   *icon,
+                                                        guint                    height);
+guint           gs_icon_get_scale                      (GIcon                   *icon);
+void            gs_icon_set_scale                      (GIcon                   *icon,
+                                                        guint                    scale);
+
+GIcon          *gs_icon_new_for_appstream_icon         (AsIcon                  *appstream_icon);
+
+G_END_DECLS
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index 7fac824c1..2cc0fc4d4 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -3043,19 +3043,6 @@ gs_plugin_loader_monitor_network (GsPluginLoader *plugin_loader)
 
 /******************************************************************************/
 
-static AsIcon *
-_gs_app_get_icon_by_kind (GsApp *app, AsIconKind kind)
-{
-       GPtrArray *icons = gs_app_get_icons (app);
-       guint i;
-       for (i = 0; icons != NULL && i < icons->len; i++) {
-               AsIcon *ic = g_ptr_array_index (icons, i);
-               if (as_icon_get_kind (ic) == kind)
-                       return ic;
-       }
-       return NULL;
-}
-
 static void
 generic_update_cancelled_cb (GCancellable *cancellable, gpointer data)
 {
@@ -3331,15 +3318,14 @@ gs_plugin_loader_process_thread_cb (GTask *task,
        case GS_PLUGIN_ACTION_FILE_TO_APP:
                for (guint j = 0; j < gs_app_list_length (list); j++) {
                        GsApp *app = gs_app_list_index (list, j);
-                       if (_gs_app_get_icon_by_kind (app, AS_ICON_KIND_STOCK) == NULL &&
-                           _gs_app_get_icon_by_kind (app, AS_ICON_KIND_LOCAL) == NULL &&
-                           _gs_app_get_icon_by_kind (app, AS_ICON_KIND_CACHED) == NULL) {
-                               g_autoptr(AsIcon) ic = as_icon_new ();
-                               as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
+                       if (gs_app_get_icons (app) == NULL) {
+                               g_autoptr(GIcon) ic = NULL;
+                               const gchar *icon_name;
                                if (gs_app_has_quirk (app, GS_APP_QUIRK_HAS_SOURCE))
-                                       as_icon_set_name (ic, "x-package-repository");
+                                       icon_name = "x-package-repository";
                                else
-                                       as_icon_set_name (ic, "application-x-executable");
+                                       icon_name = "application-x-executable";
+                               ic = g_themed_icon_new (icon_name);
                                gs_app_add_icon (app, ic);
                        }
                }
diff --git a/lib/meson.build b/lib/meson.build
index c9b9367b7..5cad44a4a 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -10,6 +10,7 @@ libgnomesoftware_public_headers = [
   'gs-category.h',
   'gs-category-manager.h',
   'gs-desktop-data.h',
+  'gs-icon.h',
   'gs-ioprio.h',
   'gs-key-colors.h',
   'gs-metered.h',
@@ -76,6 +77,7 @@ libgnomesoftware = static_library(
     'gs-category-manager.c',
     'gs-debug.c',
     'gs-desktop-data.c',
+    'gs-icon.c',
     'gs-ioprio.c',
     'gs-ioprio.h',
     'gs-key-colors.c',
diff --git a/plugins/core/gs-appstream.c b/plugins/core/gs-appstream.c
index 53648166f..433b0a22e 100644
--- a/plugins/core/gs-appstream.c
+++ b/plugins/core/gs-appstream.c
@@ -192,6 +192,14 @@ gs_appstream_get_icon_by_kind_and_size (XbNode *component, AsIconKind icon_kind,
        return gs_appstream_new_icon (component, icon, icon_kind, sz);
 }
 
+static void
+app_add_icon (GsApp  *app,
+              AsIcon *as_icon)
+{
+       g_autoptr(GIcon) icon = gs_icon_new_for_appstream_icon (as_icon);
+       gs_app_add_icon (app, icon);
+}
+
 static void
 gs_appstream_refine_icon (GsPlugin *plugin, GsApp *app, XbNode *component)
 {
@@ -205,7 +213,7 @@ gs_appstream_refine_icon (GsPlugin *plugin, GsApp *app, XbNode *component)
                 * theme (usually more stock icon entries are added to permit huge themes like Papirus
                 * to style all apps in the software center). Since we can not rely on the icon's presence,
                 * we also add other icons to the list and do not return here. */
-               gs_app_add_icon (app, icon);
+               app_add_icon (app, icon);
        }
 
        /* cached icon for large uses */
@@ -213,7 +221,7 @@ gs_appstream_refine_icon (GsPlugin *plugin, GsApp *app, XbNode *component)
                                                       AS_ICON_KIND_CACHED,
                                                       128 * gs_plugin_get_scale (plugin));
        if (icon != NULL) {
-               gs_app_add_icon (app, icon);
+               app_add_icon (app, icon);
        }
 
        /* cached icon for normal uses */
@@ -221,7 +229,7 @@ gs_appstream_refine_icon (GsPlugin *plugin, GsApp *app, XbNode *component)
                                                       AS_ICON_KIND_CACHED,
                                                       64 * gs_plugin_get_scale (plugin));
        if (icon != NULL) {
-               gs_app_add_icon (app, icon);
+               app_add_icon (app, icon);
        }
 
        /* prefer local */
@@ -234,14 +242,14 @@ gs_appstream_refine_icon (GsPlugin *plugin, GsApp *app, XbNode *component)
                                 as_icon_get_name (icon));
                        as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
                }
-               gs_app_add_icon (app, icon);
+               app_add_icon (app, icon);
                return;
        }
 
        /* remote URL */
        icon = gs_appstream_get_icon_by_kind (component, AS_ICON_KIND_REMOTE);
        if (icon != NULL) {
-               gs_app_add_icon (app, icon);
+               app_add_icon (app, icon);
                return;
        }
 
@@ -249,7 +257,7 @@ gs_appstream_refine_icon (GsPlugin *plugin, GsApp *app, XbNode *component)
        n = xb_node_query_first (component, "icon", NULL);
        if (n != NULL) {
                icon = gs_appstream_new_icon (component, n, AS_ICON_KIND_STOCK, 0);
-               gs_app_add_icon (app, icon);
+               app_add_icon (app, icon);
        }
 }
 
diff --git a/plugins/core/gs-plugin-generic-updates.c b/plugins/core/gs-plugin-generic-updates.c
index 319529f71..bb05492a5 100644
--- a/plugins/core/gs-plugin-generic-updates.c
+++ b/plugins/core/gs-plugin-generic-updates.c
@@ -41,7 +41,7 @@ gs_plugin_generic_updates_get_os_update (GsPlugin *plugin)
 {
        GsApp *app;
        const gchar *id = "org.gnome.Software.OsUpdate";
-       g_autoptr(AsIcon) ic = NULL;
+       g_autoptr(GIcon) ic = NULL;
 
        /* create new */
        app = gs_app_new (id);
@@ -62,9 +62,7 @@ gs_plugin_generic_updates_get_os_update (GsPlugin *plugin)
        gs_app_set_description (app,
                                GS_APP_QUALITY_NORMAL,
                                gs_app_get_summary (app));
-       ic = as_icon_new ();
-       as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
-       as_icon_set_name (ic, "software-update-available-symbolic");
+       ic = g_themed_icon_new ("software-update-available-symbolic");
        gs_app_add_icon (app, ic);
        return app;
 }
diff --git a/plugins/core/gs-plugin-icons.c b/plugins/core/gs-plugin-icons.c
index 3d71fc4b7..34bf52442 100644
--- a/plugins/core/gs-plugin-icons.c
+++ b/plugins/core/gs-plugin-icons.c
@@ -19,36 +19,15 @@
  *
  * It is provided so that each plugin handling icons does not
  * have to handle the download and caching functionality.
+ *
+ * FIXME: This plugin will eventually go away. Currently it only exists as the
+ * plugin threading code is a convenient way of ensuring that loading the remote
+ * icons happens in a worker thread.
  */
 
-struct GsPluginData {
-       GtkIconTheme            *icon_theme;
-       GMutex                   icon_theme_lock;
-       GHashTable              *icon_theme_paths;
-};
-
-static void gs_plugin_icons_add_theme_path (GsPlugin *plugin, const gchar *path);
-
 void
 gs_plugin_initialize (GsPlugin *plugin)
 {
-       GsPluginData *priv = gs_plugin_alloc_data (plugin, sizeof(GsPluginData));
-       const gchar *test_search_path;
-
-       priv->icon_theme = gtk_icon_theme_new ();
-       gtk_icon_theme_set_screen (priv->icon_theme, gdk_screen_get_default ());
-       priv->icon_theme_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
-       g_mutex_init (&priv->icon_theme_lock);
-
-       test_search_path = g_getenv ("GS_SELF_TEST_ICON_THEME_PATH");
-       if (test_search_path != NULL) {
-               g_auto(GStrv) dirs = g_strsplit (test_search_path, ":", -1);
-
-               /* add_theme_path() prepends, so we have to iterate in reverse to preserve order */
-               for (gsize i = g_strv_length (dirs); i > 0; i--)
-                       gs_plugin_icons_add_theme_path (plugin, dirs[i - 1]);
-       }
-
        /* needs remote icons downloaded */
        gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "appstream");
 }
@@ -56,245 +35,7 @@ gs_plugin_initialize (GsPlugin *plugin)
 void
 gs_plugin_destroy (GsPlugin *plugin)
 {
-       GsPluginData *priv = gs_plugin_get_data (plugin);
-       g_object_unref (priv->icon_theme);
-       g_hash_table_unref (priv->icon_theme_paths);
-       g_mutex_clear (&priv->icon_theme_lock);
-}
-
-static gboolean
-gs_plugin_icons_download (GsPlugin *plugin,
-                         const gchar *uri,
-                         const gchar *filename,
-                         GError **error)
-{
-       guint status_code;
-       g_autoptr(GdkPixbuf) pixbuf_new = NULL;
-       g_autoptr(GdkPixbuf) pixbuf = NULL;
-       g_autoptr(GInputStream) stream = NULL;
-       g_autoptr(SoupMessage) msg = NULL;
-
-       /* create the GET data */
-       msg = soup_message_new (SOUP_METHOD_GET, uri);
-       if (msg == NULL) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                            "%s is not a valid URL", uri);
-               return FALSE;
-       }
-
-       /* set sync request */
-       status_code = soup_session_send_message (gs_plugin_get_soup_session (plugin), msg);
-       if (status_code != SOUP_STATUS_OK) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_DOWNLOAD_FAILED,
-                            "Failed to download icon %s: %s",
-                            uri, soup_status_get_phrase (status_code));
-               return FALSE;
-       }
-
-       /* we're assuming this is a 64x64 png file, resize if not */
-       stream = g_memory_input_stream_new_from_data (msg->response_body->data,
-                                                     msg->response_body->length,
-                                                     NULL);
-       pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error);
-       if (pixbuf == NULL) {
-               gs_utils_error_convert_gdk_pixbuf (error);
-               return FALSE;
-       }
-       if (gdk_pixbuf_get_height (pixbuf) == 64 &&
-           gdk_pixbuf_get_width (pixbuf) == 64) {
-               pixbuf_new = g_object_ref (pixbuf);
-       } else {
-               pixbuf_new = gdk_pixbuf_scale_simple (pixbuf, 64, 64,
-                                                     GDK_INTERP_BILINEAR);
-       }
-
-       /* write file */
-       if (!gdk_pixbuf_save (pixbuf_new, filename, "png", error, NULL)) {
-               gs_utils_error_convert_gdk_pixbuf (error);
-               return FALSE;
-       }
-       return TRUE;
-}
-
-static GdkPixbuf *
-gs_plugin_icons_load_local (GsPlugin *plugin, AsIcon *icon, GError **error)
-{
-       GdkPixbuf *pixbuf;
-       gint size;
-       if (as_icon_get_filename (icon) == NULL) {
-               g_set_error_literal (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                    "icon has no filename");
-               return NULL;
-       }
-       size = (gint) (64 * gs_plugin_get_scale (plugin));
-       pixbuf = gdk_pixbuf_new_from_file_at_size (as_icon_get_filename (icon),
-                                                  size, size, error);
-       if (pixbuf == NULL) {
-               gs_utils_error_convert_gdk_pixbuf (error);
-               return NULL;
-       }
-       return pixbuf;
-}
-
-static gchar *
-gs_plugin_icons_get_cache_fn (AsIcon *icon)
-{
-       g_autofree gchar *basename = NULL;
-       g_autofree gchar *checksum = NULL;
-       checksum = g_compute_checksum_for_string (G_CHECKSUM_SHA1,
-                                                 as_icon_get_url (icon),
-                                                 -1);
-       basename = g_path_get_basename (as_icon_get_url (icon));
-       return g_strdup_printf ("%s-%s", checksum, basename);
-}
-
-static GdkPixbuf *
-gs_plugin_icons_load_remote (GsPlugin *plugin, AsIcon *icon, GError **error)
-{
-       const gchar *fn;
-       gchar *found;
-
-       /* not applicable for remote */
-       if (as_icon_get_url (icon) == NULL) {
-               g_set_error_literal (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                    "icon has no URL");
-               return NULL;
-       }
-
-       /* set cache filename if not already set */
-       if (as_icon_get_filename (icon) == NULL) {
-               g_autofree gchar *fn_cache = NULL;
-               g_autofree gchar *fn_basename = NULL;
-
-               /* use a hash-prefixed filename to avoid cache clashes */
-               fn_basename = gs_plugin_icons_get_cache_fn (icon);
-               fn_cache = gs_utils_get_cache_filename ("icons",
-                                                       fn_basename,
-                                                       GS_UTILS_CACHE_FLAG_WRITEABLE,
-                                                       error);
-               if (fn_cache == NULL)
-                       return NULL;
-               as_icon_set_filename (icon, fn_cache);
-       }
-
-       /* already in cache */
-       if (g_file_test (as_icon_get_filename (icon), G_FILE_TEST_EXISTS))
-               return gs_plugin_icons_load_local (plugin, icon, error);
-
-       /* a REMOTE that's really LOCAL */
-       if (g_str_has_prefix (as_icon_get_url (icon), "file://")) {
-               as_icon_set_filename (icon, as_icon_get_url (icon) + 7);
-               as_icon_set_kind (icon, AS_ICON_KIND_LOCAL);
-               return gs_plugin_icons_load_local (plugin, icon, error);
-       }
-
-       /* convert filename from jpg to png */
-       fn = as_icon_get_filename (icon);
-       found = g_strstr_len (fn, -1, ".jpg");
-       if (found != NULL)
-               memcpy (found, ".png", 4);
-
-       /* create runtime dir and download */
-       if (!gs_mkdir_parent (fn, error))
-               return NULL;
-       if (!gs_plugin_icons_download (plugin, as_icon_get_url (icon), fn, error))
-               return NULL;
-       as_icon_set_kind (icon, AS_ICON_KIND_LOCAL);
-       return gs_plugin_icons_load_local (plugin, icon, error);
-}
-
-static void
-gs_plugin_icons_add_theme_path (GsPlugin *plugin, const gchar *path)
-{
-       GsPluginData *priv = gs_plugin_get_data (plugin);
-       if (path == NULL)
-               return;
-
-       if (!g_hash_table_contains (priv->icon_theme_paths, path)) {
-               gtk_icon_theme_prepend_search_path (priv->icon_theme, path);
-               g_hash_table_add (priv->icon_theme_paths, g_strdup (path));
-       }
-}
-
-static GdkPixbuf *
-gs_plugin_icons_load_stock (GsPlugin *plugin, AsIcon *icon, GError **error)
-{
-       GsPluginData *priv = gs_plugin_get_data (plugin);
-       GdkPixbuf *pixbuf;
-       gint size;
-       g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->icon_theme_lock);
-
-       /* required */
-       if (as_icon_get_name (icon) == NULL) {
-               g_set_error_literal (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                    "icon has no name");
-               return NULL;
-       }
-
-       size = (gint) (64 * gs_plugin_get_scale (plugin));
-       pixbuf = gtk_icon_theme_load_icon (priv->icon_theme,
-                                          as_icon_get_name (icon),
-                                          size,
-                                          GTK_ICON_LOOKUP_USE_BUILTIN |
-                                          GTK_ICON_LOOKUP_FORCE_SIZE,
-                                          error);
-       if (pixbuf == NULL) {
-               gs_utils_error_convert_gdk_pixbuf (error);
-               return NULL;
-       }
-       return pixbuf;
-}
-
-static GdkPixbuf *
-gs_plugin_icons_load_cached (GsPlugin *plugin, AsIcon *icon, GError **error)
-{
-       const gchar *fname = as_icon_get_filename (icon);
-       const gchar *icon_fname = as_icon_get_name (icon);
-
-       if (fname == NULL || icon_fname == NULL) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_FAILED,
-                            "Icon %s has no full filename - can not load pixbuf.",
-                            icon_fname);
-               return NULL;
-       }
-
-       if (!g_str_has_suffix (fname, icon_fname)) {
-               g_autofree gchar *full_fname = NULL;
-               if (as_icon_get_scale (icon) <= 1) {
-                       full_fname = g_strdup_printf ("%s/%ux%u/%s",
-                                                       fname,
-                                                       as_icon_get_width (icon),
-                                                       as_icon_get_height (icon),
-                                                       icon_fname);
-               } else {
-                       full_fname = g_strdup_printf ("%s/%ux%u@%u/%s",
-                                                       fname,
-                                                       as_icon_get_width (icon),
-                                                       as_icon_get_height (icon),
-                                                       as_icon_get_scale (icon),
-                                                       icon_fname);
-               }
-               /* cache new filename, overriding the incomplete one */
-               as_icon_set_filename (icon, full_fname);
-               fname = as_icon_get_filename (icon);
-       }
-
-       return gdk_pixbuf_new_from_file_at_size (fname,
-                                                (gint) as_icon_get_width (icon),
-                                                (gint) as_icon_get_height (icon),
-                                                error);
+       /* Nothing to do here */
 }
 
 static gboolean
@@ -306,49 +47,35 @@ refine_app (GsPlugin             *plugin,
 {
        GPtrArray *icons;
        guint i;
+       SoupSession *soup_session;
+       guint maximum_icon_size;
 
        /* not required */
        if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON) == 0)
                return TRUE;
 
-       /* invalid */
-       if (gs_app_has_pixbufs (app))
-               return TRUE;
+       soup_session = gs_plugin_get_soup_session (plugin);
+
+       /* Currently a 160px icon is needed for #GsFeatureTile, at most. */
+       maximum_icon_size = 160 * gs_plugin_get_scale (plugin);
 
        /* process all icons */
        icons = gs_app_get_icons (app);
        for (i = 0; icons != NULL && i < icons->len; i++) {
-               AsIcon *icon = g_ptr_array_index (icons, i);
-               g_autoptr(GdkPixbuf) pixbuf = NULL;
+               GIcon *icon = g_ptr_array_index (icons, i);
                g_autoptr(GError) error_local = NULL;
 
-               /* handle different icon types */
-               switch (as_icon_get_kind (icon)) {
-               case AS_ICON_KIND_LOCAL:
-                       pixbuf = gs_plugin_icons_load_local (plugin, icon, &error_local);
-                       break;
-               case AS_ICON_KIND_STOCK:
-                       pixbuf = gs_plugin_icons_load_stock (plugin, icon, &error_local);
-                       break;
-               case AS_ICON_KIND_REMOTE:
-                       pixbuf = gs_plugin_icons_load_remote (plugin, icon, &error_local);
-                       break;
-               case AS_ICON_KIND_CACHED:
-                       pixbuf = gs_plugin_icons_load_cached (plugin, icon, &error_local);
-                       break;
-               default:
-                       g_set_error (&error_local,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                                    "icon kind '%s' unknown",
-                                    as_icon_kind_to_string (as_icon_get_kind (icon)));
-                       break;
-               }
-               if (pixbuf != NULL) {
-                       gs_app_add_pixbuf (app, pixbuf);
-               } else {
+               /* Only remote icons need to be cached. */
+               if (!GS_IS_REMOTE_ICON (icon))
+                       continue;
+
+               if (!gs_remote_icon_ensure_cached (GS_REMOTE_ICON (icon),
+                                                  soup_session,
+                                                  maximum_icon_size,
+                                                  cancellable,
+                                                  &error_local)) {
                        /* we failed, but keep going */
-                       g_debug ("failed to load icon for %s: %s",
+                       g_debug ("failed to cache icon for %s: %s",
                                 gs_app_get_id (app),
                                 error_local->message);
                }
diff --git a/plugins/dummy/gs-plugin-dummy.c b/plugins/dummy/gs-plugin-dummy.c
index 334fbac24..c6cbe46af 100644
--- a/plugins/dummy/gs-plugin-dummy.c
+++ b/plugins/dummy/gs-plugin-dummy.c
@@ -276,7 +276,7 @@ gs_plugin_add_search (GsPlugin *plugin,
 {
        GsPluginData *priv = gs_plugin_get_data (plugin);
        g_autoptr(GsApp) app = NULL;
-       g_autoptr(AsIcon) ic = NULL;
+       g_autoptr(GIcon) ic = NULL;
 
        /* hang the plugin for 5 seconds */
        if (g_strcmp0 (values[0], "hang") == 0) {
@@ -305,9 +305,7 @@ gs_plugin_add_search (GsPlugin *plugin,
                g_timeout_add_seconds (1, gs_plugin_dummy_poll_cb, plugin);
 
        /* use a generic stock icon */
-       ic = as_icon_new ();
-       as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
-       as_icon_set_name (ic, "drive-harddisk");
+       ic = g_themed_icon_new ("drive-harddisk");
 
        /* add a live updatable normal application */
        app = gs_app_new ("chiron.desktop");
@@ -337,7 +335,7 @@ gs_plugin_add_updates (GsPlugin *plugin,
 {
        GsApp *app;
        GsApp *proxy;
-       g_autoptr(AsIcon) ic = NULL;
+       g_autoptr(GIcon) ic = NULL;
 
        /* update UI as this might take some time */
        gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_WAITING);
@@ -347,9 +345,7 @@ gs_plugin_add_updates (GsPlugin *plugin,
                return FALSE;
 
        /* use a generic stock icon */
-       ic = as_icon_new ();
-       as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
-       as_icon_set_name (ic, "drive-harddisk");
+       ic = g_themed_icon_new ("drive-harddisk");
 
        /* add a live updatable normal application */
        app = gs_app_new ("chiron.desktop");
@@ -650,10 +646,7 @@ refine_app (GsPlugin *plugin,
                if (gs_app_get_summary (app) == NULL)
                        gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, "tmp");
                if (gs_app_get_icons (app) == NULL) {
-                       g_autoptr(AsIcon) ic = NULL;
-                       ic = as_icon_new ();
-                       as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
-                       as_icon_set_name (ic, "drive-harddisk");
+                       g_autoptr(GIcon) ic = g_themed_icon_new ("drive-harddisk");
                        gs_app_add_icon (app, ic);
                }
        }
@@ -737,14 +730,14 @@ gs_plugin_add_category_apps (GsPlugin *plugin,
                             GCancellable *cancellable,
                             GError **error)
 {
-       g_autoptr(GdkPixbuf) pixbuf = gdk_pixbuf_new_from_file 
("/usr/share/icons/hicolor/48x48/apps/chiron.desktop.png", NULL);
+       g_autoptr(GIcon) icon = g_themed_icon_new ("chiron.desktop");
        g_autoptr(GsApp) app = gs_app_new ("chiron.desktop");
        gs_app_set_name (app, GS_APP_QUALITY_NORMAL, "Chiron");
        gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, "View and use virtual machines");
        gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, "http://www.box.org";);
        gs_app_set_kind (app, AS_COMPONENT_KIND_DESKTOP_APP);
        gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
-       gs_app_add_pixbuf (app, pixbuf);
+       gs_app_add_icon (app, icon);
        gs_app_set_kind (app, AS_COMPONENT_KIND_DESKTOP_APP);
        gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
        gs_app_list_add (list, app);
@@ -758,14 +751,14 @@ gs_plugin_add_recent (GsPlugin *plugin,
                      GCancellable *cancellable,
                      GError **error)
 {
-       g_autoptr(GdkPixbuf) pixbuf = gdk_pixbuf_new_from_file 
("/usr/share/icons/hicolor/48x48/apps/chiron.desktop.png", NULL);
+       g_autoptr(GIcon) icon = g_themed_icon_new ("chiron.desktop");
        g_autoptr(GsApp) app = gs_app_new ("chiron.desktop");
        gs_app_set_name (app, GS_APP_QUALITY_NORMAL, "Chiron");
        gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, "View and use virtual machines");
        gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, "http://www.box.org";);
        gs_app_set_kind (app, AS_COMPONENT_KIND_DESKTOP_APP);
        gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
-       gs_app_add_pixbuf (app, pixbuf);
+       gs_app_add_icon (app, icon);
        gs_app_set_kind (app, AS_COMPONENT_KIND_DESKTOP_APP);
        gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
        gs_app_list_add (list, app);
@@ -779,12 +772,10 @@ gs_plugin_add_distro_upgrades (GsPlugin *plugin,
                               GError **error)
 {
        g_autoptr(GsApp) app = NULL;
-       g_autoptr(AsIcon) ic = NULL;
+       g_autoptr(GIcon) ic = NULL;
 
        /* use stock icon */
-       ic = as_icon_new ();
-       as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
-       as_icon_set_name (ic, "application-x-addon");
+       ic = g_themed_icon_new ("application-x-addon");
 
        /* get existing item from the cache */
        app = gs_plugin_cache_lookup (plugin, 
"user/*/os-upgrade/org.fedoraproject.release-rawhide.upgrade/*");
diff --git a/plugins/eos-updater/gs-plugin-eos-updater.c b/plugins/eos-updater/gs-plugin-eos-updater.c
index a8d7127a7..fec3a027b 100644
--- a/plugins/eos-updater/gs-plugin-eos-updater.c
+++ b/plugins/eos-updater/gs-plugin-eos-updater.c
@@ -493,7 +493,7 @@ gs_plugin_setup (GsPlugin *plugin,
        g_autoptr(GError) error_local = NULL;
        g_autofree gchar *name_owner = NULL;
        g_autoptr(GsApp) app = NULL;
-       g_autoptr(AsIcon) ic = NULL;
+       g_autoptr(GIcon) ic = NULL;
        g_autoptr(GMutexLocker) locker = NULL;
 
        g_debug ("%s", G_STRFUNC);
@@ -549,9 +549,7 @@ gs_plugin_setup (GsPlugin *plugin,
        /* prepare EOS upgrade app + sync initial state */
 
        /* use stock icon */
-       ic = as_icon_new ();
-       as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
-       as_icon_set_name (ic, "application-x-addon");
+       ic = g_themed_icon_new ("application-x-addon");
 
        /* create the OS upgrade */
        app = gs_app_new ("com.endlessm.EOS.upgrade");
diff --git a/plugins/fedora-pkgdb-collections/gs-plugin-fedora-pkgdb-collections.c 
b/plugins/fedora-pkgdb-collections/gs-plugin-fedora-pkgdb-collections.c
index 21c187b11..e77665991 100644
--- a/plugins/fedora-pkgdb-collections/gs-plugin-fedora-pkgdb-collections.c
+++ b/plugins/fedora-pkgdb-collections/gs-plugin-fedora-pkgdb-collections.c
@@ -257,7 +257,8 @@ _create_upgrade_from_info (GsPlugin *plugin, PkgdbItem *item)
        g_autofree gchar *cache_key = NULL;
        g_autofree gchar *css = NULL;
        g_autofree gchar *url = NULL;
-       g_autoptr(AsIcon) ic = NULL;
+       g_autoptr(GFile) icon_file = NULL;
+       g_autoptr(GIcon) ic = NULL;
 
        /* search in the cache */
        cache_key = g_strdup_printf ("release-%u", item->version);
@@ -269,9 +270,8 @@ _create_upgrade_from_info (GsPlugin *plugin, PkgdbItem *item)
        app_version = g_strdup_printf ("%u", item->version);
 
        /* icon from disk */
-       ic = as_icon_new ();
-       as_icon_set_kind (ic, AS_ICON_KIND_LOCAL);
-       as_icon_set_filename (ic, "/usr/share/pixmaps/fedora-logo-sprite.png");
+       icon_file = g_file_new_for_path ("/usr/share/pixmaps/fedora-logo-sprite.png");
+       ic = g_file_icon_new (icon_file);
 
        /* create */
        app = gs_app_new (app_id);
diff --git a/plugins/flatpak/gs-flatpak-utils.c b/plugins/flatpak/gs-flatpak-utils.c
index 8c4715282..26d56f7a3 100644
--- a/plugins/flatpak/gs-flatpak-utils.c
+++ b/plugins/flatpak/gs-flatpak-utils.c
@@ -203,11 +203,11 @@ gs_flatpak_app_new_from_repo_file (GFile *file,
        if (repo_default_branch != NULL)
                gs_app_set_branch (app, repo_default_branch);
        repo_icon = g_key_file_get_string (kf, "Flatpak Repo", "Icon", NULL);
-       if (repo_icon != NULL) {
-               g_autoptr(AsIcon) ic = as_icon_new ();
-               as_icon_set_kind (ic, AS_ICON_KIND_REMOTE);
-               as_icon_set_url (ic, repo_icon);
-               gs_app_add_icon (app, ic);
+       if (repo_icon != NULL &&
+           (g_str_has_prefix (repo_icon, "http:") ||
+            g_str_has_prefix (repo_icon, "https:"))) {
+               g_autoptr(GIcon) icon = gs_remote_icon_new (repo_icon);
+               gs_app_add_icon (app, icon);
        }
 
        /* success */
diff --git a/plugins/flatpak/gs-flatpak.c b/plugins/flatpak/gs-flatpak.c
index 12bfc9ae1..84752b78f 100644
--- a/plugins/flatpak/gs-flatpak.c
+++ b/plugins/flatpak/gs-flatpak.c
@@ -348,15 +348,13 @@ gs_flatpak_create_app (GsFlatpak *self,
 
        /* fallback values */
        if (gs_flatpak_app_get_ref_kind (app) == FLATPAK_REF_KIND_RUNTIME) {
-               g_autoptr(AsIcon) icon = NULL;
+               g_autoptr(GIcon) icon = NULL;
                gs_app_set_name (app, GS_APP_QUALITY_NORMAL,
                                 flatpak_ref_get_name (FLATPAK_REF (xref)));
                gs_app_set_summary (app, GS_APP_QUALITY_NORMAL,
                                    "Framework for applications");
                gs_app_set_version (app, flatpak_ref_get_branch (FLATPAK_REF (xref)));
-               icon = as_icon_new ();
-               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-               as_icon_set_name (icon, "system-run-symbolic");
+               icon = g_themed_icon_new ("system-run-symbolic");
                gs_app_add_icon (app, icon);
        }
 
@@ -3086,6 +3084,7 @@ gs_flatpak_file_to_app_bundle (GsFlatpak *self,
        g_autoptr(GsApp) app = NULL;
        g_autoptr(FlatpakBundleRef) xref_bundle = NULL;
        g_autoptr(FlatpakInstalledRef) installed_ref = NULL;
+       g_autoptr(GIcon) icon = NULL;
        const char *origin = NULL;
 
        /* load bundle */
@@ -3146,23 +3145,12 @@ gs_flatpak_file_to_app_bundle (GsFlatpak *self,
        icon_data = flatpak_bundle_ref_get_icon (xref_bundle, size);
        if (icon_data == NULL)
                icon_data = flatpak_bundle_ref_get_icon (xref_bundle, 64);
-       if (icon_data != NULL) {
-               g_autoptr(GInputStream) stream_icon = NULL;
-               g_autoptr(GdkPixbuf) pixbuf = NULL;
-               stream_icon = g_memory_input_stream_new_from_bytes (icon_data);
-               pixbuf = gdk_pixbuf_new_from_stream (stream_icon, cancellable, error);
-               if (pixbuf == NULL) {
-                       gs_utils_error_convert_gdk_pixbuf (error);
-                       return NULL;
-               }
-               gs_app_add_pixbuf (app, pixbuf);
-       } else {
-               g_autoptr(AsIcon) icon = NULL;
-               icon = as_icon_new ();
-               as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-               as_icon_set_name (icon, "application-x-executable");
-               gs_app_add_icon (app, icon);
-       }
+
+       if (icon_data != NULL)
+               icon = g_bytes_icon_new (icon_data);
+       else
+               icon = g_themed_icon_new ("application-x-executable");
+       gs_app_add_icon (app, icon);
 
        /* not quite true: this just means we can update this specific app */
        if (flatpak_bundle_ref_get_origin (xref_bundle))
@@ -3277,11 +3265,11 @@ gs_flatpak_file_to_app_ref (GsFlatpak *self,
        if (ref_homepage != NULL)
                gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, ref_homepage);
        ref_icon = g_key_file_get_string (kf, "Flatpak Ref", "Icon", NULL);
-       if (ref_icon != NULL) {
-               g_autoptr(AsIcon) ic = as_icon_new ();
-               as_icon_set_kind (ic, AS_ICON_KIND_REMOTE);
-               as_icon_set_url (ic, ref_icon);
-               gs_app_add_icon (app, ic);
+       if (ref_icon != NULL &&
+           (g_str_has_prefix (ref_icon, "http:") ||
+            g_str_has_prefix (ref_icon, "https:"))) {
+               g_autoptr(GIcon) icon = gs_remote_icon_new (ref_icon);
+               gs_app_add_icon (app, icon);
        }
 
        /* set the origin data */
diff --git a/plugins/fwupd/gs-plugin-fwupd.c b/plugins/fwupd/gs-plugin-fwupd.c
index cda9093ec..9b3b0738f 100644
--- a/plugins/fwupd/gs-plugin-fwupd.c
+++ b/plugins/fwupd/gs-plugin-fwupd.c
@@ -278,7 +278,7 @@ gs_plugin_fwupd_new_app_from_device (GsPlugin *plugin, FwupdDevice *dev)
        FwupdRelease *rel = fwupd_device_get_release_default (dev);
        GsApp *app;
        g_autofree gchar *id = NULL;
-       g_autoptr(AsIcon) icon = NULL;
+       g_autoptr(GIcon) icon = NULL;
 
        /* older versions of fwups didn't record this for historical devices */
        if (fwupd_release_get_appstream_id (rel) == NULL)
@@ -306,9 +306,7 @@ gs_plugin_fwupd_new_app_from_device (GsPlugin *plugin, FwupdDevice *dev)
        gs_fwupd_app_set_device_id (app, fwupd_device_get_id (dev));
 
        /* create icon */
-       icon = as_icon_new ();
-       as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-       as_icon_set_name (icon, "application-x-firmware");
+       icon = g_themed_icon_new ("application-x-firmware");
        gs_app_add_icon (app, icon);
        gs_fwupd_app_set_from_device (app, dev);
        gs_fwupd_app_set_from_release (app, rel);
@@ -359,16 +357,15 @@ gs_plugin_fwupd_new_app_from_device_raw (GsPlugin *plugin, FwupdDevice *device)
        /* create icon */
        icons = fwupd_device_get_icons (device);
        for (guint j = 0; j < icons->len; j++) {
-               const gchar *icon = g_ptr_array_index (icons, j);
-               g_autoptr(AsIcon) icon_tmp = as_icon_new ();
-               if (g_str_has_prefix (icon, "/")) {
-                       as_icon_set_kind (icon_tmp, AS_ICON_KIND_LOCAL);
-                       as_icon_set_filename (icon_tmp, icon);
+               const gchar *icon_str = g_ptr_array_index (icons, j);
+               g_autoptr(GIcon) icon = NULL;
+               if (g_str_has_prefix (icon_str, "/")) {
+                       g_autoptr(GFile) icon_file = g_file_new_for_path (icon_str);
+                       icon = g_file_icon_new (icon_file);
                } else {
-                       as_icon_set_kind (icon_tmp, AS_ICON_KIND_STOCK);
-                       as_icon_set_name (icon_tmp, icon);
+                       icon = g_themed_icon_new (icon_str);
                }
-               gs_app_add_icon (app, icon_tmp);
+               gs_app_add_icon (app, icon);
        }
        return g_steal_pointer (&app);
 }
diff --git a/plugins/modalias/gs-plugin-modalias.c b/plugins/modalias/gs-plugin-modalias.c
index 87930b2c4..90b7d023f 100644
--- a/plugins/modalias/gs-plugin-modalias.c
+++ b/plugins/modalias/gs-plugin-modalias.c
@@ -125,10 +125,8 @@ refine_app (GsPlugin             *plugin,
                items = as_provided_get_items (prov);
                for (guint j = 0; j < items->len; j++) {
                        if (gs_plugin_modalias_matches (plugin, (const gchar*) g_ptr_array_index (items, j))) 
{
-                               g_autoptr(AsIcon) ic = NULL;
-                               ic = as_icon_new ();
-                               as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
-                               as_icon_set_name (ic, "emblem-system-symbolic");
+                               g_autoptr(GIcon) ic = NULL;
+                               ic = g_themed_icon_new ("emblem-system-symbolic");
                                gs_app_add_icon (app, ic);
                                gs_app_add_quirk (app, GS_APP_QUIRK_NOT_LAUNCHABLE);
                                break;
diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c
index 8a82e9630..18596d143 100644
--- a/plugins/snap/gs-plugin-snap.c
+++ b/plugins/snap/gs-plugin-snap.c
@@ -796,7 +796,7 @@ load_desktop_icon (GsApp *app, SnapdSnap *snap)
                g_autoptr(GKeyFile) desktop_file = NULL;
                g_autoptr(GError) error = NULL;
                g_autofree gchar *icon_value = NULL;
-               g_autoptr(AsIcon) icon = NULL;
+               g_autoptr(GIcon) icon = NULL;
 
                desktop_file_path = snapd_app_get_desktop_file (snap_app);
                if (desktop_file_path == NULL)
@@ -816,11 +816,10 @@ load_desktop_icon (GsApp *app, SnapdSnap *snap)
 
                icon = as_icon_new ();
                if (g_str_has_prefix (icon_value, "/")) {
-                       as_icon_set_kind (icon, AS_ICON_KIND_LOCAL);
-                       as_icon_set_filename (icon, icon_value);
+                       g_autoptr(GFile) icon_file = g_file_new_for_path (icon_value);
+                       icon = g_file_icon_new (icon_file);
                } else {
-                       as_icon_set_kind (icon, AS_ICON_KIND_STOCK);
-                       as_icon_set_name (icon, icon_value);
+                       icon = g_themed_icon_new (icon_value);
                }
                gs_app_add_icon (app, icon);
 
@@ -840,9 +839,7 @@ load_store_icon (GsApp *app, SnapdSnap *snap)
                return FALSE;
 
        if (g_str_has_prefix (icon_url, "http://";) || g_str_has_prefix (icon_url, "https://";)) {
-               g_autoptr(AsIcon) icon = as_icon_new ();
-               as_icon_set_kind (icon, AS_ICON_KIND_REMOTE);
-               as_icon_set_url (icon, icon_url);
+               g_autoptr(GIcon) icon = gs_remote_icon_new (icon_url);
                gs_app_add_icon (app, icon);
                return TRUE;
        }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]