diff options
Diffstat (limited to '')
-rw-r--r-- | src/gs-app-details-page.c | 451 |
1 files changed, 451 insertions, 0 deletions
diff --git a/src/gs-app-details-page.c b/src/gs-app-details-page.c new file mode 100644 index 0000000..c224f2a --- /dev/null +++ b/src/gs-app-details-page.c @@ -0,0 +1,451 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2013-2016 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2014-2018 Kalev Lember <klember@redhat.com> + * Copyright (C) 2021 Purism SPC + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +/** + * SECTION:gs-app-details-page + * @title: GsAppDetailsPage + * @include: gnome-software.h + * @stability: Stable + * @short_description: A small page showing an application's details + * + * This is a page from #GsUpdateDialog. + */ + +#include "config.h" + +#include <adwaita.h> +#include <glib/gi18n.h> + +#include "gs-app-details-page.h" +#include "gs-app-row.h" +#include "gs-update-list.h" +#include "gs-common.h" + +typedef enum { + PROP_APP = 1, + PROP_SHOW_BACK_BUTTON, + PROP_TITLE, +} GsAppDetailsPageProperty; + +enum { + SIGNAL_BACK_CLICKED, + SIGNAL_LAST +}; + +static GParamSpec *obj_props[PROP_TITLE + 1] = { NULL, }; + +static guint signals[SIGNAL_LAST] = { 0 }; + +struct _GsAppDetailsPage +{ + GtkBox parent_instance; + + GtkWidget *back_button; + GtkWidget *header_bar; + GtkWidget *label_details; + GtkWidget *permissions_section; + GtkWidget *permissions_section_list; + GtkWidget *status_page; + AdwWindowTitle *window_title; + + GsApp *app; /* (owned) (nullable) */ +}; + +G_DEFINE_TYPE (GsAppDetailsPage, gs_app_details_page, GTK_TYPE_BOX) + +static const struct { + GsAppPermissionsFlags permission; + const char *title; + const char *subtitle; +} permission_display_data[] = { + { GS_APP_PERMISSIONS_FLAGS_NETWORK, N_("Network"), N_("Can communicate over the network") }, + { GS_APP_PERMISSIONS_FLAGS_SYSTEM_BUS, N_("System Services"), N_("Can access D-Bus services on the system bus") }, + { GS_APP_PERMISSIONS_FLAGS_SESSION_BUS, N_("Session Services"), N_("Can access D-Bus services on the session bus") }, + { GS_APP_PERMISSIONS_FLAGS_DEVICES, N_("Devices"), N_("Can access system device files") }, + { GS_APP_PERMISSIONS_FLAGS_HOME_FULL, N_("Home folder"), N_("Can view, edit and create files") }, + { GS_APP_PERMISSIONS_FLAGS_HOME_READ, N_("Home folder"), N_("Can view files") }, + { GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL, N_("File system"), N_("Can view, edit and create files") }, + { GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ, N_("File system"), N_("Can view files") }, + /* The GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_OTHER is used only as a flag, with actual files being part of the read/full lists */ + { GS_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL, N_("Downloads folder"), N_("Can view, edit and create files") }, + { GS_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ, N_("Downloads folder"), N_("Can view files") }, + { GS_APP_PERMISSIONS_FLAGS_SETTINGS, N_("Settings"), N_("Can view and change any settings") }, + { GS_APP_PERMISSIONS_FLAGS_X11, N_("Legacy display system"), N_("Uses an old, insecure display system") }, + { GS_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX, N_("Sandbox escape"), N_("Can escape the sandbox and circumvent any other restrictions") }, +}; + +static void +add_permissions_row (GsAppDetailsPage *page, + const gchar *title, + const gchar *subtitle, + gboolean is_warning_row) +{ + GtkWidget *row, *image; + + row = adw_action_row_new (); + if (is_warning_row) + gtk_style_context_add_class (gtk_widget_get_style_context (row), "permission-row-warning"); + + image = gtk_image_new_from_icon_name ("dialog-warning-symbolic"); + if (!is_warning_row) + gtk_widget_set_opacity (image, 0); + +#if ADW_CHECK_VERSION(1,2,0) + adw_preferences_row_set_use_markup (ADW_PREFERENCES_ROW (row), FALSE); +#endif + adw_action_row_add_prefix (ADW_ACTION_ROW (row), image); + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), title); + adw_action_row_set_subtitle (ADW_ACTION_ROW (row), subtitle); + + gtk_list_box_append (GTK_LIST_BOX (page->permissions_section_list), row); +} + +static void +populate_permissions_filesystem (GsAppDetailsPage *page, + const GPtrArray *titles, /* (element-type utf-8) */ + const gchar *subtitle, + gboolean is_warning_row) +{ + if (titles == NULL) + return; + + for (guint i = 0; i < titles->len; i++) { + const gchar *title = g_ptr_array_index (titles, i); + add_permissions_row (page, title, subtitle, is_warning_row); + } +} + +static void +populate_permissions_section (GsAppDetailsPage *page, + GsAppPermissions *permissions) +{ + GsAppPermissionsFlags flags = gs_app_permissions_get_flags (permissions); + + gs_widget_remove_all (page->permissions_section_list, (GsRemoveFunc) gtk_list_box_remove); + + for (gsize i = 0; i < G_N_ELEMENTS (permission_display_data); i++) { + if ((flags & permission_display_data[i].permission) == 0) + continue; + + add_permissions_row (page, + _(permission_display_data[i].title), + _(permission_display_data[i].subtitle), + (permission_display_data[i].permission & ~MEDIUM_PERMISSIONS) != 0); + } + + populate_permissions_filesystem (page, + gs_app_permissions_get_filesystem_read (permissions), + _("Can view files"), + (GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ & ~MEDIUM_PERMISSIONS) != 0); + + populate_permissions_filesystem (page, + gs_app_permissions_get_filesystem_full (permissions), + _("Can view, edit and create files"), + (GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL & ~MEDIUM_PERMISSIONS) != 0); +} + +static void +set_updates_description_ui (GsAppDetailsPage *page, GsApp *app) +{ + g_autoptr(GIcon) icon = NULL; + guint icon_size; + const gchar *update_details; + GdkDisplay *display; + g_autoptr (GtkIconPaintable) paintable = NULL; + + /* FIXME support app == NULL */ + + /* set window title */ + adw_window_title_set_title (page->window_title, _("Update Details")); + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_TITLE]); + + /* set update header */ + update_details = gs_app_get_update_details_markup (app); + if (update_details == NULL) { + /* TRANSLATORS: this is where the packager did not write + * a description for the update */ + update_details = _("No update description available."); + } + gtk_label_set_markup (GTK_LABEL (page->label_details), update_details); + adw_status_page_set_title (ADW_STATUS_PAGE (page->status_page), gs_app_get_name (app)); + adw_status_page_set_description (ADW_STATUS_PAGE (page->status_page), gs_app_get_summary (app)); + + /* set the icon; fall back to 64px if 96px isn’t available, which sometimes + * happens at 2× scale factor (hi-DPI) */ + icon_size = 96; + icon = gs_app_get_icon_for_size (app, + icon_size, + gtk_widget_get_scale_factor (GTK_WIDGET (page)), + NULL); + if (icon == NULL) { + icon_size = 64; + icon = gs_app_get_icon_for_size (app, + icon_size, + gtk_widget_get_scale_factor (GTK_WIDGET (page)), + NULL); + } + if (icon == NULL) { + icon_size = 96; + icon = gs_app_get_icon_for_size (app, + icon_size, + gtk_widget_get_scale_factor (GTK_WIDGET (page)), + "system-component-application"); + } + + display = gdk_display_get_default (); + paintable = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_for_display (display), + icon, + icon_size, + gtk_widget_get_scale_factor (GTK_WIDGET (page)), + gtk_widget_get_direction (GTK_WIDGET (page)), + GTK_ICON_LOOKUP_FORCE_REGULAR); + adw_status_page_set_paintable (ADW_STATUS_PAGE (page->status_page), GDK_PAINTABLE (paintable)); + + if (gs_app_has_quirk (app, GS_APP_QUIRK_NEW_PERMISSIONS)) { + g_autoptr(GsAppPermissions) permissions = gs_app_dup_update_permissions (app); + gtk_widget_show (page->permissions_section); + populate_permissions_section (page, permissions); + } else { + gtk_widget_hide (page->permissions_section); + } +} + +/** + * gs_app_details_page_get_app: + * @page: a #GsAppDetailsPage + * + * Get the value of #GsAppDetailsPage:app. + * + * Returns: (nullable) (transfer none): the app + * + * Since: 41 + */ +GsApp * +gs_app_details_page_get_app (GsAppDetailsPage *page) +{ + g_return_val_if_fail (GS_IS_APP_DETAILS_PAGE (page), NULL); + return page->app; +} + +/** + * gs_app_details_page_set_app: + * @page: a #GsAppDetailsPage + * @app: (transfer none) (nullable): new app + * + * Set the value of #GsAppDetailsPage:app. + * + * Since: 41 + */ +void +gs_app_details_page_set_app (GsAppDetailsPage *page, GsApp *app) +{ + g_return_if_fail (GS_IS_APP_DETAILS_PAGE (page)); + g_return_if_fail (!app || GS_IS_APP (app)); + + if (page->app == app) + return; + + g_set_object (&page->app, app); + + set_updates_description_ui (page, app); + + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_APP]); +} + +/** + * gs_app_details_page_get_show_back_button: + * @page: a #GsAppDetailsPage + * + * Get the value of #GsAppDetailsPage:show-back-button. + * + * Returns: whether to show the back button + * + * Since: 41 + */ +gboolean +gs_app_details_page_get_show_back_button (GsAppDetailsPage *page) +{ + g_return_val_if_fail (GS_IS_APP_DETAILS_PAGE (page), FALSE); + return gtk_widget_get_visible (page->back_button); +} + +/** + * gs_app_details_page_set_show_back_button: + * @page: a #GsAppDetailsPage + * @show_back_button: whether to show the back button + * + * Set the value of #GsAppDetailsPage:show-back-button. + * + * Since: 41 + */ +void +gs_app_details_page_set_show_back_button (GsAppDetailsPage *page, gboolean show_back_button) +{ + g_return_if_fail (GS_IS_APP_DETAILS_PAGE (page)); + + show_back_button = !!show_back_button; + + if (gtk_widget_get_visible (page->back_button) == show_back_button) + return; + + gtk_widget_set_visible (page->back_button, show_back_button); + + g_object_notify_by_pspec (G_OBJECT (page), obj_props[PROP_SHOW_BACK_BUTTON]); +} + +static void +back_clicked_cb (GtkWidget *widget, GsAppDetailsPage *page) +{ + g_signal_emit (page, signals[SIGNAL_BACK_CLICKED], 0); +} + +static void +gs_app_details_page_dispose (GObject *object) +{ + GsAppDetailsPage *page = GS_APP_DETAILS_PAGE (object); + + g_clear_object (&page->app); + + G_OBJECT_CLASS (gs_app_details_page_parent_class)->dispose (object); +} + +static void +gs_app_details_page_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + GsAppDetailsPage *page = GS_APP_DETAILS_PAGE (object); + + switch ((GsAppDetailsPageProperty) prop_id) { + case PROP_APP: + g_value_set_object (value, gs_app_details_page_get_app (page)); + break; + case PROP_SHOW_BACK_BUTTON: + g_value_set_boolean (value, gs_app_details_page_get_show_back_button (page)); + break; + case PROP_TITLE: + g_value_set_string (value, adw_window_title_get_title (page->window_title)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_app_details_page_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + GsAppDetailsPage *page = GS_APP_DETAILS_PAGE (object); + + switch ((GsAppDetailsPageProperty) prop_id) { + case PROP_APP: + gs_app_details_page_set_app (page, g_value_get_object (value)); + break; + case PROP_SHOW_BACK_BUTTON: + gs_app_details_page_set_show_back_button (page, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_app_details_page_init (GsAppDetailsPage *page) +{ + gtk_widget_init_template (GTK_WIDGET (page)); +} + +static void +gs_app_details_page_class_init (GsAppDetailsPageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gs_app_details_page_dispose; + object_class->get_property = gs_app_details_page_get_property; + object_class->set_property = gs_app_details_page_set_property; + + /** + * GsAppDetailsPage:app: (nullable) + * + * The app to present. + * + * Since: 41 + */ + obj_props[PROP_APP] = + g_param_spec_object ("app", NULL, NULL, + GS_TYPE_APP, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GsAppDetailsPage:show-back-button + * + * Whether to show the back button. + * + * Since: 41 + */ + obj_props[PROP_SHOW_BACK_BUTTON] = + g_param_spec_boolean ("show-back-button", NULL, NULL, + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * GsAppDetailsPage:title + * + * Read-only window title. + * + * Since: 42 + */ + obj_props[PROP_TITLE] = + g_param_spec_string ("title", NULL, NULL, + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props); + + /** + * GsAppDetailsPage:back-clicked: + * @app: a #GsApp + * + * Emitted when the back button got activated and the #GsUpdateDialog + * containing this page is expected to go back. + * + * Since: 41 + */ + signals[SIGNAL_BACK_CLICKED] = + g_signal_new ("back-clicked", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + 0, NULL, NULL, g_cclosure_marshal_generic, + G_TYPE_NONE, 0); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-app-details-page.ui"); + + gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, back_button); + gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, header_bar); + gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, label_details); + gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, permissions_section); + gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, permissions_section_list); + gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, status_page); + gtk_widget_class_bind_template_child (widget_class, GsAppDetailsPage, window_title); + gtk_widget_class_bind_template_callback (widget_class, back_clicked_cb); +} + +/** + * gs_app_details_page_new: + * + * Create a new #GsAppDetailsPage. + * + * Returns: (transfer full): a new #GsAppDetailsPage + * Since: 41 + */ +GtkWidget * +gs_app_details_page_new (void) +{ + return GTK_WIDGET (g_object_new (GS_TYPE_APP_DETAILS_PAGE, NULL)); +} |