1
0
Fork 0
gnome-software/lib/gs-icon.c
Daniel Baumann 68ee05b3fd
Adding upstream version 48.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 21:00:23 +02:00

315 lines
8.3 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- 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-or-later
*/
/**
* 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 its 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 wed store these as properties on the objects, but
* GIO currently doesnt 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);
}