diff options
Diffstat (limited to 'src/gs-safety-context-dialog.c')
-rw-r--r-- | src/gs-safety-context-dialog.c | 651 |
1 files changed, 651 insertions, 0 deletions
diff --git a/src/gs-safety-context-dialog.c b/src/gs-safety-context-dialog.c new file mode 100644 index 0000000..ef6c054 --- /dev/null +++ b/src/gs-safety-context-dialog.c @@ -0,0 +1,651 @@ +/* -*- 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 LLC + * + * Author: Philip Withnall <pwithnall@endlessos.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +/** + * SECTION:gs-safety-context-dialog + * @short_description: A dialog showing safety information about an app + * + * #GsSafetyContextDialog is a dialog which shows detailed information about + * how safe or trustworthy an app is. This information is derived from the + * permissions the app requires to run, its runtime, origin, and various other + * sources. + * + * It is designed to show a more detailed view of the information which the + * app’s safety tile in #GsAppContextBar is derived from. + * + * The widget has no special appearance if the app is unset, so callers will + * typically want to hide the dialog in that case. + * + * Since: 41 + */ + +#include "config.h" + +#include <adwaita.h> +#include <glib.h> +#include <glib-object.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <locale.h> + +#include "gs-app.h" +#include "gs-common.h" +#include "gs-context-dialog-row.h" +#include "gs-lozenge.h" +#include "gs-safety-context-dialog.h" + +struct _GsSafetyContextDialog +{ + GsInfoWindow parent_instance; + + GsApp *app; /* (nullable) (owned) */ + gulong app_notify_handler_permissions; + gulong app_notify_handler_name; + gulong app_notify_handler_quirk; + gulong app_notify_handler_license; + gulong app_notify_handler_related; + + GtkWidget *lozenge; + GtkLabel *title; + GtkListBox *permissions_list; + + GtkLabel *license_label; + GBinding *license_label_binding; /* (owned) (nullable) */ + GtkLabel *source_label; + GBinding *source_label_binding; /* (owned) (nullable) */ + GtkLabel *sdk_label; + GtkImage *sdk_eol_image; + GtkWidget *sdk_row; +}; + +G_DEFINE_TYPE (GsSafetyContextDialog, gs_safety_context_dialog, GS_TYPE_INFO_WINDOW) + +typedef enum { + PROP_APP = 1, +} GsSafetyContextDialogProperty; + +static GParamSpec *obj_props[PROP_APP + 1] = { NULL, }; + +/* @icon_name_without_permission, @title_without_permission and + * @description_without_permission are all nullable. If they are NULL, no row + * is added if @has_permission is false. */ +static void +add_permission_row (GtkListBox *list_box, + GsContextDialogRowImportance *chosen_rating, + gboolean has_permission, + GsContextDialogRowImportance item_rating, + const gchar *icon_name_with_permission, + const gchar *title_with_permission, + const gchar *description_with_permission, + const gchar *icon_name_without_permission, + const gchar *title_without_permission, + const gchar *description_without_permission) +{ + GtkListBoxRow *row; + + if (has_permission && item_rating > *chosen_rating) + *chosen_rating = item_rating; + + if (!has_permission && title_without_permission == NULL) + return; + + row = gs_context_dialog_row_new (has_permission ? icon_name_with_permission : icon_name_without_permission, + has_permission ? item_rating : GS_CONTEXT_DIALOG_ROW_IMPORTANCE_UNIMPORTANT, + has_permission ? title_with_permission : title_without_permission, + has_permission ? description_with_permission : description_without_permission); + gtk_list_box_append (list_box, GTK_WIDGET (row)); +} + +static void +update_permissions_list (GsSafetyContextDialog *self) +{ + const gchar *icon_name, *css_class; + g_autofree gchar *title = NULL; + g_autofree gchar *description = NULL; + g_autoptr(GPtrArray) descriptions = g_ptr_array_new_with_free_func (NULL); + g_autoptr(GsAppPermissions) permissions = NULL; + GsAppPermissionsFlags perm_flags = GS_APP_PERMISSIONS_FLAGS_UNKNOWN; + GtkStyleContext *context; + GsContextDialogRowImportance chosen_rating; + + /* Treat everything as safe to begin with, and downgrade its safety + * based on app properties. */ + chosen_rating = GS_CONTEXT_DIALOG_ROW_IMPORTANCE_UNIMPORTANT; + + gs_widget_remove_all (GTK_WIDGET (self->permissions_list), (GsRemoveFunc) gtk_list_box_remove); + + /* UI state is undefined if app is not set. */ + if (self->app == NULL) + return; + + permissions = gs_app_dup_permissions (self->app); + if (permissions != NULL) + perm_flags = gs_app_permissions_get_flags (permissions); + + /* Handle unknown permissions. This means the application isn’t + * sandboxed, so we can only really base decisions on whether it was + * packaged by an organisation we trust or not. + * + * FIXME: See the comment for GS_APP_PERMISSIONS_FLAGS_UNKNOWN in + * gs-app-context-bar.c. */ + if (perm_flags == GS_APP_PERMISSIONS_FLAGS_UNKNOWN) { + add_permission_row (self->permissions_list, &chosen_rating, + !gs_app_has_quirk (self->app, GS_APP_QUIRK_PROVENANCE), + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_WARNING, + "channel-insecure-symbolic", + _("Provided by a third party"), + _("Check that you trust the vendor, as the application isn’t sandboxed"), + "channel-secure-symbolic", + _("Reviewed by your distribution"), + _("Application isn’t sandboxed but the distribution has checked that it is not malicious")); + } else { + const GPtrArray *filesystem_read, *filesystem_full; + + filesystem_read = gs_app_permissions_get_filesystem_read (permissions); + filesystem_full = gs_app_permissions_get_filesystem_full (permissions); + + add_permission_row (self->permissions_list, &chosen_rating, + (perm_flags & GS_APP_PERMISSIONS_FLAGS_NONE) != 0 && + filesystem_read == NULL && filesystem_full == NULL, + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_UNIMPORTANT, + "folder-documents-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("No Permissions"), + _("App is fully sandboxed"), + NULL, NULL, NULL); + add_permission_row (self->permissions_list, &chosen_rating, + (perm_flags & GS_APP_PERMISSIONS_FLAGS_NETWORK) != 0, + /* This isn’t actually unimportant (network access can expand a local + * vulnerability into a remotely exploitable one), but it’s + * needed commonly enough that marking it as + * %GS_CONTEXT_DIALOG_ROW_IMPORTANCE_WARNING is too noisy. */ + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_NEUTRAL, + "network-wireless-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Network Access"), + _("Can access the internet"), + "network-wireless-disabled-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("No Network Access"), + _("Cannot access the internet")); + add_permission_row (self->permissions_list, &chosen_rating, + (perm_flags & GS_APP_PERMISSIONS_FLAGS_SYSTEM_BUS) != 0, + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_WARNING, + "emblem-system-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Uses System Services"), + _("Can request data from system services"), + NULL, NULL, NULL); + add_permission_row (self->permissions_list, &chosen_rating, + (perm_flags & GS_APP_PERMISSIONS_FLAGS_SESSION_BUS) != 0, + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_IMPORTANT, + "emblem-system-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Uses Session Services"), + _("Can request data from session services"), + NULL, NULL, NULL); + add_permission_row (self->permissions_list, &chosen_rating, + (perm_flags & GS_APP_PERMISSIONS_FLAGS_DEVICES) != 0, + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_WARNING, + "camera-photo-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Device Access"), + _("Can access devices such as webcams or gaming controllers"), + "camera-disabled-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("No Device Access"), + _("Cannot access devices such as webcams or gaming controllers")); + add_permission_row (self->permissions_list, &chosen_rating, + (perm_flags & GS_APP_PERMISSIONS_FLAGS_X11) != 0, + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_IMPORTANT, + "desktop-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Legacy Windowing System"), + _("Uses a legacy windowing system"), + NULL, NULL, NULL); + add_permission_row (self->permissions_list, &chosen_rating, + (perm_flags & GS_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX) != 0, + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_IMPORTANT, + "dialog-warning-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Arbitrary Permissions"), + _("Can acquire arbitrary permissions"), + NULL, NULL, NULL); + add_permission_row (self->permissions_list, &chosen_rating, + (perm_flags & GS_APP_PERMISSIONS_FLAGS_SETTINGS) != 0, + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_WARNING, + "preferences-system-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("User Settings"), + _("Can access and change user settings"), + NULL, NULL, NULL); + + /* File system permissions are a bit more complex, since there are + * varying scopes of what’s readable/writable, and a difference between + * read-only and writable access. */ + add_permission_row (self->permissions_list, &chosen_rating, + (perm_flags & GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL) != 0, + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_IMPORTANT, + "folder-documents-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Full File System Read/Write Access"), + _("Can read and write all data on the file system"), + NULL, NULL, NULL); + add_permission_row (self->permissions_list, &chosen_rating, + ((perm_flags & GS_APP_PERMISSIONS_FLAGS_HOME_FULL) != 0 && + !(perm_flags & GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL)), + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_IMPORTANT, + "user-home-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Home Folder Read/Write Access"), + _("Can read and write all data in your home directory"), + NULL, NULL, NULL); + add_permission_row (self->permissions_list, &chosen_rating, + ((perm_flags & GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ) != 0 && + !(perm_flags & GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL)), + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_IMPORTANT, + "folder-documents-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Full File System Read Access"), + _("Can read all data on the file system"), + NULL, NULL, NULL); + add_permission_row (self->permissions_list, &chosen_rating, + ((perm_flags & GS_APP_PERMISSIONS_FLAGS_HOME_READ) != 0 && + !(perm_flags & (GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | + GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ))), + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_IMPORTANT, + "user-home-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Home Folder Read Access"), + _("Can read all data in your home directory"), + NULL, NULL, NULL); + add_permission_row (self->permissions_list, &chosen_rating, + ((perm_flags & GS_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL) != 0 && + !(perm_flags & (GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | + GS_APP_PERMISSIONS_FLAGS_HOME_FULL))), + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_WARNING, + "folder-download-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Download Folder Read/Write Access"), + _("Can read and write all data in your downloads directory"), + NULL, NULL, NULL); + add_permission_row (self->permissions_list, &chosen_rating, + ((perm_flags & GS_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ) != 0 && + !(perm_flags & (GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | + GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ | + GS_APP_PERMISSIONS_FLAGS_HOME_FULL | + GS_APP_PERMISSIONS_FLAGS_HOME_READ))), + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_WARNING, + "folder-download-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Download Folder Read Access"), + _("Can read all data in your downloads directory"), + NULL, NULL, NULL); + + for (guint i = 0; filesystem_full != NULL && i < filesystem_full->len; i++) { + const gchar *fs_title = g_ptr_array_index (filesystem_full, i); + add_permission_row (self->permissions_list, &chosen_rating, + TRUE, + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_WARNING, + "folder-documents-symbolic", + fs_title, + _("Can read and write all data in the directory"), + NULL, NULL, NULL); + } + + for (guint i = 0; filesystem_read != NULL && i < filesystem_read->len; i++) { + const gchar *fs_title = g_ptr_array_index (filesystem_read, i); + add_permission_row (self->permissions_list, &chosen_rating, + TRUE, + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_WARNING, + "folder-documents-symbolic", + fs_title, + _("Can read all data in the directory"), + NULL, NULL, NULL); + } + + add_permission_row (self->permissions_list, &chosen_rating, + !(perm_flags & (GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL | + GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ | + GS_APP_PERMISSIONS_FLAGS_FILESYSTEM_OTHER | + GS_APP_PERMISSIONS_FLAGS_HOME_FULL | + GS_APP_PERMISSIONS_FLAGS_HOME_READ | + GS_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL | + GS_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ)) && + filesystem_read == NULL && filesystem_full == NULL, + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_UNIMPORTANT, + "folder-documents-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("No File System Access"), + _("Cannot access the file system at all"), + NULL, NULL, NULL); + } + + /* Is the code FOSS and hence inspectable? This doesn’t distinguish + * between closed source and open-source-but-not-FOSS software, even + * though the code of the latter is technically publicly auditable. This + * is because I don’t want to get into the business of maintaining lists + * of ‘auditable’ source code licenses. */ + add_permission_row (self->permissions_list, &chosen_rating, + !gs_app_get_license_is_free (self->app), + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_WARNING, + "dialog-warning-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Proprietary Code"), + _("The source code is not public, so it cannot be independently audited and might be unsafe"), + "app-installed-symbolic", + /* Translators: This refers to permissions (for example, from flatpak) which an app requests from the user. */ + _("Auditable Code"), + _("The source code is public and can be independently audited, which makes the app more likely to be safe")); + + add_permission_row (self->permissions_list, &chosen_rating, + gs_app_has_quirk (self->app, GS_APP_QUIRK_DEVELOPER_VERIFIED), + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_UNIMPORTANT, + "app-installed-symbolic", + /* Translators: This indicates an app was written and released by a developer who has been verified. + * It’s used in a context tile, so should be short. */ + _("App developer is verified"), + _("The developer of this app has been verified to be who they say they are"), + NULL, NULL, NULL); + + add_permission_row (self->permissions_list, &chosen_rating, + gs_app_get_metadata_item (self->app, "GnomeSoftware::EolReason") != NULL || ( + gs_app_get_runtime (self->app) != NULL && + gs_app_get_metadata_item (gs_app_get_runtime (self->app), "GnomeSoftware::EolReason") != NULL), + GS_CONTEXT_DIALOG_ROW_IMPORTANCE_IMPORTANT, + "dialog-warning-symbolic", + /* Translators: This indicates an app uses an outdated SDK. + * It’s used in a context tile, so should be short. */ + _("Insecure Dependencies"), + _("Software or its dependencies are no longer supported and may be insecure"), + NULL, NULL, NULL); + + /* Update the UI. */ + switch (chosen_rating) { + case GS_CONTEXT_DIALOG_ROW_IMPORTANCE_UNIMPORTANT: + icon_name = "safety-symbolic"; + /* Translators: The app is considered safe to install and run. + * The placeholder is the app name. */ + title = g_strdup_printf (_("%s is safe"), gs_app_get_name (self->app)); + css_class = "green"; + break; + case GS_CONTEXT_DIALOG_ROW_IMPORTANCE_WARNING: + icon_name = "dialog-question-symbolic"; + /* Translators: The app is considered potentially unsafe to install and run. + * The placeholder is the app name. */ + title = g_strdup_printf (_("%s is potentially unsafe"), gs_app_get_name (self->app)); + css_class = "yellow"; + break; + case GS_CONTEXT_DIALOG_ROW_IMPORTANCE_IMPORTANT: + icon_name = "dialog-warning-symbolic"; + /* Translators: The app is considered unsafe to install and run. + * The placeholder is the app name. */ + title = g_strdup_printf (_("%s is unsafe"), gs_app_get_name (self->app)); + css_class = "red"; + break; + default: + g_assert_not_reached (); + } + + gs_lozenge_set_icon_name (GS_LOZENGE (self->lozenge), icon_name); + gtk_label_set_text (self->title, title); + + context = gtk_widget_get_style_context (self->lozenge); + + gtk_style_context_remove_class (context, "green"); + gtk_style_context_remove_class (context, "yellow"); + gtk_style_context_remove_class (context, "red"); + + gtk_style_context_add_class (context, css_class); +} + +static void +app_notify_cb (GObject *obj, + GParamSpec *pspec, + gpointer user_data) +{ + GsSafetyContextDialog *self = GS_SAFETY_CONTEXT_DIALOG (user_data); + + update_permissions_list (self); +} + +static void +update_sdk (GsSafetyContextDialog *self) +{ + GsApp *runtime; + + /* UI state is undefined if app is not set. */ + if (self->app == NULL) + return; + + runtime = gs_app_get_runtime (self->app); + + if (runtime != NULL) { + GtkStyleContext *context; + g_autofree gchar *label = NULL; + const gchar *version = gs_app_get_version_ui (runtime); + gboolean is_eol = gs_app_get_metadata_item (runtime, "GnomeSoftware::EolReason") != NULL; + + if (version != NULL) { + /* Translators: The first placeholder is an app runtime + * name, the second is its version number. */ + label = g_strdup_printf (_("%s (%s)"), + gs_app_get_name (runtime), + version); + } else { + label = g_strdup (gs_app_get_name (runtime)); + } + + gtk_label_set_label (self->sdk_label, label); + + context = gtk_widget_get_style_context (GTK_WIDGET (self->sdk_label)); + + if (is_eol) { + gtk_style_context_add_class (context, "eol-red"); + gtk_style_context_remove_class (context, "dim-label"); + } else { + gtk_style_context_add_class (context, "dim-label"); + gtk_style_context_remove_class (context, "eol-red"); + } + + gtk_widget_set_visible (GTK_WIDGET (self->sdk_eol_image), is_eol); + } + + /* Only show the row if a runtime was found. */ + gtk_widget_set_visible (self->sdk_row, (runtime != NULL)); +} + +static void +app_notify_related_cb (GObject *obj, + GParamSpec *pspec, + gpointer user_data) +{ + GsSafetyContextDialog *self = GS_SAFETY_CONTEXT_DIALOG (user_data); + + update_sdk (self); +} + +static void +gs_safety_context_dialog_init (GsSafetyContextDialog *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +gs_safety_context_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsSafetyContextDialog *self = GS_SAFETY_CONTEXT_DIALOG (object); + + switch ((GsSafetyContextDialogProperty) prop_id) { + case PROP_APP: + g_value_set_object (value, gs_safety_context_dialog_get_app (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_safety_context_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsSafetyContextDialog *self = GS_SAFETY_CONTEXT_DIALOG (object); + + switch ((GsSafetyContextDialogProperty) prop_id) { + case PROP_APP: + gs_safety_context_dialog_set_app (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_safety_context_dialog_dispose (GObject *object) +{ + GsSafetyContextDialog *self = GS_SAFETY_CONTEXT_DIALOG (object); + + gs_safety_context_dialog_set_app (self, NULL); + + G_OBJECT_CLASS (gs_safety_context_dialog_parent_class)->dispose (object); +} + +static void +gs_safety_context_dialog_class_init (GsSafetyContextDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = gs_safety_context_dialog_get_property; + object_class->set_property = gs_safety_context_dialog_set_property; + object_class->dispose = gs_safety_context_dialog_dispose; + + /** + * GsSafetyContextDialog:app: (nullable) + * + * The app to display the safety context details for. + * + * This may be %NULL; if so, the content of the widget will be + * undefined. + * + * Since: 41 + */ + obj_props[PROP_APP] = + g_param_spec_object ("app", NULL, NULL, + GS_TYPE_APP, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-safety-context-dialog.ui"); + + gtk_widget_class_bind_template_child (widget_class, GsSafetyContextDialog, lozenge); + gtk_widget_class_bind_template_child (widget_class, GsSafetyContextDialog, title); + gtk_widget_class_bind_template_child (widget_class, GsSafetyContextDialog, permissions_list); + gtk_widget_class_bind_template_child (widget_class, GsSafetyContextDialog, license_label); + gtk_widget_class_bind_template_child (widget_class, GsSafetyContextDialog, source_label); + gtk_widget_class_bind_template_child (widget_class, GsSafetyContextDialog, sdk_label); + gtk_widget_class_bind_template_child (widget_class, GsSafetyContextDialog, sdk_eol_image); + gtk_widget_class_bind_template_child (widget_class, GsSafetyContextDialog, sdk_row); +} + +/** + * gs_safety_context_dialog_new: + * @app: (nullable): the app to display safety context information for, or %NULL + * + * Create a new #GsSafetyContextDialog and set its initial app to @app. + * + * Returns: (transfer full): a new #GsSafetyContextDialog + * Since: 41 + */ +GsSafetyContextDialog * +gs_safety_context_dialog_new (GsApp *app) +{ + g_return_val_if_fail (app == NULL || GS_IS_APP (app), NULL); + + return g_object_new (GS_TYPE_SAFETY_CONTEXT_DIALOG, + "app", app, + NULL); +} + +/** + * gs_safety_context_dialog_get_app: + * @self: a #GsSafetyContextDialog + * + * Gets the value of #GsSafetyContextDialog:app. + * + * Returns: (nullable) (transfer none): app whose safety context information is + * being displayed, or %NULL if none is set + * Since: 41 + */ +GsApp * +gs_safety_context_dialog_get_app (GsSafetyContextDialog *self) +{ + g_return_val_if_fail (GS_IS_SAFETY_CONTEXT_DIALOG (self), NULL); + + return self->app; +} + +/** + * gs_safety_context_dialog_set_app: + * @self: a #GsSafetyContextDialog + * @app: (nullable) (transfer none): the app to display safety context + * information for, or %NULL for none + * + * Set the value of #GsSafetyContextDialog:app. + * + * Since: 41 + */ +void +gs_safety_context_dialog_set_app (GsSafetyContextDialog *self, + GsApp *app) +{ + g_return_if_fail (GS_IS_SAFETY_CONTEXT_DIALOG (self)); + g_return_if_fail (app == NULL || GS_IS_APP (app)); + + if (app == self->app) + return; + + g_clear_signal_handler (&self->app_notify_handler_permissions, self->app); + g_clear_signal_handler (&self->app_notify_handler_name, self->app); + g_clear_signal_handler (&self->app_notify_handler_quirk, self->app); + g_clear_signal_handler (&self->app_notify_handler_license, self->app); + g_clear_signal_handler (&self->app_notify_handler_related, self->app); + + g_clear_object (&self->license_label_binding); + g_clear_object (&self->source_label_binding); + + g_set_object (&self->app, app); + + if (self->app != NULL) { + self->app_notify_handler_permissions = g_signal_connect (self->app, "notify::permissions", G_CALLBACK (app_notify_cb), self); + self->app_notify_handler_name = g_signal_connect (self->app, "notify::name", G_CALLBACK (app_notify_cb), self); + self->app_notify_handler_quirk = g_signal_connect (self->app, "notify::quirk", G_CALLBACK (app_notify_cb), self); + self->app_notify_handler_license = g_signal_connect (self->app, "notify::license", G_CALLBACK (app_notify_cb), self); + + self->app_notify_handler_related = g_signal_connect (self->app, "notify::related", G_CALLBACK (app_notify_related_cb), self); + + self->license_label_binding = g_object_bind_property (self->app, "license", self->license_label, "label", G_BINDING_SYNC_CREATE); + self->source_label_binding = g_object_bind_property (self->app, "origin-ui", self->source_label, "label", G_BINDING_SYNC_CREATE); + } + + /* Update the UI. */ + update_permissions_list (self); + update_sdk (self); + + g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_APP]); +} |