diff options
Diffstat (limited to 'lib/gs-icon.c')
-rw-r--r-- | lib/gs-icon.c | 315 |
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); +} |