/* -*- 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 * * 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 #include #include #include #include #include #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); }