summaryrefslogtreecommitdiffstats
path: root/lib/gs-icon.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gs-icon.c')
-rw-r--r--lib/gs-icon.c315
1 files changed, 315 insertions, 0 deletions
diff --git a/lib/gs-icon.c b/lib/gs-icon.c
new file mode 100644
index 0000000..955ab02
--- /dev/null
+++ b/lib/gs-icon.c
@@ -0,0 +1,315 @@
+/* -*- 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 *name_allocated = NULL;
+ g_autofree gchar *full_filename = NULL;
+ g_autoptr(GFile) file = NULL;
+
+ if (filename == NULL || name == NULL)
+ return NULL;
+
+ /* FIXME: Work around https://github.com/hughsie/appstream-glib/pull/390
+ * where appstream files generated with appstream-builder from
+ * appstream-glib, with its hidpi option enabled, will contain an
+ * unnecessary size subdirectory in the icon name. */
+ if (g_str_has_prefix (name, "64x64/"))
+ name = name_allocated = g_strdup (name + strlen ("64x64/"));
+ else if (g_str_has_prefix (name, "128x128/"))
+ name = name_allocated = g_strdup (name + strlen ("128x128/"));
+
+ 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);
+}